Full Code of toucansites/toucan for AI

main c00c213fba90 cached
217 files
908.3 KB
197.6k tokens
1 requests
Download .txt
Showing preview only (978K chars total). Download the full file or copy to clipboard to get everything.
Repository: toucansites/toucan
Branch: main
Commit: c00c213fba90
Files: 217
Total size: 908.3 KB

Directory structure:
gitextract__gpz1oly/

├── .github/
│   └── workflows/
│       ├── actions.yml
│       ├── linux.yml
│       ├── macos.yml
│       ├── pr-for-formula.yml
│       └── tag_actions.yml
├── .gitignore
├── .swift-format
├── .swiftformat
├── .swiftformatignore
├── .swiftheaderignore
├── Docker/
│   ├── Dockerfile
│   └── Dockerfile.testing
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   ├── ToucanCore/
│   │   ├── Extensions/
│   │   │   ├── Dictionary+Extensions.swift
│   │   │   ├── Logging+Extensions.swift
│   │   │   ├── String+Extensions.swift
│   │   │   └── URL+Extensions.swift
│   │   ├── GeneratorInfo.swift
│   │   ├── Logger.swift
│   │   └── ToucanError.swift
│   ├── ToucanMarkdown/
│   │   ├── Markdown/
│   │   │   ├── HTML.swift
│   │   │   ├── HTMLVisitor.swift
│   │   │   ├── MarkdownBlockDirective.swift
│   │   │   └── MarkdownToHTMLRenderer.swift
│   │   ├── MarkdownRenderer.swift
│   │   ├── Outline/
│   │   │   ├── Outline.swift
│   │   │   └── OutlineParser.swift
│   │   ├── ReadingTime/
│   │   │   └── ReadingTimeCalculator.swift
│   │   └── Transformers/
│   │       ├── ContentTransformer.swift
│   │       ├── TransformerExecutor.swift
│   │       └── TransformerPipeline.swift
│   ├── ToucanSDK/
│   │   ├── Behaviors/
│   │   │   ├── Behavior.swift
│   │   │   ├── CompileSASSBehavior.swift
│   │   │   └── MinifyCSSBehavior.swift
│   │   ├── Content/
│   │   │   ├── Content+Query.swift
│   │   │   ├── Content.swift
│   │   │   ├── ContentResolver.swift
│   │   │   ├── ContentTypeResolver.swift
│   │   │   ├── IteratorInfo.swift
│   │   │   ├── Query+Resolve.swift
│   │   │   └── RelationValue.swift
│   │   ├── DateFormats/
│   │   │   ├── DateContext.swift
│   │   │   └── ToucanDateFormatters.swift
│   │   ├── Models/
│   │   │   ├── ContextBundle.swift
│   │   │   ├── Destination.swift
│   │   │   ├── PipelineResult.swift
│   │   │   └── Slug.swift
│   │   ├── Outputs/
│   │   │   ├── ContextBundleToHTMLRenderer.swift
│   │   │   └── ContextBundleToJSONRenderer.swift
│   │   ├── Renderers/
│   │   │   ├── BuildTargetSourceRenderer.swift
│   │   │   └── MustacheRenderer.swift
│   │   ├── Toucan.swift
│   │   ├── Utilities/
│   │   │   ├── Any+AnyCodable.swift
│   │   │   ├── AnyCodable+Json.swift
│   │   │   ├── Array+AnyCodable.swift
│   │   │   ├── ContextKeys.swift
│   │   │   ├── CopyManager.swift
│   │   │   ├── Dictionary+AnyCodable.swift
│   │   │   ├── Encodable+Json.swift
│   │   │   └── FirstSucceeding.swift
│   │   └── Validators/
│   │       ├── BuildTargetSourceValidator.swift
│   │       └── TemplateValidator.swift
│   ├── ToucanSerialization/
│   │   ├── ToucanDecoder.swift
│   │   ├── ToucanDecoderError.swift
│   │   ├── ToucanEncoder.swift
│   │   ├── ToucanEncoderError.swift
│   │   ├── ToucanJSONDecoder.swift
│   │   ├── ToucanJSONEncoder.swift
│   │   ├── ToucanYAMLDecoder.swift
│   │   └── ToucanYAMLEncoder.swift
│   ├── ToucanSource/
│   │   ├── Errors/
│   │   │   ├── ObjectLoaderError.swift
│   │   │   ├── SourceLoaderError.swift
│   │   │   └── TemplateLoaderError.swift
│   │   ├── Extensions/
│   │   │   ├── Decoder+Validate.swift
│   │   │   ├── Dictionary+AnyCodable.swift
│   │   │   └── FileManagerKit+Extensions.swift
│   │   ├── Loaders/
│   │   │   ├── BuildTargetSourceLoader.swift
│   │   │   ├── ObjectLoader.swift
│   │   │   ├── RawContentLoader.swift
│   │   │   └── TemplateLoader.swift
│   │   ├── MarkdownParser.swift
│   │   ├── Models/
│   │   │   ├── BuildTargetSource.swift
│   │   │   ├── BuiltTargetSourceLocations.swift
│   │   │   ├── Markdown.swift
│   │   │   ├── Origin.swift
│   │   │   ├── Path.swift
│   │   │   ├── RawContent.swift
│   │   │   ├── Template.swift
│   │   │   └── View.swift
│   │   └── Objects/
│   │       ├── AnyCodable.swift
│   │       ├── Blocks/
│   │       │   ├── Block+Attribute.swift
│   │       │   ├── Block+Parameter.swift
│   │       │   └── Block.swift
│   │       ├── Config/
│   │       │   ├── Config+Blocks.swift
│   │       │   ├── Config+Contents.swift
│   │       │   ├── Config+DataTypes+Date.swift
│   │       │   ├── Config+DataTypes.swift
│   │       │   ├── Config+Location.swift
│   │       │   ├── Config+Pipelines.swift
│   │       │   ├── Config+Renderer+ParagraphStyles.swift
│   │       │   ├── Config+RendererConfig.swift
│   │       │   ├── Config+Site.swift
│   │       │   ├── Config+Templates.swift
│   │       │   ├── Config+Types.swift
│   │       │   └── Config.swift
│   │       ├── Date/
│   │       │   ├── DateFormatterConfig.swift
│   │       │   └── DateLocalization.swift
│   │       ├── Pipeline/
│   │       │   ├── Pipeline+Assets.swift
│   │       │   ├── Pipeline+ContentTypes.swift
│   │       │   ├── Pipeline+DataTypes+Date.swift
│   │       │   ├── Pipeline+DataTypes.swift
│   │       │   ├── Pipeline+Engine.swift
│   │       │   ├── Pipeline+Output.swift
│   │       │   ├── Pipeline+Scope+Context.swift
│   │       │   ├── Pipeline+Scope.swift
│   │       │   ├── Pipeline+Transformers+Transformer.swift
│   │       │   ├── Pipeline+Transformers.swift
│   │       │   └── Pipeline.swift
│   │       ├── Property/
│   │       │   ├── Property.swift
│   │       │   ├── PropertyType.swift
│   │       │   └── SystemPropertyKeys.swift
│   │       ├── Query/
│   │       │   ├── Condition.swift
│   │       │   ├── Direction.swift
│   │       │   ├── Operator.swift
│   │       │   ├── Order.swift
│   │       │   └── Query.swift
│   │       ├── Relation/
│   │       │   ├── Relation.swift
│   │       │   └── RelationType.swift
│   │       ├── Settings/
│   │       │   └── Settings.swift
│   │       ├── Target/
│   │       │   ├── Target.swift
│   │       │   └── TargetConfig.swift
│   │       └── Types/
│   │           └── ContentType.swift
│   ├── _GitCommitHash/
│   │   ├── git_commit_hash.c
│   │   └── include/
│   │       └── git_commit_hash.h
│   ├── toucan/
│   │   └── Entrypoint.swift
│   ├── toucan-generate/
│   │   └── Entrypoint.swift
│   ├── toucan-init/
│   │   ├── Download.swift
│   │   └── Entrypoint.swift
│   ├── toucan-serve/
│   │   ├── Entrypoint.swift
│   │   └── NotFoundMiddleware.swift
│   └── toucan-watch/
│       └── Entrypoint.swift
├── Tests/
│   ├── ToucanCoreTests/
│   │   ├── Extensions/
│   │   │   ├── StringExtensionsTestSuite.swift
│   │   │   └── URLExtensionsTestSuite.swift
│   │   └── ToucanCoreTestSuite.swift
│   ├── ToucanMarkdownTests/
│   │   ├── ContentRendererTestSuite.swift
│   │   ├── HTMLVisitorTestSuite.swift
│   │   ├── MarkdownBlockDirective+Mock.swift
│   │   ├── MarkdownBlockDirectiveTestSuite.swift
│   │   └── OutlineTestSuite.swift
│   ├── ToucanSDKTests/
│   │   ├── BuildTargetSource/
│   │   │   ├── BuildTargetSourceRendererTestSuite.swift
│   │   │   └── BuildTargetSourceValidatorTestSuite.swift
│   │   ├── Content/
│   │   │   ├── ContentQueryTestSuite.swift
│   │   │   └── ContentResolverTestSuite.swift
│   │   ├── DateFormatter/
│   │   │   └── ToucanDateFormatterTestSuite.swift
│   │   ├── E2ETestSuite.swift
│   │   ├── Files/
│   │   │   ├── MarkdownFile.swift
│   │   │   ├── MustacheFile.swift
│   │   │   ├── RawContentBundle.swift
│   │   │   └── YAMLFile.swift
│   │   ├── Mocks/
│   │   │   ├── Mocks+Blocks.swift
│   │   │   ├── Mocks+BuildTargetSources.swift
│   │   │   ├── Mocks+ContentTypes.swift
│   │   │   ├── Mocks+E2E.swift
│   │   │   ├── Mocks+Files.swift
│   │   │   ├── Mocks+Pipelines.swift
│   │   │   ├── Mocks+RawContents.swift
│   │   │   ├── Mocks+Templates.swift
│   │   │   ├── Mocks+Views.swift
│   │   │   └── Mocks.swift
│   │   ├── Template/
│   │   │   └── TemplateValidatorTestSuite.swift
│   │   ├── Toucan/
│   │   │   └── ToucanTestSuite.swift
│   │   └── Utilities/
│   │       ├── AnyCodableWrapTests.swift
│   │       ├── CopyManagerTestSuite.swift
│   │       ├── PrettyPrint.swift
│   │       ├── RecursiveMergeTests.swift
│   │       ├── SlugTests.swift
│   │       └── UnboxingTestSuite.swift
│   └── ToucanSourceTests/
│       ├── BuildTargetSourceLoaderTestSuite.swift
│       ├── Extensions/
│       │   └── FileManagerKitExtensionsTestSuite.swift
│       ├── Files/
│       │   └── YAMLFile.swift
│       ├── MarkdownParserTestSuite.swift
│       ├── Models/
│       │   └── BuildTargetSourceLocationsTestSuite.swift
│       ├── Objects/
│       │   ├── AnyCodableTestSuite.swift
│       │   ├── Config/
│       │   │   └── ConfigTestSuite.swift
│       │   ├── DateFormatting/
│       │   │   └── DateFormattingTestSuite.swift
│       │   ├── Pipeline/
│       │   │   ├── PipelineContentTypeTestSuite.swift
│       │   │   ├── PipelineScopeContextTestSuite.swift
│       │   │   ├── PipelineScopeTestSuite.swift
│       │   │   ├── PipelineTestSuite.swift
│       │   │   └── PipelineTransformersTestSuite.swift
│       │   ├── Property/
│       │   │   ├── PropertyTestSuite.swift
│       │   │   └── PropertyTypeTestSuite.swift
│       │   ├── Query/
│       │   │   ├── ConditionTestSuite.swift
│       │   │   ├── DirectionTestSuite.swift
│       │   │   ├── OperatorTestSuite.swift
│       │   │   ├── OrderTestSuite.swift
│       │   │   └── QueryTestSuite.swift
│       │   ├── Relation/
│       │   │   ├── RelationTestSuite.swift
│       │   │   └── RelationTypeTestSuite.swift
│       │   ├── Settings/
│       │   │   └── SettingsTestSuite.swift
│       │   ├── Target/
│       │   │   ├── TargetConfigTestSuite.swift
│       │   │   └── TargetTestSuite.swift
│       │   └── Types/
│       │       └── TypesTestSuite.swift
│       ├── RawContentLoaderTestSuite.swift
│       └── TemplateLoaderTestSuite.swift
└── scripts/
    ├── install-toucan.sh
    ├── packaging/
    │   ├── deb.sh
    │   ├── dmg.sh
    │   ├── pkg.sh
    │   ├── rpm.sh
    │   └── toucan.spec
    ├── run-chmod.sh
    └── uninstall-toucan.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/actions.yml
================================================
name: Actions

on:
  pull_request:
    branches:
      - main

jobs:

  bb_checks:
    name: BB Checks
    uses: BinaryBirds/github-workflows/.github/workflows/extra_soundness.yml@main
    with:
      local_swift_dependencies_check_enabled : true

  swiftlang_checks:
    name: Swiftlang Checks
    uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
    with:
      license_header_check_project_name: "Toucan"
      format_check_enabled : true
      broken_symlink_check_enabled : true
      unacceptable_language_check_enabled : true
      api_breakage_check_enabled : false
      docs_check_enabled : false
      license_header_check_enabled : false
      shell_check_enabled : false
      yamllint_check_enabled : false
      python_lint_check_enabled : false

  swiftlang_tests:
    name: Swiftlang Tests
    uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
    with:
      enable_windows_checks : false
      linux_build_command: "swift test --parallel --enable-code-coverage"
      linux_exclude_swift_versions: "[{\"swift_version\": \"5.8\"}, {\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}, {\"swift_version\": \"6.0\"}, {\"swift_version\": \"nightly\"}, {\"swift_version\": \"nightly-main\"}, {\"swift_version\": \"nightly-6.0\"}, {\"swift_version\": \"nightly-6.1\"}, {\"swift_version\": \"nightly-6.3\"}]"

================================================
FILE: .github/workflows/linux.yml
================================================
name: Build, Test and Upload Linux Binaries for tag
on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
      run_rpm:
        required: false
        type: boolean
        default: true
      run_deb:
        required: false
        type: boolean
        default: true
      static_stdlib:
        required: false
        type: boolean
        default: true

jobs:
  precheck:
    runs-on: ubuntu-latest
    outputs:
      should_run: ${{ steps.check.outputs.should_run }}
    steps:
      - id: check
        run: |
          if [[ "${{ inputs.run_rpm }}" == "true" || "${{ inputs.run_deb }}" == "true" ]]; then
            echo "✅ At least one packaging format enabled"
            echo "should_run=true" >> $GITHUB_OUTPUT
          else
            echo "🚫 Both run_rpm and run_deb are false — skipping workflow"
            echo "should_run=false" >> $GITHUB_OUTPUT
          fi

  build-binaries:
    needs: precheck
    if: needs.precheck.outputs.should_run == 'true'
    runs-on: ubuntu-latest
    container:
      image: swift:6.3
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4

      - name: Install required Swift tools
        run: |
          chmod +x ./scripts/packaging/*.sh
          apt-get update
          apt-get install -y curl git clang libcurl4-openssl-dev libssl-dev libatomic1 zip

      - name: Install RPM tooling
        if: inputs.run_rpm
        run: apt-get install -y rpm

      - name: Install DEB tooling
        if: inputs.run_deb
        run: apt-get install -y dpkg-dev

      - name: Build with static stdlib
        if: inputs.static_stdlib
        run: |
          echo "🔧 Building with static Swift stdlib"
          swift build -c release -Xswiftc -static-stdlib
      
      - name: Build without static stdlib
        if: ${{ !inputs.static_stdlib }}
        run: |
          echo "🔧 Building without static Swift stdlib"
          swift build -c release

      - name: Build RPM
        if: inputs.run_rpm
        run: ./scripts/packaging/rpm.sh ${{ inputs.version }}

      - name: Verify RPM
        if: inputs.run_rpm
        run: |
          RPM="build-rpm/toucan-linux-x86_64-${{ inputs.version }}.rpm"
          echo "🧪 Verifying $RPM"
          rpm -Kv "$RPM"
          rpm -qp "$RPM"
          echo "✅ RPM passed verification"

      - name: Build DEB
        if: inputs.run_deb
        run: ./scripts/packaging/deb.sh ${{ inputs.version }}

      - name: Verify DEB
        if: inputs.run_deb
        run: |
          DEB="build-deb/toucan-linux-amd64-${{ inputs.version }}.deb"
          echo "🧪 Verifying $DEB"
          dpkg-deb --info "$DEB"
          dpkg-deb --contents "$DEB"
          echo "✅ DEB passed verification"

      - name: Upload Linux artifacts
        if: inputs.run_rpm || inputs.run_deb
        uses: actions/upload-artifact@v4
        with:
          name: linux-artifacts
          retention-days: 1     #no need to store it for 90 days
          path: |
            ${{ inputs.run_rpm && format('build-rpm/toucan-linux-x86_64-{0}.rpm', inputs.version) || '' }}
            ${{ inputs.run_rpm && format('build-rpm/toucan-linux-{0}.zip', inputs.version) || '' }}
            ${{ inputs.run_rpm && format('build-rpm/toucan-linux-{0}.sha256', inputs.version) || '' }}
            ${{ inputs.run_deb && format('build-deb/toucan-linux-amd64-{0}.deb', inputs.version) || '' }}

  test-and-upload:
    runs-on: ubuntu-latest
    needs: build-binaries
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: linux-artifacts
          path: ./packages

      - name: Check unpacked structure
        run: find packages

      - name: Test RPM in Fedora
        if: inputs.run_rpm
        run: |
          docker run --rm -v "$PWD/packages:/packages" fedora \
            bash -c "dnf install -y /packages/build-rpm/toucan-linux-x86_64-${{ inputs.version }}.rpm && toucan --version"

      - name: Upload RPM binary to tag
        if: inputs.run_rpm
        uses: AButler/upload-release-assets@v3.0
        with:
          files: packages/build-rpm/toucan-linux-x86_64-${{ inputs.version }}.rpm
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Upload zipped Linux binaries
        if: inputs.run_rpm
        uses: AButler/upload-release-assets@v3.0
        with:
          files: packages/build-rpm/toucan-linux-${{ inputs.version }}.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Upload SHA256 for zipped Linux binaries
        if: inputs.run_rpm
        uses: AButler/upload-release-assets@v3.0
        with:
          files: packages/build-rpm/toucan-linux-${{ inputs.version }}.sha256
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Test DEB in Ubuntu
        if: inputs.run_deb
        run: |
          docker run --rm -v "$PWD/packages:/packages" ubuntu \
            bash -c '
              apt-get update &&
              apt-get install -y curl &&
              dpkg -i /packages/build-deb/toucan-linux-amd64-${{ inputs.version }}.deb || apt-get install -f -y &&
              toucan --version
            '

      - name: Upload DEB binary to tag
        if: inputs.run_deb
        uses: AButler/upload-release-assets@v3.0
        with:
          files: packages/build-deb/toucan-linux-amd64-${{ inputs.version }}.deb
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

================================================
FILE: .github/workflows/macos.yml
================================================
name: Build and Publish macOS Binaries
on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
      run_pkg:
        required: false
        type: boolean
        default: true
      run_dmg:
        required: false
        type: boolean
        default: true

jobs:

  precheck:
    runs-on: ubuntu-latest
    outputs:
      should_run: ${{ steps.check.outputs.should_run }}
    steps:
      - id: check
        run: |
          if [[ "${{ inputs.run_pkg }}" == "true" || "${{ inputs.run_dmg }}" == "true" ]]; then
            echo "✅ At least one packaging format enabled"
            echo "should_run=true" >> $GITHUB_OUTPUT
          else
            echo "🚫 Both run_pkg and run_dmg are false — skipping workflow"
            echo "should_run=false" >> $GITHUB_OUTPUT
          fi

  build-binaries:
    needs: precheck
    if: needs.precheck.outputs.should_run == 'true'
    runs-on: macos-14
    permissions:
      contents: write
    steps:

      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Import certificates
        uses: apple-actions/import-codesign-certs@v3
        with:
          p12-file-base64: ${{ secrets.MAC_CERTIFICATES }}
          p12-password: ${{ secrets.MAC_CERTIFICATES_PASSWORD }}

      - name: Verify certificates
        run: |
          set -e

          # Certs to check
          certs=("Developer ID Application" "Developer ID Installer")

          # Expiration threshold in days (for warnings)
          warning_days=30
          warning_secs=$((warning_days * 86400))

          for cert_name in "${certs[@]}"; do
            # Try to get the certificate
            if ! cert_pem=$(security find-certificate -c "$cert_name" -p); then
              echo "❌ Certificate '$cert_name' not found in keychain"
              exit 1
            fi

            not_after=$(echo "$cert_pem" | openssl x509 -noout -enddate | cut -d= -f2)
            expiry_ts=$(date -j -f "%b %e %T %Y %Z" "$not_after" +%s 2>/dev/null || date -d "$not_after" +%s)
            now_ts=$(date +%s)

            if [ "$expiry_ts" -le "$now_ts" ]; then
              echo "❌ Certificate '$cert_name' is expired (expired on $not_after)"
              exit 1
            fi

            if [ $((expiry_ts - now_ts)) -le "$warning_secs" ]; then
              echo "⚠️ Certificate '$cert_name' expires soon on $not_after"
            fi
          done

      - name: Install Swift 6.3.1
        run: |
          curl -L https://download.swift.org/swift-6.3.1-release/xcode/swift-6.3.1-RELEASE/swift-6.3.1-RELEASE-osx.pkg -o /tmp/swift.pkg
          sudo installer -pkg /tmp/swift.pkg -target /
          TOOLCHAIN_PATH="/Library/Developer/Toolchains/swift-6.3.1-RELEASE.xctoolchain/usr/bin"
          echo "$TOOLCHAIN_PATH" >> $GITHUB_PATH
          export PATH="$TOOLCHAIN_PATH:$PATH"
          swift --version

      - name: Check for uncommitted changes
        run: |
          git_status=$(git status --porcelain)
          
          if [[ -n "$git_status" ]]; then
            echo "❌ Uncommitted changes detected:"
            echo "$git_status"
            exit 1
          else
            echo "✅ Working directory is clean. No uncommitted changes."
          fi

      - name: Build Swift binaries for arm64 and x86_64
        if: inputs.run_pkg
        run: |
          chmod +x scripts/packaging/pkg.sh
          swift build -c release --arch arm64
          swift build -c release --arch x86_64

      - name: Package .pkg and .zip
        if: inputs.run_pkg
        run: scripts/packaging/pkg.sh ${{ inputs.version }}
        env:
          MAC_APP_IDENTITY: ${{ secrets.MAC_APP_IDENTITY }}
          MAC_INSTALLER_IDENTITY: ${{ secrets.MAC_INSTALLER_IDENTITY }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}

      - name: Verify .pkg file
        if: inputs.run_pkg
        run: |
          PKG="release/toucan-macos-${{ inputs.version }}.pkg"
          echo "🧪 Verifying $PKG"
          pkgutil --payload-files "$PKG"
          echo "✅ PKG passed verification"

      - name: Test installing .pkg file
        if: inputs.run_pkg
        run: |
          PKG="release/toucan-macos-${{ inputs.version }}.pkg"
          echo "📦 Installing $PKG to /"
          sudo installer -pkg "$PKG" -target /
      
          echo "🔍 Checking for installed binaries"
          ls -lh /usr/local/bin/toucan*
      
          echo "📈 Version output:"
          /usr/local/bin/toucan --version || echo "⚠️ toucan binary failed to run"

      - name: Upload .pkg file to tag
        if: inputs.run_pkg
        uses: AButler/upload-release-assets@v3.0
        with:
          files: |
            release/toucan-macos-${{ inputs.version }}.pkg
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Upload .zip to tag
        if: inputs.run_pkg
        uses: AButler/upload-release-assets@v3.0
        with:
          files: release/toucan-macos-${{ inputs.version }}.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Upload SHA256 to tag
        if: inputs.run_pkg
        uses: AButler/upload-release-assets@v3.0
        with:
          files: release/toucan-macos-${{ inputs.version }}.sha256
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}

      - name: Create .dmg file
        if: inputs.run_pkg && inputs.run_dmg
        run: ./scripts/packaging/dmg.sh ${{ inputs.version }}
        env:
          MAC_APP_IDENTITY: ${{ secrets.MAC_APP_IDENTITY }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}

      - name: Verify .dmg file structure and integrity
        if: inputs.run_pkg && inputs.run_dmg
        run: |
          DMG="release/toucan-macos-${{ inputs.version }}.dmg"
          echo "🧪 Verifying structure of $DMG"
          hdiutil verify "$DMG"
          echo "✅ Verified: $DMG is structurally valid"

      - name: Upload .dmg file to tag
        if: inputs.run_pkg && inputs.run_dmg
        uses: AButler/upload-release-assets@v3.0
        with:
          files: |
            release/toucan-macos-${{ inputs.version }}.dmg
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          release-tag: ${{ github.ref_name }}


================================================
FILE: .github/workflows/pr-for-formula.yml
================================================
name: Push Homebrew Formula

on:
  workflow_call:
    inputs:
      version:
        required: true
        type: string
      baseurl:
        required: true
        type: string
      homepage:
        required: true
        type: string
      formulafile:
        required: true
        type: string
      formulaclass:
        required: true
        type: string
      repository:
        required: true
        type: string
    secrets:
      HOMEBREW_TAP_PAT:
        required: true

jobs:
  push_formula:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Tap Repo
        uses: actions/checkout@v4
        with:
          repository: ${{ inputs.repository }} 
          token: ${{ secrets.HOMEBREW_TAP_PAT }}
          ref: main

      - name: Set Version Variables
        run: |
          echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV
          echo "BASE_URL=${{ inputs.baseurl }}" >> $GITHUB_ENV
          echo "HOMEPAGE=${{ inputs.homepage }}" >> $GITHUB_ENV

      - name: Download SHA256 files from release
        run: |
          curl -LO "$BASE_URL/toucan-linux-$VERSION.sha256"
          curl -LO "$BASE_URL/toucan-macos-$VERSION.sha256"

      - name: Read SHA256 values
        id: shas
        run: |
          macos_sha=$(awk '{print $1}' "toucan-macos-$VERSION.sha256")
          linux_sha=$(awk '{print $1}' "toucan-linux-$VERSION.sha256")
          echo "macos_sha=$macos_sha" >> $GITHUB_OUTPUT
          echo "linux_sha=$linux_sha" >> $GITHUB_OUTPUT

      - name: Write Formula File
        run: |
          mkdir -p Formula
          cat > Formula/${{ inputs.formulafile }} <<EOF
          class ${{ inputs.formulaclass }} < Formula
            desc "Toucan is a static site generator written in Swift."
            homepage "$HOMEPAGE"
            version "$VERSION"

            if OS.mac?
              url "$BASE_URL/toucan-macos-$VERSION.zip"
              sha256 "${{ steps.shas.outputs.macos_sha }}"
            elsif OS.linux?
              url "$BASE_URL/toucan-linux-$VERSION.zip"
              sha256 "${{ steps.shas.outputs.linux_sha }}"
            end

            def install
              bin.install "toucan"
              bin.install "toucan-init"
              bin.install "toucan-generate"
              bin.install "toucan-serve"
              bin.install "toucan-watch"
            end

            test do
              assert_match "Usage", shell_output("\#{bin}/toucan --help")
            end
          end
          EOF

      - name: Clean up SHA256 files
        run: rm -f *.sha256

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
        with:
          token: ${{ secrets.HOMEBREW_TAP_PAT }}
          commit-message: "Update formula to version ${{ env.VERSION }}"
          branch: feature/update-formula-${{ env.VERSION }}
          base: main
          title: "Update Homebrew formula to ${{ env.VERSION }}"
          body: |
            This PR updates the Homebrew formula to version `${{ env.VERSION }}`.

================================================
FILE: .github/workflows/tag_actions.yml
================================================
name: Dispatch macOS and Linux Builds on new tag

on:
  push:
    tags:
      - 'v*'
      - '[0-9]*'

permissions:
  contents: write

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.extract.outputs.version }}
      original_version: ${{ steps.extract.outputs.original_version }}
    steps:
      - name: Extract version from tag
        id: extract
        run: |
          RAW_VERSION="${GITHUB_REF#refs/tags/}"
          CLEAN_VERSION="${RAW_VERSION//-/.}"
          echo "Version: $CLEAN_VERSION"
          echo "VERSION=$CLEAN_VERSION" >> $GITHUB_ENV
          echo "version=$CLEAN_VERSION" >> $GITHUB_OUTPUT

  linux:
    needs: prepare
    uses: ./.github/workflows/linux.yml
    with:
      version: ${{ needs.prepare.outputs.version }}
      run_rpm: true
      run_deb: true
      static_stdlib: true
    secrets: inherit

  macos:
    needs: prepare
    uses: ./.github/workflows/macos.yml
    with:
      version: ${{ needs.prepare.outputs.version }}
      run_pkg: true
      run_dmg: false
    secrets: inherit

  pr_for_formula:
    name: Create a PR for Homebrew Formula
    needs: [prepare, linux, macos]
    uses: ./.github/workflows/pr-for-formula.yml
    with:
      version: ${{ needs.prepare.outputs.version }}
      baseurl: https://github.com/toucansites/toucan/releases/download/${{ github.ref_name }}
      homepage: https://github.com/toucansites/toucan
      formulafile: toucan.rb
      formulaclass: Toucan
      repository: toucansites/homebrew-toucan
    secrets:
      HOMEBREW_TAP_PAT: ${{ secrets.HOMEBREW_TAP_PAT }}

================================================
FILE: .gitignore
================================================
.DS_Store
.swiftpm
.build
.vscode
.obsidian
**/dist
**/docs
Tests/sites/benchmark/
Examples/try-o/


================================================
FILE: .swift-format
================================================
{
  "fileScopedDeclarationPrivacy" : {
    "accessLevel" : "private"
  },
  "indentation" : {
    "spaces" : 4
  },
  "multiElementCollectionTrailingCommas": true,
  "indentConditionalCompilationBlocks" : false,
  "indentSwitchCaseLabels" : false,
  "lineBreakAroundMultilineExpressionChainComponents" : true,
  "lineBreakBeforeControlFlowKeywords" : true,
  "lineBreakBeforeEachArgument" : true,
  "lineBreakBeforeEachGenericRequirement" : true,
  "lineLength" : 80,
  "maximumBlankLines" : 1,
  "prioritizeKeepingFunctionOutputTogether" : true,
  "respectsExistingLineBreaks" : true,
  "rules" : {
    "AllPublicDeclarationsHaveDocumentation" : true,
    "AlwaysUseLowerCamelCase" : false,
    "AmbiguousTrailingClosureOverload" : true,
    "BeginDocumentationCommentWithOneLineSummary" : false,
    "DoNotUseSemicolons" : true,
    "DontRepeatTypeInStaticProperties" : false,
    "FileScopedDeclarationPrivacy" : true,
    "FullyIndirectEnum" : true,
    "GroupNumericLiterals" : true,
    "IdentifiersMustBeASCII" : true,
    "NeverForceUnwrap" : false,
    "NeverUseForceTry" : false,
    "NeverUseImplicitlyUnwrappedOptionals" : false,
    "NoAccessLevelOnExtensionDeclaration" : false,
    "NoAssignmentInExpressions" : true,
    "NoBlockComments" : true,
    "NoCasesWithOnlyFallthrough" : true,
    "NoEmptyTrailingClosureParentheses" : true,
    "NoLabelsInCasePatterns" : false,
    "NoLeadingUnderscores" : false,
    "NoParensAroundConditions" : true,
    "NoVoidReturnOnFunctionSignature" : true,
    "OneCasePerLine" : true,
    "OneVariableDeclarationPerLine" : true,
    "OnlyOneTrailingClosureArgument" : true,
    "OrderedImports" : false,
    "ReturnVoidInsteadOfEmptyTuple" : true,
    "UseEarlyExits" : false,
    "UseLetInEveryBoundCaseVariable" : false,
    "UseShorthandTypeNames" : true,
    "UseSingleLinePropertyGetter" : false,
    "UseSynthesizedInitializer" : true,
    "UseTripleSlashForDocumentationComments" : true,
    "UseWhereClausesInForLoops" : false,
    "ValidateDocumentationComments" : true
  },
  "spacesAroundRangeFormationOperators" : false,
  "tabWidth" : 4,
  "version" : 1
}


================================================
FILE: .swiftformat
================================================
--swiftversion 6

--indent 4
    --indentstrings true
    --smarttabs true
    --xcodeindentation enabled

--maxwidth 80
--trimwhitespace always
--self init-only
--elseposition next-line
--guardelse next-line
--ranges nospace

--wraparguments before-first
--wrapparameters before-first
--wrapcollections before-first
--wrapconditions before-first


--enable trailingclosures
--enable todos
--enable preferKeyPath

--enable organizeDeclarations
    --organizationmode type
    --visibilityorder private, fileprivate, internal, package, public, open
    --categorymark "MARK: - %c"
    --markcategories false

--enable wrapAttributes
    --funcattributes prev-line
    --typeattributes prev-line
    --storedvarattrs prev-line
    --computedvarattrs prev-line
    --complexattrs prev-line

--enable trailingCommas
    --commas always

--disable wrapSingleLineComments

--enable yodaConditions

--enable acronyms
    --acronyms "ID,URL,UUID,HTTP,YML,YAML,JSON,XML"
    --preserveacronyms "rootUrl"


================================================
FILE: .swiftformatignore
================================================
Package.swift

================================================
FILE: .swiftheaderignore
================================================
.*
*.c
*.h
*.txt
*.html
*.yaml
README.md
Package.resolved
Makefile
LICENSE
Package.swift
Docker/**
scripts/**


================================================
FILE: Docker/Dockerfile
================================================
FROM swift:6.3-noble AS build

WORKDIR /build
COPY ./Package.* ./
RUN swift package resolve
COPY Sources ./Sources
COPY Tests ./Tests
COPY Package.swift .
COPY Package.resolved .
RUN swift build -c release --static-swift-stdlib

WORKDIR /staging
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/toucan" ./
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/toucan-generate" ./
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/toucan-init" ./
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/toucan-serve" ./
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/toucan-watch" ./
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

FROM ubuntu:noble

RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y tzdata locales curl unzip \
    && ln -fs /usr/share/zoneinfo/UTC /etc/localtime \
    && dpkg-reconfigure -f noninteractive tzdata \
    && locale-gen en_US.UTF-8 \
    && update-locale LANG=en_US.UTF-8 \
    && rm -r /var/lib/apt/lists/*

ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app toucan

WORKDIR /app
COPY --from=build --chown=toucan:toucan /staging /app

# ✅ Ensure all files in /app are executable by all users
RUN chmod -R a+rx /app

ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static
ENV PATH="/app:$PATH"

USER toucan:toucan

EXPOSE 3000

ENTRYPOINT ["/app/toucan"]
CMD ["--help"]


================================================
FILE: Docker/Dockerfile.testing
================================================
FROM swift:6.1

WORKDIR /app

COPY . ./

RUN swift package resolve
RUN swift package clean
RUN swift package update

CMD ["swift", "test", "--parallel", "--enable-code-coverage"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-2022 Tibor Bödecs
Copyright (c) 2022-2025 Binary Birds Ltd.

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.


================================================
FILE: Makefile
================================================
SHELL=/bin/bash

.PHONY: docker

baseUrl = https://raw.githubusercontent.com/BinaryBirds/github-workflows/refs/heads/main/scripts

check: symlinks language deps lint headers

symlinks:
	curl -s $(baseUrl)/check-broken-symlinks.sh | bash
	
language:
	curl -s $(baseUrl)/check-unacceptable-language.sh | bash
	
deps:
	curl -s $(baseUrl)/check-local-swift-dependencies.sh | bash
	
lint:
	curl -s $(baseUrl)/run-swift-format.sh | bash

fmt:
	swiftformat .

format:
	curl -s $(baseUrl)/run-swift-format.sh | bash -s -- --fix

headers:
	curl -s $(baseUrl)/check-swift-headers.sh | bash

fix-headers:
	curl -s $(baseUrl)/check-swift-headers.sh | bash -s -- --fix

build:
	swift build

release:
	swift build -c release
	
test:
	swift test --parallel

test-with-coverage:
	swift test --parallel --enable-code-coverage

clean:
	rm -rf .build

install:
	./scripts/install-toucan.sh

uninstall:
	./scripts/uninstall-toucan.sh

docker-image:
	docker buildx build \
		--platform linux/amd64,linux/arm64 \
		-t toucan \
		-f ./Docker/Dockerfile \
		--load \
		.

# docker run --rm -v $(pwd):/app/site toucan generate /app/site/src /app/site/dist
# docker run --rm -v $(pwd):/app/site toucan generate ./site/src ./site/dist --base-url "http://localhost:3000"
# docker run --rm -v $(pwd):/app/site --entrypoint /app/toucan toucan generate ./site/src ./site/dist --base-url "http://localhost:3000"
# docker run --rm -p 3000:3000 -v $(pwd):/app/site toucan serve --hostname "0.0.0.0" --port 3000 ./site/dist
# docker run --rm -v $(pwd):/app/site --entrypoint toucan toucansites/toucan generate /app/site/src /app/site/dist

docker-run:
	docker run --rm -v $(pwd):/app -it swift:6.1

docker-tests:
	docker build -t toucan-tests . -f ./Docker/Dockerfile.testing && docker run --rm toucan-tests

diff:
	diff --color=always -r dist-live dist --exclude=api || true


================================================
FILE: Package.resolved
================================================
{
  "originHash" : "b0aa8e8208e365a7988670c760c7c1a87737dac7471c3bcff7b3381dbaabf37b",
  "pins" : [
    {
      "identity" : "async-http-client",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/swift-server/async-http-client.git",
      "state" : {
        "revision" : "5dd84c7bb48b348751d7bbe7ba94a17bafdcef37",
        "version" : "1.30.2"
      }
    },
    {
      "identity" : "file-manager-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/binarybirds/file-manager-kit",
      "state" : {
        "revision" : "89a7485d5564aafd77d846fe4366f7e67d6b4b9b",
        "version" : "0.4.0"
      }
    },
    {
      "identity" : "filemonitor",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/toucansites/FileMonitor",
      "state" : {
        "revision" : "082b744b35a2f3d53e19bc1925d358590761551f",
        "version" : "0.1.0"
      }
    },
    {
      "identity" : "hummingbird",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/hummingbird-project/hummingbird",
      "state" : {
        "revision" : "3ae359b1bb1e72378ed43b59fdcd4d44cac5d7a4",
        "version" : "2.16.0"
      }
    },
    {
      "identity" : "semver.swift",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/johnfairh/Semver.swift.git",
      "state" : {
        "revision" : "0a6e2fe061ecb840c9bf80b6427e70ac039239fa",
        "version" : "1.2.4"
      }
    },
    {
      "identity" : "sourcemapper",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/johnfairh/SourceMapper.git",
      "state" : {
        "revision" : "2c86d8f44beb2c41effa2f9c5f6cf29b871bf3b9",
        "version" : "2.0.0"
      }
    },
    {
      "identity" : "swift-algorithms",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-algorithms",
      "state" : {
        "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
        "version" : "1.2.1"
      }
    },
    {
      "identity" : "swift-argument-parser",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-argument-parser",
      "state" : {
        "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615",
        "version" : "1.7.0"
      }
    },
    {
      "identity" : "swift-asn1",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-asn1.git",
      "state" : {
        "revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab",
        "version" : "1.7.0"
      }
    },
    {
      "identity" : "swift-async-algorithms",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-async-algorithms",
      "state" : {
        "revision" : "9d349bcc328ac3c31ce40e746b5882742a0d1272",
        "version" : "1.1.3"
      }
    },
    {
      "identity" : "swift-atomics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-atomics.git",
      "state" : {
        "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
        "version" : "1.3.0"
      }
    },
    {
      "identity" : "swift-certificates",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-certificates.git",
      "state" : {
        "revision" : "bde8ca32a096825dfce37467137c903418c1893d",
        "version" : "1.19.1"
      }
    },
    {
      "identity" : "swift-cmark",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/swiftlang/swift-cmark.git",
      "state" : {
        "revision" : "5d9bdaa4228b381639fff09403e39a04926e2dbe",
        "version" : "0.7.1"
      }
    },
    {
      "identity" : "swift-collections",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-collections.git",
      "state" : {
        "revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
        "version" : "1.4.1"
      }
    },
    {
      "identity" : "swift-crypto",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-crypto.git",
      "state" : {
        "revision" : "1b6b2e274e85105bfa155183145a1dcfd63331f1",
        "version" : "4.5.0"
      }
    },
    {
      "identity" : "swift-css-parser",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/stackotter/swift-css-parser",
      "state" : {
        "revision" : "6cf16c6696def00a313daef0e29eb27f5c39ece4",
        "version" : "0.1.2"
      }
    },
    {
      "identity" : "swift-distributed-tracing",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-distributed-tracing.git",
      "state" : {
        "revision" : "dc4030184203ffafbb2ec614352487235d747fe0",
        "version" : "1.4.1"
      }
    },
    {
      "identity" : "swift-http-structured-headers",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-http-structured-headers.git",
      "state" : {
        "revision" : "933538faa42c432d385f02e07df0ace7c5ecfc47",
        "version" : "1.7.0"
      }
    },
    {
      "identity" : "swift-http-types",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-http-types.git",
      "state" : {
        "revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
        "version" : "1.5.1"
      }
    },
    {
      "identity" : "swift-log",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-log",
      "state" : {
        "revision" : "2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181",
        "version" : "1.9.1"
      }
    },
    {
      "identity" : "swift-markdown",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-markdown",
      "state" : {
        "revision" : "7d9a5ce307528578dfa777d505496bd5f544ad94",
        "version" : "0.7.3"
      }
    },
    {
      "identity" : "swift-metrics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-metrics.git",
      "state" : {
        "revision" : "d51c8d13fa366eec807eedb4e37daa60ff5bfdd5",
        "version" : "2.10.1"
      }
    },
    {
      "identity" : "swift-mustache",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/hummingbird-project/swift-mustache",
      "state" : {
        "revision" : "2e2a84698dd8a5fff2fe28857f0f95bb03d21d64",
        "version" : "2.0.2"
      }
    },
    {
      "identity" : "swift-nio",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio.git",
      "state" : {
        "revision" : "bdf004b44f77c56fca752cd1cf243c802f8469c9",
        "version" : "2.97.0"
      }
    },
    {
      "identity" : "swift-nio-extras",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-extras.git",
      "state" : {
        "revision" : "5a48717e29f62cb8326d6d42e46b562ca93847a6",
        "version" : "1.34.0"
      }
    },
    {
      "identity" : "swift-nio-http2",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-http2.git",
      "state" : {
        "revision" : "81cc18264f92cd307ff98430f89372711d4f6fe9",
        "version" : "1.43.0"
      }
    },
    {
      "identity" : "swift-nio-ssl",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-ssl.git",
      "state" : {
        "revision" : "3f337058ccd7243c4cac7911477d8ad4c598d4da",
        "version" : "2.37.0"
      }
    },
    {
      "identity" : "swift-nio-transport-services",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-transport-services.git",
      "state" : {
        "revision" : "67787bb645a5e67d2edcdfbe48a216cc549222d5",
        "version" : "1.28.0"
      }
    },
    {
      "identity" : "swift-numerics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-numerics.git",
      "state" : {
        "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
        "version" : "1.1.1"
      }
    },
    {
      "identity" : "swift-protobuf",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-protobuf.git",
      "state" : {
        "revision" : "81558271e243f8f47dfe8e9fdd55f3c2b5413f68",
        "version" : "1.37.0"
      }
    },
    {
      "identity" : "swift-sass",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/johnfairh/swift-sass",
      "state" : {
        "revision" : "361b70bbb4f038cc850cb2d5e6ef5aa3495cb6ef",
        "version" : "3.3.0"
      }
    },
    {
      "identity" : "swift-service-context",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-service-context.git",
      "state" : {
        "revision" : "d0997351b0c7779017f88e7a93bc30a1878d7f29",
        "version" : "1.3.0"
      }
    },
    {
      "identity" : "swift-service-lifecycle",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/swift-server/swift-service-lifecycle.git",
      "state" : {
        "revision" : "9829955b385e5bb88128b73f1b8389e9b9c3191a",
        "version" : "2.11.0"
      }
    },
    {
      "identity" : "swift-system",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-system",
      "state" : {
        "revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
        "version" : "1.6.4"
      }
    },
    {
      "identity" : "swiftcommand",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/Zollerboy1/SwiftCommand",
      "state" : {
        "revision" : "28efb038351a8c45010772b407adb9ea02ba67b5",
        "version" : "1.4.2"
      }
    },
    {
      "identity" : "swiftsoup",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/scinfu/SwiftSoup",
      "state" : {
        "revision" : "6c7915e16f729857aec3e99068c361e58a00ed68",
        "version" : "2.13.4"
      }
    },
    {
      "identity" : "version",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/mxcl/Version",
      "state" : {
        "revision" : "3043fcd2a50375db76d89ff206a612471833d1c2",
        "version" : "2.2.1"
      }
    },
    {
      "identity" : "yams",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/jpsim/Yams",
      "state" : {
        "revision" : "3d6871d5b4a5cd519adf233fbb576e0a2af71c17",
        "version" : "5.4.0"
      }
    }
  ],
  "version" : 3
}


================================================
FILE: Package.swift
================================================
// swift-tools-version: 6.1
import PackageDescription

let swiftSettings: [SwiftSetting] = [
    .enableExperimentalFeature("StrictConcurrency=complete"),
]

/// Git commit hash information based on context.
var gitCommitHash: String {
    if let git = Context.gitInformation {
        let base = git.currentCommit
        return git.hasUncommittedChanges ? "\(base)-dev" : base
    }
    return "untracked"
}

let package = Package(
    name: "toucan",
    platforms: [
        .macOS(.v14),
        .iOS(.v17),
        .tvOS(.v17),
        .watchOS(.v10),
        .visionOS(.v1),
    ],
    products: [
        .executable(name: "toucan", targets: ["toucan"]),
        .executable(name: "toucan-init", targets: ["toucan-init"]),
        .executable(name: "toucan-generate", targets: ["toucan-generate"]),
        .executable(name: "toucan-watch", targets: ["toucan-watch"]),
        .executable(name: "toucan-serve", targets: ["toucan-serve"]),

        .library(name: "ToucanCore", targets: ["ToucanCore"]),
        .library(name: "ToucanSerialization", targets: ["ToucanSerialization"]),
        .library(name: "ToucanMarkdown", targets: ["ToucanMarkdown"]),
        .library(name: "ToucanSource", targets: ["ToucanSource"]),
        .library(name: "ToucanSDK", targets: ["ToucanSDK"]),
    ],
    dependencies: [
        .package(
            url: "https://github.com/apple/swift-argument-parser",
            "1.5.0"..<"1.7.1" // breaks dep graph for 6.0
        ),
        .package(
            url: "https://github.com/apple/swift-markdown",
            from: "0.6.0"
        ),
        .package(
            url: "https://github.com/apple/swift-log",
            "1.6.0"..<"1.10.0"
        ),
        .package(
            url: "https://github.com/apple/swift-nio",
            "2.0.0"..<"2.97.1" // breaks dep graph for 6.0
        ),
        .package(
            url: "https://github.com/binarybirds/file-manager-kit",
            exact: "0.4.0"
        ),
        .package(
            url: "https://github.com/swift-server/async-http-client",
            "1.0.0"..<"1.30.3" // swift configuration breaks on 6.3.1
        ),
        .package(
            url: "https://github.com/hummingbird-project/hummingbird",
            "2.0.0"..<"2.17.0" // swift configuration breaks on 6.3.1
        ),
        .package(
            url: "https://github.com/hummingbird-project/swift-mustache",
            from: "2.0.0"
        ),
        .package(
            url: "https://github.com/jpsim/Yams",
            from: "5.4.0"
        ),
        .package(
            url: "https://github.com/scinfu/SwiftSoup",
            from: "2.8.0"
        ),
        .package(
            url: "https://github.com/toucansites/FileMonitor",
            from: "0.1.0"
        ),
        .package(
            url: "https://github.com/Zollerboy1/SwiftCommand",
            from: "1.4.0"
        ),
        .package(
            url: "https://github.com/johnfairh/swift-sass",
            from: "3.1.0"
        ),
        .package(
            url: "https://github.com/stackotter/swift-css-parser",
            from: "0.1.2"
        ),
        .package(
            url: "https://github.com/mxcl/Version",
            from: "2.2.0"
        ),
    ],
    targets: [
        .target(
            name: "_GitCommitHash",
            cSettings: [
                .define("GIT_COMMIT_HASH", to: #""\#(gitCommitHash)""#)
            ]
        ),
        // MARK: - executable targets

        .executableTarget(
            name: "toucan",
            dependencies: [
                .product(
                    name: "ArgumentParser",
                    package: "swift-argument-parser"
                ),
                .product(name: "Logging", package: "swift-log"),
                .product(name: "SwiftCommand", package: "SwiftCommand"),
                .target(name: "ToucanCore"),
            ],
            swiftSettings: swiftSettings
        ),
        .executableTarget(
            name: "toucan-init",
            dependencies: [
                .product(
                    name: "ArgumentParser",
                    package: "swift-argument-parser"
                ),
                .product(name: "Logging", package: "swift-log"),
                .product(name: "FileManagerKit", package: "file-manager-kit"),
                .product(name: "SwiftCommand", package: "SwiftCommand"),
                .target(name: "ToucanCore"),
                .target(name: "ToucanSource")
            ],
            swiftSettings: swiftSettings
        ),
        .executableTarget(
            name: "toucan-generate",
            dependencies: [
                .product(
                    name: "ArgumentParser",
                    package: "swift-argument-parser"
                ),
                .product(name: "Logging", package: "swift-log"),
                .target(name: "ToucanSDK"),
            ],
            swiftSettings: swiftSettings
        ),
        .executableTarget(
            name: "toucan-watch",
            dependencies: [
                .product(
                    name: "ArgumentParser",
                    package: "swift-argument-parser"
                ),
                .product(name: "Logging", package: "swift-log"),
                .product(name: "FileMonitor", package: "FileMonitor"),
                .product(name: "SwiftCommand", package: "SwiftCommand"),
                .target(name: "ToucanCore"),
            ],
            swiftSettings: swiftSettings
        ),
        .executableTarget(
            name: "toucan-serve",
            dependencies: [
                .product(
                    name: "ArgumentParser",
                    package: "swift-argument-parser"
                ),
                .product(name: "Logging", package: "swift-log"),
                .product(name: "Hummingbird", package: "hummingbird"),
                .target(name: "ToucanCore"),
            ],
            swiftSettings: swiftSettings
        ),

        // MARK: - regular targets

        .target(
            name: "ToucanCore",
            dependencies: [
                .product(name: "Logging", package: "swift-log"),
                .product(name: "FileManagerKit", package: "file-manager-kit"),
                .product(name: "Version", package: "Version"),
                .target(name: "_GitCommitHash"),
            ],
            swiftSettings: swiftSettings
        ),
        .target(
            name: "ToucanSerialization",
            dependencies: [
                .product(name: "Yams", package: "yams"),
                .target(name: "ToucanCore"),
            ],
            swiftSettings: swiftSettings
        ),
        .target(
            name: "ToucanMarkdown",
            dependencies: [
                // for outline
                .product(name: "SwiftSoup", package: "SwiftSoup"),
                // for markdown to html
                .product(name: "Markdown", package: "swift-markdown"),
                // for transformers
                .product(name: "SwiftCommand", package: "SwiftCommand"),
                .target(name: "ToucanCore"),
            ],
            swiftSettings: swiftSettings
        ),
        .target(
            name: "ToucanSource",
            dependencies: [
                .target(name: "ToucanCore"),
                .target(name: "ToucanSerialization"),
            ],
            swiftSettings: swiftSettings
        ),
        .target(
            name: "ToucanSDK",
            dependencies: [
                .product(name: "Mustache", package: "swift-mustache"),
                .product(name: "DartSass", package: "swift-sass"),
                .product(name: "SwiftCSSParser", package: "swift-css-parser"),
                .target(name: "ToucanCore"),
                .target(name: "ToucanSerialization"),
                .target(name: "ToucanMarkdown"),
                .target(name: "ToucanSource"),
            ],
            swiftSettings: swiftSettings
        ),

        // MARK: - test targets

        .testTarget(
            name: "ToucanCoreTests",
            dependencies: [
                .target(name: "ToucanCore"),
            ]
        ),
        .testTarget(
            name: "ToucanMarkdownTests",
            dependencies: [
                .target(name: "ToucanMarkdown"),
            ]
        ),
        .testTarget(
            name: "ToucanSourceTests",
            dependencies: [
                .target(name: "ToucanCore"),
                .target(name: "ToucanSource"),
                .product(
                    name: "FileManagerKitBuilder",
                    package: "file-manager-kit"
                ),
            ]
        ),
        .testTarget(
            name: "ToucanSDKTests",
            dependencies: [
                .target(name: "ToucanSDK"),
                .product(
                    name: "FileManagerKitBuilder",
                    package: "file-manager-kit"
                ),
            ]
        ),
    ]
)


================================================
FILE: README.md
================================================
# Toucan

Toucan is a markdown-based Static Site Generator (SSG) written in Swift.

## Installation

## Compile from source

Make sure you have Swift 6+ installed. See [how to install swift](https://www.swift.org/install/) for instructions.

To build Toucan from source, run the following commands:

```shell
# clone the Toucan repository
git clone https://github.com/toucansites/toucan.git
cd toucan

# install Toucan on your system under /usr/local/bin
make install
# enter your password, if needed

# verify
which toucan
# should return /usr/local/bin/toucan

# uninstall, remove Toucan from your system
make uninstall
# enter your password, if needed
```

## Quickstart

To quickly bootstrap a Toucan-based static site, run the following commands:

```shell
toucan init my-site
cd my-site
toucan generate
toucan serve
# Visit: http://localhost:3000
```

## Documentation

The complete documentation for Toucan is available on [toucansites.com](https://toucansites.com/docs/).


================================================
FILE: Sources/ToucanCore/Extensions/Dictionary+Extensions.swift
================================================
//
//  Dictionary+Extensions.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 03..
//

public extension Dictionary {
    /// Transforms the keys of the dictionary using the given closure, preserving the associated values.
    ///
    /// This method applies the provided transformation to each key in the dictionary,
    /// resulting in a new dictionary with the transformed keys and original values.
    ///
    /// - Parameter t: A closure that takes a key as input and returns a transformed key.
    /// - Returns: A dictionary with transformed keys and the original values.
    func mapKeys<T>(
        _ t: (Key) throws -> T
    ) rethrows -> [T: Value] {
        try .init(
            uniqueKeysWithValues: map { try (t($0.key), $0.value) }
        )
    }
}

/// This extension allows recursive merging of dictionaries with String keys and Any values.
public extension Dictionary where Key == String {
    /// Recursively merges another `[String: Value]` dictionary into the current dictionary and returns a new dictionary.
    ///
    /// - Parameter other: The dictionary to merge into the current dictionary.
    /// - Returns: A new dictionary with the merged contents.
    func recursivelyMerged(
        with other: [String: Value]
    ) -> [String: Value] {
        var result = self
        for (key, value) in other {
            if let existingValue = result[key] as? [String: Value],
                let newValue = value as? [String: Value]
            {
                result[key] =
                    existingValue.recursivelyMerged(with: newValue) as? Value
            }
            else {
                result[key] = value
            }
        }
        return result
    }

    /// Retrieves a nested value from the receiver using a dot-separated key path.
    /// Supports traversal through dictionaries with `String` keys and arrays with numeric indices.
    ///
    /// - Parameter keyPath: A dot-separated string representing the path to the nested value.
    /// - Returns: The value at the specified key path, or `nil` if the path is invalid.
    func value(
        forKeyPath keyPath: String
    ) -> Any? {
        let keys = keyPath.split(separator: ".").map(String.init)
        var current: Any? = self

        for key in keys {
            if let dict = current as? [String: Any], let next = dict[key] {
                current = next
                continue
            }

            if let array = current as? [Any], let index = Int(key),
                array.indices.contains(index)
            {
                current = array[index]
                continue
            }

            return nil
        }

        return current
    }
}


================================================
FILE: Sources/ToucanCore/Extensions/Logging+Extensions.swift
================================================
//
//  Logging+Extensions.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 18..
//

import class Foundation.ProcessInfo
import Logging

public extension Logger {

    /// Returns a logger instance for the specified subsystem.
    ///
    /// Constructs a logger with a label based on the subsystem identifier and sets its log level.
    /// The log level is determined by checking environment variables
    /// for subsystem-specific or global log level settings. If none are found, the provided default level is used.
    ///
    /// - Parameters:
    ///   - id: The subsystem identifier (e.g., `"generate"`, `"object-loader"`).
    ///   - level: The default log level to use if not specified elsewhere. Defaults to `.info`.
    /// - Returns: A configured `Logger` instance for the subsystem.
    static func subsystem(
        _ id: String = "",
        _ level: Logger.Level = .info
    ) -> Logger {
        var logger = Logger(label: id.loggerLabel())
        logger.logLevel = findEnvLogLevel(id) ?? level

        return logger
    }
}

private extension Logger {

    /// Returns the log level from environment variables for the given subsystem identifier.
    ///
    /// Checks for a subsystem-specific log level key and a global log level key (`TOUCAN_LOG_LEVEL`)
    /// in the environment. If a valid log level string is found, it is converted to a `Logger.Level`.
    ///
    /// - Parameter id: The subsystem identifier.
    /// - Returns: The log level if found and valid, otherwise `nil`.
    static func findEnvLogLevel(_ id: String) -> Logger.Level? {
        let env = ProcessInfo.processInfo.environment
        let keys = [
            id.subsystemLogLevelKey(),
            "TOUCAN_LOG_LEVEL",
        ]

        for key in keys {
            if let rawLevel = env[key]?.lowercased(),
                let level = Logger.Level(rawValue: rawLevel)
            {
                return level
            }
        }

        return nil
    }
}

private extension String {

    /// Returns the logger label for a subsystem.
    ///
    /// Constructs a logger label by joining "TOUCAN" and the subsystem identifier with a hyphen.
    /// If the identifier is empty, returns "toucan".
    ///
    /// - Examples:
    ///   - For an empty string: `"toucan"`
    ///   - For `"generate"`: `"toucan-generate"`
    ///   - For `"object-loader"`: `"toucan-object-loader"`
    func loggerLabel() -> String {
        let prefix = "toucan"
        let parts = isEmpty ? [prefix] : [prefix, self]
        return parts.joined(separator: "-")
    }

    /// Returns the environment variable key for the log level of a subsystem.
    ///
    /// This method constructs a log level key by converting the logger label to uppercase,
    /// replacing hyphens with underscores, and appending "_LOG_LEVEL".
    ///
    /// - Examples:
    ///   - For an empty string: `"TOUCAN_LOG_LEVEL"`
    ///   - For `"generate"`: `"TOUCAN_GENERATE_LOG_LEVEL"`
    ///   - For `"object-loader"`: `"TOUCAN_OBJECT_LOADER_LOG_LEVEL"`
    func subsystemLogLevelKey() -> String {
        loggerLabel()
            .uppercased()
            .replacing("-", with: "_")
            .appending("_LOG_LEVEL")
    }
}


================================================
FILE: Sources/ToucanCore/Extensions/String+Extensions.swift
================================================
//
//  String+Extensions.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 17..
//

import Foundation

public extension String {
    /// A convenience property that converts an empty string to `nil`.
    ///
    /// This is useful for cases where an empty string should be treated as the absence of a value,
    /// such as when preparing optional fields for encoding, form validation, or API payloads.
    ///
    /// For example:
    /// ```swift
    /// let name: String = ""
    /// let optionalName = name.emptyToNil // Result: nil
    /// ```
    ///
    /// - Returns: `nil` if the string is empty; otherwise, returns the original string.
    var emptyToNil: String? {
        isEmpty ? nil : self
    }

    /// Removes the leading slash from the string if present.
    ///
    /// This method checks if the string starts with a slash (`/`). If so, it removes it.
    ///
    /// - Returns: A new string without a leading slash, or the original string if no leading slash exists.
    func dropLeadingSlash() -> String {
        if hasPrefix("/") {
            return String(dropFirst())
        }
        return self
    }

    /// Removes the trailing slash from the string if present.
    ///
    /// This method checks if the string ends with a slash (`/`). If so, it removes it.
    ///
    /// - Returns: A new string without a trailing slash, or the original string if no trailing slash exists.
    func dropTrailingSlash() -> String {
        if hasSuffix("/") {
            return String(dropLast())
        }
        return self
    }

    /// Ensures the string starts with a leading slash.
    ///
    /// This method checks if the string already begins with a slash (`/`). If it does, the original string is returned.
    /// Otherwise, it prepends a slash to the beginning of the string.
    ///
    /// - Returns: A new string with a leading slash ensured.
    func ensureLeadingSlash() -> String {
        if hasPrefix("/") {
            return self
        }
        return "/" + self
    }

    /// Appends a trailing slash to the string if not already present.
    ///
    /// This method checks if the string ends with a slash (`/`). If not, it appends one.
    ///
    /// - Returns: A new string with a trailing slash ensured.
    func ensureTrailingSlash() -> String {
        if hasSuffix("/") {
            return self
        }
        return self + "/"
    }

    /// Replaces substrings in the string using a given dictionary of replacements.
    ///
    /// This method iterates over the key-value pairs in the provided dictionary
    /// and replaces all occurrences of each key with its corresponding value.
    ///
    /// - Parameter dictionary: A dictionary where each key is a substring to search for,
    ///   and the corresponding value is the string to replace it with.
    /// - Returns: A new string with all specified substrings replaced.
    func replacing(
        _ dictionary: [String: String]
    ) -> String {
        var result = self
        for (key, value) in dictionary {
            result = result.replacing(key, with: value)
        }
        return result
    }

    /// Converts the string into a URL-friendly slug.
    ///
    /// This method removes diacritics, trims whitespace, lowercases the string,
    /// and keeps only alphanumeric characters, dashes, underscores, and periods.
    /// Invalid characters are removed, and remaining components are joined with hyphens.
    ///
    /// - Returns: A slugified version of the original string.
    func slugify() -> String {
        let allowed = CharacterSet(
            charactersIn: "abcdefghijklmnopqrstuvwxyz0123456789-_."
        )
        return trimmingCharacters(in: .whitespacesAndNewlines)
            .lowercased()
            .folding(
                options: .diacriticInsensitive,
                locale: .init(identifier: "en-US")
            )
            .components(separatedBy: allowed.inverted)
            .filter { $0 != "" }
            .joined(separator: "-")
    }

    /// Resolves a relative asset path by combining it with a base URL, assets path, and slug.
    ///
    /// This method builds a complete asset URL by handling various cases:
    /// - If the base URL or assets path is empty, it returns the original string.
    /// - If the string starts with `/`, it appends the string directly to the base URL.
    /// - If the string starts with a relative prefix (e.g., `./assetsPath/`), it removes the prefix
    ///   and combines the base URL, assets path, slug, and remaining path parts into a full URL.
    ///
    /// - Parameters:
    ///   - baseURL: The base URL used to form the full path.
    ///   - assetsPath: The relative directory for the assets.
    ///   - slug: A string inserted in the final path for identification or grouping.
    /// - Returns: A full string URL combining all parts, or the original string if no resolution is applied.
    func resolveAsset(
        baseURL: String,
        assetsPath: String,
        slug: String
    ) -> String {
        if baseURL.isEmpty || assetsPath.isEmpty {
            return self
        }

        let baseURL = baseURL.dropTrailingSlash()
        if hasPrefix("/") {
            return [baseURL, dropLeadingSlash()].joined(separator: "/")
        }

        let prefix = "./\(assetsPath)/"
        guard hasPrefix(prefix) else {
            return self
        }

        let src = String(dropFirst(prefix.count))

        return [baseURL, assetsPath, slug, src]
            .filter { !$0.isEmpty }
            .joined(separator: "/")
    }

    /// Checks if a string contains only valid URL characters.
    ///
    /// Allowed: unreserved (`A–Z a–z 0–9 - . _ ~`), reserved
    /// (`:/?#[]@!$&'()*+,;=`), and `%` for encoding.
    ///
    /// - Returns: `true` if all characters are valid, otherwise `false`.
    func containsOnlyValidURLCharacters() -> Bool {
        let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        let numerics = "0123456789"
        let special = "-._~{}%"
        let reserved = ":/?#[]@!$&'()*+,;="

        let allowed = CharacterSet(
            charactersIn: alphabet + numerics + special + reserved
        )

        return unicodeScalars.allSatisfy { allowed.contains($0) }
    }

    /// Checks if a string contains only valid URL characters.
    ///
    /// Allowed: unreserved (`A–Z a–z 0–9 - . _ ~`), reserved
    /// (`:/?#[]@!$&'()*+,;=`), and `%` for encoding.
    ///
    /// - Returns: `true` if all characters are valid, otherwise `false`.
    func containsOnlyValidPathCharacters() -> Bool {
        let disallowed = CharacterSet(
            charactersIn: "%?#&="
        )
        return unicodeScalars.allSatisfy { !disallowed.contains($0) }
    }

}


================================================
FILE: Sources/ToucanCore/Extensions/URL+Extensions.swift
================================================
//
//  URL+Extensions.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 17..
//

import Foundation

public extension URL {
    /// Returns a new URL by appending the given path component if it is non-nil and not empty.
    ///
    /// This method is useful when working with optional path components where you want to
    /// conditionally append the value only if it's meaningful (i.e., not `nil` or an empty string).
    ///
    /// - Parameter path: An optional string representing the path component to append.
    /// - Returns: A new `URL` with the appended path component if valid; otherwise, the original URL.
    ///
    /// ## Example
    /// ```swift
    /// let baseURL = URL(string: "https://example.com/api")!
    /// let endpoint: String? = "users"
    /// let fullURL = baseURL.appendingPathIfPresent(endpoint)
    /// // fullURL: https://example.com/api/users
    /// ```
    func appendingPathIfPresent(_ path: String?) -> URL {
        guard let path, !path.isEmpty else {
            return self
        }
        return appending(path: path)
    }
}


================================================
FILE: Sources/ToucanCore/GeneratorInfo.swift
================================================
//
//  GeneratorInfo.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2025. 03. 19..
//

import _GitCommitHash
import Version

public extension GeneratorInfo {

    /// Returns the most current version of the generator.
    static var current: Self {
        .v1_0_0
    }
}

/// List available releases here
extension GeneratorInfo {
    static let v1_0_0 = GeneratorInfo(version: "1.0.0")
    static let v1_0_0_rc_1 = GeneratorInfo(version: "1.0.0-rc.1")
    static let v1_0_0_beta_6 = GeneratorInfo(version: "1.0.0-beta.6")
    static let v1_0_0_beta_5 = GeneratorInfo(version: "1.0.0-beta.5")
    static let v1_0_0_beta_4 = GeneratorInfo(version: "1.0.0-beta.4")
    static let v1_0_0_beta_3 = GeneratorInfo(version: "1.0.0-beta.3")
    static let v1_0_0_beta_2 = GeneratorInfo(version: "1.0.0-beta.2")
    static let v1_0_0_beta_1 = GeneratorInfo(version: "1.0.0-beta.1")
}

/// Metadata describing the content generator, including its name, version, and homepage link.
public struct GeneratorInfo: Codable, Sendable {

    /// The name of the generator.
    public let name: String

    /// The version (e.g., `"1.0.0"`, `"1.0.0-beta.4"`).
    public let release: Version

    /// The git commit hash based on the SPM context.
    public var gitCommitHash: String {
        .init(cString: git_commit_hash())
    }

    /// The complete version information based on the version and the git commit hash
    public var version: String {
        "\(release.description) (\(gitCommitHash))"
    }

    /// A URL pointing to the generator’s homepage or documentation.
    public let link: String

    /// Initializes a generator metadata instance.
    ///
    /// - Parameters:
    ///   - name: The name of the generator (defaults to `"Toucan"`).
    ///   - version: The generator version string.
    ///   - link: A link to the project or documentation (defaults to GitHub).
    init(
        name: String = "Toucan",
        version: String,
        link: String = "https://github.com/toucansites/toucan"
    ) {
        self.name = name
        self.release = Version(version)!
        self.link = link
    }
}


================================================
FILE: Sources/ToucanCore/Logger.swift
================================================
//
//  Logger.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 18..
//

import Logging

/// A protocol for types that can provide structured metadata for logging.
///
/// Conforming types expose a dictionary of metadata values used to enrich log messages.
public protocol LoggerMetadataRepresentable {
    /// A dictionary of key-value pairs representing structured logging metadata.
    ///
    /// This metadata can be used to provide additional context in log output.
    var logMetadata: [String: Logger.MetadataValue] { get }
}


================================================
FILE: Sources/ToucanCore/ToucanError.swift
================================================
//
//  ToucanError.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 20..
//

import Foundation

/// A protocol for custom errors used in the Toucan framework.
///
/// Provides properties for logging, user-facing messages, and a method
/// to format nested error messages in a readable stack-like format.
public protocol ToucanError: Error {
    /// A developer-facing error description used for logging purposes.
    var logMessage: String { get }
    /// A simplified error message suitable for display to end users.
    var userFriendlyMessage: String { get }
    /// A list of underlying errors, useful for representing error hierarchies.
    var underlyingErrors: [Error] { get }
    /// Generates a readable stack-like message of the error and any underlying errors.
    ///
    /// - Returns: A formatted string detailing the error structure.
    func logMessageStack() -> String

    /// Searches for an error of a specific type in the error hierarchy.
    ///
    /// This method traverses the list of underlying errors and attempts to cast
    /// each one to the specified error type `T`. If a match is found, it is returned.
    /// The search is recursive and will descend into nested `ToucanError`s.
    ///
    /// - Parameter errorType: The type of error to search for.
    /// - Returns: An instance of the specified error type if found, otherwise `nil`.
    func lookup<T: Error>(
        _ errorType: T.Type
    ) -> T?

    /// Searches for a specific associated value in the error hierarchy using a custom matcher.
    ///
    /// This method first attempts to locate an error of type `T`, and if successful,
    /// applies the provided matcher closure to extract an associated value.
    ///
    /// - Parameter t: A closure that takes an error of type `T` and returns an associated value of type `V?`.
    /// - Returns: The extracted associated value if found, otherwise `nil`.
    func lookup<T: Error, V>(
        _ t: (T) -> V?
    ) -> V?
}

public extension ToucanError {
    /// Searches for an error of a specific type in the error hierarchy.
    ///
    /// This method traverses the list of underlying errors and attempts to cast
    /// each one to the specified error type `T`. If a match is found, it is returned.
    /// The search is recursive and will descend into nested `ToucanError`s.
    ///
    /// - Parameter errorType: The type of error to search for.
    /// - Returns: An instance of the specified error type if found, otherwise `nil`.
    func lookup<T: Error>(
        _ errorType: T.Type
    ) -> T? {
        for error in underlyingErrors {
            if let match = error as? T {
                return match
            }
            if let match = (error as ToucanError).lookup(errorType) {
                return match
            }
        }
        return nil
    }

    /// Searches for a specific associated value in the error hierarchy using a custom matcher.
    ///
    /// This method first attempts to locate an error of type `T`, and if successful,
    /// applies the provided matcher closure to extract an associated value.
    ///
    /// - Parameter t: A closure that takes an error of type `T` and returns an associated value of type `V?`.
    /// - Returns: The extracted associated value if found, otherwise `nil`.
    func lookup<T: Error, V>(
        _ t: (T) -> V?
    ) -> V? {
        lookup(T.self).flatMap(t)
    }
}

/// Conforms `NSError` to the `ToucanError` protocol, providing
/// default implementations for logging and user-friendly messages.
extension NSError: ToucanError {
    /// A detailed log message composed of the domain, code, and localized description.
    public var logMessage: String {
        "\(domain):\(code) - \(localizedDescription)"
    }

    /// A user-facing message derived from the localized description.
    public var userFriendlyMessage: String {
        "\(localizedDescription)"
    }
}

/// Provides default implementations for `ToucanError` protocol
/// including empty `underlyingErrors` and a recursive `logMessageStack`.
public extension ToucanError {
    /// A default empty list of underlying errors. Can be overridden by conforming types to provide error hierarchies.
    var underlyingErrors: [Error] { [] }

    /// Recursively builds a string that describes the error and its underlying errors in a readable format.
    ///
    /// - Returns: A formatted stack-like string representing the error and any nested underlying errors.
    func logMessageStack() -> String {
        format(error: self)
    }

    /// Recursively formats an error and its underlying errors into a structured log message.
    ///
    /// - Parameters:
    ///   - error: The error to format.
    ///   - prefix: The current indentation prefix.
    ///   - isLast: Indicates whether the error is the last in its group.
    /// - Returns: A formatted string representing the error hierarchy.
    private func format(
        error: Error,
        prefix: String = "",
        isLast: Bool = true
    ) -> String {
        let type = type(of: error)

        var message: String
        var underlyingErrors: [Error]
        switch error {
        case let e as ToucanError:
            message = e.logMessage
            underlyingErrors = e.underlyingErrors
        case let e as LocalizedError:
            message = e.localizedDescription
            underlyingErrors = []
        default:
            message = "\(error)"
            underlyingErrors = []
        }

        let branch = prefix.isEmpty ? "" : (isLast ? "└─ " : "├─ ")
        var output = "\(prefix)\(branch)\(type): \"\(message)\"\n"
        let childPrefix = prefix + (isLast ? "    " : "│   ")

        let childCount = underlyingErrors.count
        for (idx, error) in underlyingErrors.enumerated() {
            let lastChild = (idx == childCount - 1)
            output += format(
                error: error,
                prefix: childPrefix,
                isLast: lastChild
            )
        }

        return output
    }
}


================================================
FILE: Sources/ToucanMarkdown/Markdown/HTML.swift
================================================
//
//  HTML.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 19..
//

struct HTML {
    enum TagType {
        case standard
        case short
    }

    struct Attribute {
        var key: String
        var value: String
    }

    var name: String
    var type: TagType
    var attributes: [Attribute]
    var contents: String?

    init(
        name: String,
        type: TagType = .standard,
        attributes: [Attribute] = [],
        contents: String? = nil
    ) {
        self.name = name
        self.type = type
        self.attributes = attributes
        self.contents = contents
    }

    func render() -> String {
        let attributeString =
            attributes
            .map { #"\#($0.key)="\#($0.value)""# }
            .joined(separator: " ")

        let tag = [name, attributeString]
            .filter { !$0.isEmpty }
            .joined(separator: " ")

        var result = "<\(tag)>"
        result += contents ?? ""
        if type == .standard {
            result += "</\(name)>"
        }
        return result
    }
}


================================================
FILE: Sources/ToucanMarkdown/Markdown/HTMLVisitor.swift
================================================
//
//  HTMLVisitor.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 19..
//

import Logging
import Markdown
import ToucanCore

/// NOTE: https://www.markdownguide.org/basic-syntax/

private extension String {

    func escapeAngleBrackets() -> String {
        replacing(
            [
                #"<"#: #"&lt;"#,
                #">"#: #"&gt;"#,
                    // #"&"#: #"&amp;"#,
                    // #"'"#: #"&apos;"#,
                    // #"""#: #"&quot;"#,
            ]
        )
    }
}

private extension Markup {
    var isInsideList: Bool {
        self is ListItemContainer || parent?.isInsideList == true
    }
}

private extension [DirectiveArgument] {
    func getFirstValueBy(key name: String) -> String? {
        first(where: { $0.name == name })?.value
    }
}

struct HTMLVisitor: MarkupVisitor {
    typealias Result = String

    var customBlockDirectives: [MarkdownBlockDirective]
    var paragraphStyles: [String: [String]]
    var logger: Logger
    var slug: String
    var assetsPath: String
    var baseURL: String

    init(
        blockDirectives: [MarkdownBlockDirective] = [],
        paragraphStyles: [String: [String]],
        slug: String,
        assetsPath: String,
        baseURL: String,
        logger: Logger = .subsystem("html-visitor")
    ) {
        self.customBlockDirectives = blockDirectives
        self.paragraphStyles = paragraphStyles
        self.slug = slug
        self.assetsPath = assetsPath
        self.baseURL = baseURL
        self.logger = logger
    }

    // MARK: - visitor functions

    private mutating func visit(
        _ children: MarkupChildren
    ) -> Result {
        var result = ""
        for child in children {
            result += visit(child)
        }
        return result
    }

    mutating func defaultVisit(
        _ markup: any Markup
    ) -> Result {
        visit(markup.children)
    }

    mutating func visitText(
        _ text: Text
    ) -> Result {
        text.plainText
    }

    mutating func visitHTMLBlock(
        _ html: HTMLBlock
    ) -> Result {
        html.rawHTML  //.escapeAngleBrackets()
    }

    mutating func visitInlineHTML(
        _ inlineHTML: InlineHTML
    ) -> Result {
        inlineHTML.rawHTML.escapeAngleBrackets()
    }

    // MARK: - simple HTML elements

    mutating func visitSoftBreak(
        _: SoftBreak
    ) -> Result {
        HTML(name: "br", type: .short).render()
    }

    mutating func visitLineBreak(
        _: LineBreak
    ) -> Result {
        HTML(name: "br", type: .short).render()
    }

    mutating func visitThematicBreak(
        _: ThematicBreak
    ) -> Result {
        HTML(name: "hr", type: .short).render()
    }

    mutating func visitListItem(
        _ listItem: ListItem
    ) -> Result {
        HTML(name: "li", contents: visit(listItem.children)).render()
    }

    mutating func visitOrderedList(
        _ orderedList: OrderedList
    ) -> Result {
        var attributes: [HTML.Attribute] = []
        if orderedList.startIndex > 1 {
            attributes.append(
                .init(
                    key: "start",
                    value: String(
                        orderedList.startIndex
                    )
                )
            )
        }
        return HTML(
            name: "ol",
            attributes: attributes,
            contents: visit(orderedList.children)
        )
        .render()
    }

    mutating func visitUnorderedList(
        _ unorderedList: UnorderedList
    ) -> Result {
        HTML(name: "ul", contents: visit(unorderedList.children)).render()
    }

    mutating func visitInlineCode(
        _ inlineCode: InlineCode
    ) -> Result {
        HTML(
            name: "code",
            contents: inlineCode.code.escapeAngleBrackets()
        )
        .render()
    }

    mutating func visitEmphasis(
        _ emphasis: Emphasis
    ) -> Result {
        HTML(name: "em", contents: visit(emphasis.children)).render()
    }

    mutating func visitStrong(
        _ strong: Strong
    ) -> Result {
        HTML(name: "strong", contents: visit(strong.children)).render()
    }

    mutating func visitStrikethrough(
        _ strikethrough: Strikethrough
    ) -> Result {
        HTML(name: "s", contents: visit(strikethrough.children)).render()
    }

    mutating func visitParagraph(
        _ paragraph: Paragraph
    ) -> Result {
        let filterBlocks =
            customBlockDirectives
            .filter { $0.removesChildParagraph ?? false }
            .map(\.name)

        if let block = paragraph.parent as? BlockDirective,
            filterBlocks.contains(block.name.lowercased())
        {
            return visit(paragraph.children)
        }
        /// if the parent is a list element, we don't need to render the p tag
        if paragraph.isInsideList {
            return visit(paragraph.children)
        }
        return HTML(name: "p", contents: visit(paragraph.children)).render()
    }

    mutating func visitBlockQuote(
        _ blockQuote: BlockQuote
    ) -> Result {
        var paragraphCount = 0
        var otherCount = 0

        var type: String?
        var dropCount = 0

        for i in blockQuote.children {
            if let p = i as? Paragraph {
                paragraphCount += 1
                let text = p.plainText.lowercased()

                typeLoop: for (typeValue, prefixes) in paragraphStyles {
                    for prefix in prefixes {
                        let fullPrefix = "\(prefix): ".lowercased()
                        if text.hasPrefix(fullPrefix) {
                            type = typeValue
                            dropCount = fullPrefix.count
                            break typeLoop
                        }
                    }
                }
            }
            else {
                otherCount += 1
            }
        }
        guard let type, otherCount == 0, paragraphCount == 1 else {
            return HTML(
                name: "blockquote",
                contents: visit(blockQuote.children)
            )
            .render()
        }
        let paragraph = visit(blockQuote.children)
        let pTagCount = 3
        let contents =
            paragraph.prefix(pTagCount)
            + paragraph.dropFirst(pTagCount).dropFirst(dropCount)
        return HTML(
            name: "blockquote",
            attributes: [
                .init(key: "class", value: type)
            ],
            contents: String(contents)
        )
        .render()
    }

    mutating func visitCodeBlock(
        _ codeBlock: CodeBlock
    ) -> Result {

        var attributes: [HTML.Attribute] = []
        if let language = codeBlock.language {
            attributes.append(
                .init(
                    key: "class",
                    value: "language-\(language.lowercased())"
                )
            )
        }
        let code = HTML(
            name: "code",
            attributes: attributes,
            contents: codeBlock.code
                .escapeAngleBrackets()
                .replacing(
                    [
                        #"/*!*/"#: #"<span class="highlight">"#,
                        #"/*.*/"#: "</span>",
                    ]
                )
        )
        .render()

        return HTML(name: "pre", contents: code).render()
    }

    mutating func visitHeading(
        _ heading: Heading
    ) -> Result {
        var attributes: [HTML.Attribute] = []
        if [2, 3].contains(heading.level) {
            let fragment = heading.plainText.lowercased().slugify()
            let id = HTML.Attribute(key: "id", value: "\(fragment)")
            attributes.append(id)
        }
        return HTML(
            name: "h\(heading.level)",
            attributes: attributes,
            contents: visit(heading.children)
        )
        .render()
    }

    mutating func visitLink(
        _ link: Link
    ) -> Result {
        var attributes: [HTML.Attribute] = []

        if let destination = link.destination {
            let anchorPrefix = "#[name]"
            if destination.hasPrefix(anchorPrefix) {
                attributes.append(
                    .init(
                        key: "name",
                        value: String(destination.dropFirst(anchorPrefix.count))
                    )
                )
            }
            else {
                var hrefDestination = destination
                if destination.hasPrefix("/") {
                    hrefDestination =
                        "\(baseURL.ensureTrailingSlash())\(destination.dropFirst())"
                }
                attributes.append(
                    .init(
                        key: "href",
                        value: hrefDestination
                    )
                )
            }

            if !destination.hasPrefix("."),
                !destination.hasPrefix("/"),
                !destination.hasPrefix("#")
            {
                attributes.append(
                    .init(
                        key: "target",
                        value: "_blank"
                    )
                )
            }
        }

        return HTML(
            name: "a",
            attributes: attributes,
            contents: visit(link.children)
        )
        .render()
    }

    mutating func visitImage(_ image: Image) -> Result {
        guard let source = image.source, !source.isEmpty else {
            return ""
        }
        let imagePath = source.resolveAsset(
            baseURL: baseURL,
            assetsPath: assetsPath,
            slug: slug
        )
        var attributes: [HTML.Attribute] = [
            .init(key: "src", value: imagePath),
            .init(key: "alt", value: image.plainText),
        ]
        if let title = image.title {
            attributes.append(
                .init(key: "title", value: title)
            )
        }
        return HTML(
            name: "img",
            type: .short,
            attributes: attributes
        )
        .render()
    }

    // MARK: - table

    mutating func visitTable(
        _ table: Table
    ) -> Result {
        HTML(name: "table", contents: visit(table.children)).render()
    }

    mutating func visitTableHead(
        _ tableHead: Table.Head
    ) -> Result {
        HTML(name: "thead", contents: visit(tableHead.children)).render()
    }

    mutating func visitTableBody(
        _ tableBody: Table.Body
    ) -> Result {
        HTML(name: "tbody", contents: visit(tableBody.children)).render()
    }

    mutating func visitTableRow(
        _ tableRow: Table.Row
    ) -> Result {
        HTML(name: "tr", contents: visit(tableRow.children)).render()
    }

    mutating func visitTableCell(
        _ tableCell: Table.Cell
    ) -> Result {
        HTML(name: "td", contents: visit(tableCell.children)).render()
    }

    // MARK: - custom block directives

    mutating func visitBlockDirective(
        _ blockDirective: BlockDirective
    ) -> Result {

        var parseErrors = [DirectiveArgumentText.ParseError]()
        var arguments: [DirectiveArgument] = []
        let blockName = blockDirective.name.lowercased()
        if !blockDirective.argumentText.isEmpty {
            arguments = blockDirective.argumentText.parseNameValueArguments(
                parseErrors: &parseErrors
            )
        }

        let block = customBlockDirectives.first {
            $0.name.lowercased() == blockName.lowercased()
        }
        guard let block else {
            logger.warning(
                "Unrecognized block directive: `\(blockName)`",
                metadata: [
                    "name": .string(blockName)
                ]
            )
            return ""
        }

        guard parseErrors.isEmpty else {
            let errors =
                parseErrors.map { error -> String in
                    switch error {
                    case let .duplicateArgument(name, _, _):
                        return "Duplicate argument: `\(name)`."
                    case let .missingExpectedCharacter(char, _):
                        return "Misisng expected character: `\(char)`."
                    case let .unexpectedCharacter(char, _):
                        return "Unexpected character: `\(char)`."
                    }
                }
                .joined(separator: ", ")

            logger.warning(
                "\(errors)",
                metadata: [
                    "name": .string(blockName)
                ]
            )
            return ""
        }

        var parameters: [String: String] = [:]
        for p in block.parameters ?? [] {
            if p.required ?? false {
                if let v = arguments.getFirstValueBy(key: p.label) {
                    parameters[p.label] = v
                }
                else {
                    logger.warning(
                        "Parameter `\(p.label)` for `\(block.name)` is required.",
                        metadata: [
                            "name": .string(blockName)
                        ]
                    )
                }
            }
            else {
                let v =
                    arguments.getFirstValueBy(key: p.label) ?? p.default ?? ""

                parameters[p.label] = v
            }
        }

        let templateParams = parameters.mapKeys { "{{\($0)}}" }

        if let parent = block.requiresParentDirective, !parent.isEmpty {
            guard
                let p = blockDirective.parent as? BlockDirective,
                p.name.lowercased() == parent.lowercased()
            else {
                logger.warning(
                    "Block directive `\(block.name)` requires parent block `\(parent)`",
                    metadata: [
                        "name": .string(blockName)
                    ]
                )
                return ""
            }
        }

        if let output = block.output {
            var contents = ""
            for child in blockDirective.children {
                contents += visit(child)
            }

            var params = templateParams
            params["{{contents}}"] = contents

            return output.replacing(params)
        }

        if let name = block.tag {
            let attributes: [HTML.Attribute] =
                block.attributes?
                .map { a in
                    .init(
                        key: a.name,
                        value: a.value.replacing(templateParams)
                    )
                } ?? []

            return HTML(
                name: name,
                attributes: attributes,
                contents: visit(blockDirective.children)
            )
            .render()
        }
        return ""
    }
}


================================================
FILE: Sources/ToucanMarkdown/Markdown/MarkdownBlockDirective.swift
================================================
//
//  MarkdownBlockDirective.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 19..
//

/// A representation of a custom block directive in Markdown, used for extending Markdown syntax with special tags or behaviors.
public struct MarkdownBlockDirective: Codable, Equatable {
    /// Defines a configurable parameter for a directive, which may be required and have a default value.
    public struct Parameter: Codable, Equatable {
        /// The label of the parameter.
        public var label: String

        /// Indicates whether the parameter is required. Defaults to `nil` (optional).
        public var required: Bool?

        /// A default value for the parameter, used if it is not explicitly specified in the directive.
        public var `default`: String?

        /// Initializes a `Parameter` for a directive.
        ///
        /// - Parameters:
        ///   - label: The name of the parameter.
        ///   - isRequired: Indicates if the parameter must be provided.
        ///   - defaultValue: A fallback value if none is provided.
        public init(
            label: String,
            isRequired: Bool? = nil,
            defaultValue: String? = nil
        ) {
            self.label = label
            self.required = isRequired
            self.default = defaultValue
        }
    }

    /// Represents a static HTML attribute that will be rendered on the directive's HTML tag.
    public struct Attribute: Codable, Equatable {

        /// The name of the HTML attribute (e.g., `class`, `id`).
        public var name: String

        /// The corresponding value of the attribute.
        public var value: String

        /// Initializes an `Attribute` for the rendered directive HTML tag.
        ///
        /// - Parameters:
        ///   - name: The attribute key.
        ///   - value: The attribute value.
        public init(
            name: String,
            value: String
        ) {
            self.name = name
            self.value = value
        }
    }

    /// The name of the directive (e.g., `"note"`, `"warning"`, `"info"`).
    public var name: String

    /// A list of supported parameters for the directive.
    public var parameters: [Parameter]?

    /// If specified, this directive must appear within another directive of the given name.
    public var requiresParentDirective: String?

    /// Indicates whether child paragraphs should be removed from the HTML output. Defaults to `nil`.
    public var removesChildParagraph: Bool?

    /// The HTML tag to render (e.g., `"div"`, `"section"`, `"aside"`).
    public var tag: String?

    /// Static attributes to apply to the rendered HTML tag.
    public var attributes: [Attribute]?

    /// Custom output HTML string that overrides default rendering behavior, if provided.
    public var output: String?

    /// Initializes a `MarkdownBlockDirective`.
    ///
    /// - Parameters:
    ///   - name: The directive's name.
    ///   - parameters: Optional list of accepted parameters.
    ///   - requiresParentDirective: Name of a parent directive this one must reside within.
    ///   - removesChildParagraph: Whether to exclude child `<p>` tags during rendering.
    ///   - tag: HTML tag to be generated.
    ///   - attributes: HTML attributes to apply.
    ///   - output: Optional custom HTML output template.
    public init(
        name: String,
        parameters: [Parameter]? = nil,
        requiresParentDirective: String? = nil,
        removesChildParagraph: Bool? = nil,
        tag: String? = nil,
        attributes: [Attribute]? = nil,
        output: String? = nil
    ) {
        self.name = name
        self.parameters = parameters
        self.requiresParentDirective = requiresParentDirective
        self.removesChildParagraph = removesChildParagraph
        self.tag = tag
        self.attributes = attributes
        self.output = output
    }
}


================================================
FILE: Sources/ToucanMarkdown/Markdown/MarkdownToHTMLRenderer.swift
================================================
//
//  MarkdownToHTMLRenderer.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 19..
//

import Logging
import Markdown
import ToucanCore

/// A renderer that converts Markdown text to HTML, with support for custom block directives and paragraph styling.
public struct MarkdownToHTMLRenderer {
    /// Custom block directives to extend Markdown syntax.
    public let customBlockDirectives: [MarkdownBlockDirective]

    /// A collection of paragraph styles.
    public let paragraphStyles: [String: [String]]

    /// Logger instance
    public let logger: Logger

    /// Initializes a `MarkdownToHTMLRenderer`.
    ///
    /// - Parameters:
    ///   - customBlockDirectives: A list of custom Markdown block directives to parse during rendering.
    ///   - paragraphStyles: The paragraph styles configuration for styling rendered HTML.
    ///   - logger: A logger instance for logging. Defaults to a logger labeled "MarkdownToHTMLRenderer".
    public init(
        customBlockDirectives: [MarkdownBlockDirective] = [],
        paragraphStyles: [String: [String]] = [:],
        logger: Logger = .subsystem("markdown-to-html-renderer")
    ) {
        self.customBlockDirectives = customBlockDirectives
        self.paragraphStyles = paragraphStyles
        self.logger = logger
    }

    // MARK: - render api

    /// Renders the provided Markdown string to an HTML string.
    ///
    /// - Parameters:
    ///   - markdown: The input Markdown text to render.
    ///   - slug: A slug identifier used for generating.
    ///   - assetsPath: The path to the assets folder used for resource resolution.
    ///   - baseURL: The base URL used to resolve relative links within the Markdown.
    ///
    /// - Returns: A fully rendered HTML string.
    public func renderHTML(
        markdown: String,
        slug: String,
        assetsPath: String,
        baseURL: String
    ) -> String {
        // Create a Markdown document, enabling block directives if any are provided.
        let document = Document(
            parsing: markdown,
            options: !customBlockDirectives.isEmpty
                ? [.parseBlockDirectives] : []
        )

        // Initialize the HTML visitor with the current configuration.
        var htmlVisitor = HTMLVisitor(
            blockDirectives: customBlockDirectives,
            paragraphStyles: paragraphStyles,
            slug: slug,
            assetsPath: assetsPath,
            baseURL: baseURL
        )

        // Generate HTML by visiting the document tree.
        return htmlVisitor.visitDocument(document)
    }
}


================================================
FILE: Sources/ToucanMarkdown/MarkdownRenderer.swift
================================================
//
//  MarkdownRenderer.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 20..
//

import Logging
import ToucanCore

/// A comprehensive content processing engine that renders Markdown content to HTML,
/// applies transformations, computes reading time, and generates an outline structure.
public struct MarkdownRenderer {

    /// Holds all the settings required for rendering and processing content.
    public struct Configuration {
        /// Configuration specific to Markdown processing.
        public struct Markdown {

            /// Custom block directives to extend the Markdown grammar.
            public var customBlockDirectives: [MarkdownBlockDirective]

            //

            /// Initializes a Markdown configuration.
            public init(
                customBlockDirectives: [MarkdownBlockDirective]
            ) {
                self.customBlockDirectives = customBlockDirectives
            }
        }

        /// Configuration for outlining logic, such as which heading levels to parse.
        public struct Outline {

            /// Which heading levels to include in the parsed outline.
            public var levels: [Int]

            //

            /// Initializes an Outline configuration.
            public init(
                levels: [Int]
            ) {
                self.levels = levels
            }
        }

        /// Configuration for estimating reading time.
        public struct ReadingTime {

            /// Estimated words per minute reading speed.
            public var wordsPerMinute: Int

            //

            /// Initializes a ReadingTime configuration.
            public init(
                wordsPerMinute: Int
            ) {
                self.wordsPerMinute = wordsPerMinute
            }
        }

        /// Markdown-specific rendering options.
        public var markdown: Markdown

        /// Outline-parsing preferences.
        public var outline: Outline

        /// Reading time calculation preferences.
        public var readingTime: ReadingTime

        /// Optional transformation pipeline to apply pre-processing on the input.
        public var transformerPipeline: TransformerPipeline?

        /// Paragraph styles for customizing the HTML rendering.
        public var paragraphStyles: [String: [String]]

        /// Initializes a new rendering configuration.
        ///
        /// - Parameters:
        ///   - markdown: Markdown rendering configuration.
        ///   - outline: Outline extraction preferences.
        ///   - readingTime: Reading time estimation settings.
        ///   - transformerPipeline: Optional content transformation pipeline.
        ///   - paragraphStyles: Block-level style customization for HTML rendering.
        public init(
            markdown: Markdown,
            outline: Outline,
            readingTime: ReadingTime,
            transformerPipeline: TransformerPipeline?,
            paragraphStyles: [String: [String]]
        ) {
            self.markdown = markdown
            self.outline = outline
            self.readingTime = readingTime
            self.transformerPipeline = transformerPipeline
            self.paragraphStyles = paragraphStyles
        }
    }

    /// Final output of the rendering pipeline.
    public struct Output {
        /// The fully rendered HTML output.
        public var html: String

        /// Estimated reading time in minutes.
        public var readingTime: Int

        ///  A hierarchical structure representing the document's headings.
        public var outline: [Outline]
    }

    /// Configuration for rendering, including markdown styles, outline levels, and transformation settings.
    public var configuration: Configuration

    /// Responsible for converting Markdown into HTML with support for custom directives and styling.
    public var markdownToHTMLRenderer: MarkdownToHTMLRenderer

    /// Parses the rendered HTML to build a heading outline (used for TOC or navigation).
    public var outlineParser: OutlineParser

    /// Calculates the estimated reading time for a given HTML or Markdown document.
    public var readingTimeCalculator: ReadingTimeCalculator

    /// Logger for diagnostics and error reporting during rendering.
    public var logger: Logger

    /// Creates a new `ContentRenderer` instance with the provided configuration, file manager, and logger.
    ///
    /// - Parameters:
    ///   - configuration: Rendering configuration including markdown, outline, and reading time options.
    ///   - logger: Optional logger for tracking events and issues.
    public init(
        configuration: Configuration,
        logger: Logger = .subsystem("content-renderer")
    ) {
        self.configuration = configuration

        self.markdownToHTMLRenderer = MarkdownToHTMLRenderer(
            customBlockDirectives: configuration.markdown.customBlockDirectives,
            paragraphStyles: configuration.paragraphStyles
        )

        self.outlineParser = OutlineParser(
            levels: configuration.outline.levels
        )

        self.readingTimeCalculator = ReadingTimeCalculator(
            wordsPerMinute: configuration.readingTime.wordsPerMinute
        )

        self.logger = logger
    }

    /// Processes the input Markdown content, optionally transforms it, renders it as HTML,
    /// calculates reading time, and generates an outline.
    ///
    /// - Parameters:
    ///   - content: The raw Markdown content to process.
    ///   - typeAwareID: A unique identifier used for transformation and rendering context.
    ///   - slug: The slug of the content.
    ///   - assetsPath: Path to associated assets (e.g., images or includes).
    ///   - baseURL: The base URL for resolving relative paths or links.
    ///
    /// - Returns: A structured `Output` containing HTML, reading time, and outline.
    public func render(
        content: String,
        typeAwareID: String,
        slug: String,
        assetsPath: String,
        baseURL: String
    ) -> Output {
        var finalHtml = content
        var shouldRenderMarkdown = true

        // Step 1: Run transformer pipeline, if defined and non-empty.
        if let transformerPipeline = configuration.transformerPipeline {
            if !transformerPipeline.run.isEmpty {
                shouldRenderMarkdown = transformerPipeline.isMarkdownResult
                let executor = TransformerExecutor(
                    pipeline: transformerPipeline
                )
                do {
                    finalHtml = try executor.transform(
                        contents: finalHtml,
                        id: typeAwareID,
                        slug: slug
                    )
                }
                catch {
                    logger.error("\(String(describing: error))")
                }
            }
            else {
                logger.warning("Empty transformer pipeline.")
            }
        }

        // Step 2: If the transformer output isn't already HTML, render Markdown to HTML.
        if shouldRenderMarkdown {
            finalHtml = markdownToHTMLRenderer.renderHTML(
                markdown: content,
                slug: slug,
                assetsPath: assetsPath,
                baseURL: baseURL
            )
        }

        // Step 3: Calculate reading time and parse outline from HTML.
        let readingTime = readingTimeCalculator.calculate(for: finalHtml)
        let outline = outlineParser.parseHTML(finalHtml)

        return .init(
            html: finalHtml,
            readingTime: readingTime,
            outline: outline
        )
    }
}


================================================
FILE: Sources/ToucanMarkdown/Outline/Outline.swift
================================================
//
//  Outline.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 04. 17..
//

/// A hierarchical representation of an outline element, used for
/// structuring headings or sections in a document or interface.
public struct Outline: Equatable, Codable {
    /// The depth level of the outline node (e.g., 1 for top-level, 2 for a subheading, etc.).
    public var level: Int

    /// The display text of the outline entry, such as a heading title.
    public var text: String

    /// An optional fragment identifier that can be used for navigation (e.g., URL anchors).
    public var fragment: String?

    /// A list of child outlines, representing nested structure under this node.
    public var children: [Outline]

    /// Initializes a new `Outline` instance.
    ///
    /// - Parameters:
    ///   - level: The heading level of the outline (e.g., 1 for `h1`, 2 for `h2`, etc.).
    ///   - text: The display text for this outline item.
    ///   - fragment: An optional anchor or link target associated with this item.
    ///   - children: A list of nested `Outline` elements under this item.
    public init(
        level: Int,
        text: String,
        fragment: String? = nil,
        children: [Outline] = []
    ) {
        self.level = level
        self.text = text
        self.fragment = fragment
        self.children = children
    }
}


================================================
FILE: Sources/ToucanMarkdown/Outline/OutlineParser.swift
================================================
//
//  OutlineParser.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2024. 10. 14..
//

import Logging
import SwiftSoup
import ToucanCore

/// A parser that extracts heading elements (`<h1>` to `<h6>`) from HTML and converts them into a structured outline.
public struct OutlineParser {
    /// The heading levels (e.g., `[1, 2, 3]` for `<h1>`, `<h2>`, and `<h3>`) to include in the outline.
    public var levels: [Int]

    /// Logger instance
    public var logger: Logger

    /// Initializes an `OutlineParser` with optional levels and a logger.
    ///
    /// - Parameters:
    ///   - levels: Heading levels to extract from the HTML. Must be between 1 and 6. Defaults to all (`[1, 2, 3, 4, 5, 6]`).
    ///   - logger: A `Logger` instance for capturing logs. Defaults to a logger labeled "OutlineParser".
    public init(
        levels: [Int] = [1, 2, 3, 4, 5, 6],
        logger: Logger = .subsystem("outline-parser")
    ) {
        // Ensure levels are within the valid range of HTML headings.
        precondition(
            levels.allSatisfy { 1...6 ~= $0 },
            "Values must be between 1 and 6."
        )

        self.levels = levels
        self.logger = logger
    }

    /// Converts a single SwiftSoup element into an `Outline` if it corresponds to a valid heading.
    ///
    /// - Parameter element: A SwiftSoup `Element` representing a heading node.
    /// - Returns: An `Outline` instance if the element is a valid heading, otherwise `nil`.
    /// - Throws: An error if parsing the element fails.
    func createToC(
        from element: SwiftSoup.Element
    ) throws -> Outline? {
        let text = try element.text()

        let nodeName = element.nodeName()
        guard
            nodeName.count > 1,
            let rawLevel = nodeName.last,
            let level = Int(String(rawLevel)),
            (1...6).contains(level)
        else {
            return nil
        }

        var fragment: String?
        let id = try element.attr("id")
        if !id.isEmpty {
            fragment = id
        }

        return .init(
            level: level,
            text: text,
            fragment: fragment
        )
    }

    /// Parses the given HTML string and returns a flat list of `Outline` items corresponding to the specified heading levels.
    ///
    /// - Parameter html: A string of HTML content.
    /// - Returns: An array of `Outline` instances representing the headings found.
    public func parseHTML(
        _ html: String
    ) -> [Outline] {
        do {
            // Parse HTML content into a SwiftSoup document.
            let document = try SwiftSoup.parse(html)

            // Build a CSS selector for the specified heading levels (e.g., "h1, h2, h3").
            let tagSelector = levels.map { "h\($0)" }.joined(separator: ", ")

            // Select and process matching heading elements.
            let headings = try document.select(tagSelector)
            return try headings.compactMap { try createToC(from: $0) }
        }
        catch let Exception.Error(type, message) {
            logger.error("\(type) - \(message)")
            return []
        }
        catch {
            logger.error("\(error.localizedDescription)")
            return []
        }
    }
}


================================================
FILE: Sources/ToucanMarkdown/ReadingTime/ReadingTimeCalculator.swift
================================================
//
//  ReadingTimeCalculator.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2024. 10. 15..
//

import Logging
import ToucanCore

/// A utility to estimate the reading time of a given string of text based on words per minute.
public struct ReadingTimeCalculator {
    /// The number of words assumed to be read per minute.
    public var wordsPerMinute: Int

    /// Logger instance
    public var logger: Logger

    /// Initializes a new instance of `ReadingTimeCalculator`.
    ///
    /// - Parameters:
    ///   - wordsPerMinute: The number of words a person can read per minute. Defaults to 238.
    ///   - logger: A `Logger` instance for logging internal operations. Defaults to a logger labeled "ReadingTimeCalculator".
    public init(
        wordsPerMinute: Int = 238,
        logger: Logger = .subsystem("reading-time-calculator")
    ) {
        self.wordsPerMinute = wordsPerMinute
        self.logger = logger
    }

    /// Calculates the estimated reading time for a given string.
    ///
    /// - Parameter string: The input text to estimate reading time for.
    /// - Returns: An estimated reading time in minutes. Returns at least 1 minute.
    public func calculate(
        for string: String
    ) -> Int {
        max(string.split(separator: " ").count / wordsPerMinute, 1)
    }
}


================================================
FILE: Sources/ToucanMarkdown/Transformers/ContentTransformer.swift
================================================
//
//  ContentTransformer.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 21..
//

/// Represents a content transformer command used in a transformation pipeline.
public struct ContentTransformer {

    /// The directory path where the executable is located.
    /// Defaults to `"/usr/local/bin"` if not explicitly specified.
    public var path: String

    /// The name of the executable or script to run.
    public var name: String

    /// Initializes a new `ContentTransformer` with an optional path and required name.
    ///
    /// - Parameters:
    ///   - path: The directory path to the executable. Defaults to `"/usr/local/bin"`.
    ///   - name: The name of the command-line executable or script.
    public init(
        path: String = "/usr/local/bin",
        name: String
    ) {
        self.path = path
        self.name = name
    }
}


================================================
FILE: Sources/ToucanMarkdown/Transformers/TransformerExecutor.swift
================================================
//
//  TransformerExecutor.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2024. 10. 15..
//

import Foundation
import Logging
import SwiftCommand
import ToucanCore

/// Executes a sequence of shell-based transformation commands defined in a `TransformerPipeline`,
/// allowing content to be programmatically modified.
public struct TransformerExecutor {
    /// The transformation pipeline consisting of commands to execute.
    public var pipeline: TransformerPipeline

    /// File manager utility for file system interactions, including temp files and cleanup.
    public var fileManager: FileManager

    /// Logger instance.
    public var logger: Logger

    /// Initializes a `TransformerExecutor` with a transformation pipeline and file manager.
    ///
    /// - Parameters:
    ///   - pipeline: A sequence of external commands to run for transformation.
    ///   - fileManager: A file manager abstraction for working with files.
    ///   - logger: A logger for capturing stdout, stderr, and errors.
    public init(
        pipeline: TransformerPipeline,
        fileManager: FileManager = .default,
        logger: Logger = .subsystem("transformer-executor")
    ) {
        self.pipeline = pipeline
        self.fileManager = fileManager
        self.logger = logger
    }

    /// Transforms the given content string using the defined pipeline.
    ///
    /// This function:
    /// - Saves the content to a temporary file.
    /// - Executes each command in the pipeline sequentially, modifying the file in place.
    /// - Captures and logs output and errors.
    /// - Returns the final transformed content.
    ///
    /// - Parameters:
    ///   - contents: The raw content to be transformed.
    ///   - id: An identifier used to pass context to the commands.
    ///   - slug: The slug of the content.
    ///
    /// - Throws: Rethrows any error encountered during reading, writing, or transformation.
    /// - Returns: The final transformed content string.
    public func transform(
        contents: String,
        id: String,
        slug: String
    ) throws -> String {
        // Step 1: Write the content to a temp file
        let tempDirectoryURL = fileManager.temporaryDirectory
        let fileName = UUID().uuidString
        let fileURL = tempDirectoryURL.appendingPathComponent(fileName)
        try contents.write(to: fileURL, atomically: true, encoding: .utf8)

        // Step 2: Run each command in the transformation pipeline
        for command in pipeline.run {
            do {
                let arguments: [String] = [
                    "--id", id,
                    "--file", fileURL.path,
                    "--slug", slug,
                ]
                let commandURL = URL(fileURLWithPath: command.path)
                    .appendingPathComponent(command.name)

                let command = Command(executablePath: .init(commandURL.path()))
                    .addArguments(arguments)

                let result = try command.waitForOutput()

                // Log output and errors
                if !result.stdout.isEmpty {
                    logger.debug("\(result)")
                }
                if let err = result.stderr, !err.isEmpty {
                    logger.error("\(err)")
                }
            }
            catch {
                logger.error("\(error))")
            }
        }

        // Step 3: Read the transformed contents, clean up, and return
        do {
            let finalContents = try String(
                contentsOf: fileURL,
                encoding: .utf8
            )
            try fileManager.removeItem(at: fileURL)
            return finalContents
        }
        catch {
            // Ensure cleanup is still performed
            try fileManager.removeItem(at: fileURL)
            throw error
        }
    }
}


================================================
FILE: Sources/ToucanMarkdown/Transformers/TransformerPipeline.swift
================================================
//
//  TransformerPipeline.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 21..
//

/// Represents a sequence of content transformers to run before rendering,
/// along with an indicator of whether the final result is Markdown.
public struct TransformerPipeline {

    /// An ordered list of transformers (external commands or scripts) to execute.
    ///
    /// Each `ContentTransformer` represents an individual transformation step.
    public var run: [ContentTransformer]

    /// Indicates whether the final output from this pipeline is expected to be Markdown.
    ///
    /// If `false`, the renderer may treat the output as already-formatted HTML or another format.
    public var isMarkdownResult: Bool

    /// Initializes a new `TransformerPipeline`.
    ///
    /// - Parameters:
    ///   - run: An array of `ContentTransformer` instances to execute.
    ///   - isMarkdownResult: A flag indicating whether the final output is Markdown. Defaults to `true`.
    public init(
        run: [ContentTransformer] = [],
        isMarkdownResult: Bool = true
    ) {
        self.run = run
        self.isMarkdownResult = isMarkdownResult
    }
}


================================================
FILE: Sources/ToucanSDK/Behaviors/Behavior.swift
================================================
//
//  Behavior.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 06. 16..
//

import struct Foundation.URL

/// A protocol that defines a behavior with a unique identifier
/// and an operation that runs on a given file URL.
protocol Behavior {
    /// A unique identifier for the behavior.
    static var id: String { get }

    /// Executes the behavior with the given file URL.
    ///
    /// - Parameter fileURL: The URL of the file to process.
    /// - Returns: A `String` result of the behavior.
    /// - Throws: An error if the behavior fails.
    func run(fileURL: URL) throws -> String
}


================================================
FILE: Sources/ToucanSDK/Behaviors/CompileSASSBehavior.swift
================================================
//
//  CompileSASSBehavior.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 12..
//

import DartSass
import Foundation

struct CompileSASSBehavior: Behavior {

    static let id = "compile-sass"

    var compiler: Compiler

    init() throws {
        self.compiler = try .init()
    }

    /// NOTE: This is horrible... but we can live with it for a while :)
    private func unsafeSyncCompile(fileURL: URL) -> String {
        final class Enclosure: @unchecked Sendable {
            var value: CompilerResults!
        }

        let semaphore = DispatchSemaphore(value: 0)
        let enclosure = Enclosure()

        Task {
            do {
                enclosure.value =
                    try await compiler.compile(
                        fileURL: fileURL
                    )
            }
            catch {
                fatalError("\(error) - \(fileURL.path())")
            }

            semaphore.signal()
        }

        semaphore.wait()
        return enclosure.value.css
    }

    func run(fileURL: URL) throws -> String {
        let css = unsafeSyncCompile(fileURL: fileURL)

        return css
    }
}


================================================
FILE: Sources/ToucanSDK/Behaviors/MinifyCSSBehavior.swift
================================================
//
//  MinifyCSSBehavior.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 12..
//

import Foundation
import SwiftCSSParser

struct MinifyCSSBehavior: Behavior {

    static let id = "minify-css"

    func run(fileURL: URL) throws -> String {
        let src = try String(
            contentsOf: fileURL,
            encoding: .utf8
        )
        let stylesheet = try Stylesheet.parse(from: src)
        return stylesheet.minified()
    }
}


================================================
FILE: Sources/ToucanSDK/Content/Content+Query.swift
================================================
//
//  Content+Query.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 01. 31..
//

import Foundation
import ToucanSource
import Logging

public extension Content {
    /// Flattens the content's core properties, relations, and metadata into a single dictionary
    /// for use in filtering, querying, or templating contexts.
    ///
    /// - Includes:
    ///   - All `properties` as defined in the content type
    ///   - Resolved `relations`, where:
    ///     - `.one` types return a single identifier (or an empty array if unresolved)
    ///     - `.many` types return an array of identifiers
    ///   - Additional metadata:
    ///     - `"id"`: The content's unique identifier
    ///     - `"slug"`: The slug string used for URLs
    ///     - `"lastUpdate"`: Last modification timestamp of the content
    ///     - `"iterator"`: Boolean flag indicating if this content is an iterator item
    ///
    /// - Returns: A `[String: AnyCodable]` dictionary representing queryable fields.
    var queryFields: [String: AnyCodable] {
        var fields = properties

        // Flatten relational fields by type
        for (key, relation) in relations {
            switch relation.type {
            case .one:
                if relation.identifiers.isEmpty {
                    // Default to empty array if no target
                    fields[key] = .init([])
                }
                else {
                    fields[key] = .init(relation.identifiers[0])  // Single ID
                }
            case .many:
                fields[key] = .init(relation.identifiers)  // Array of IDs
            }
        }

        // Append metadata fields
        fields[SystemPropertyKeys.id.rawValue] = .init(typeAwareID)
        fields[SystemPropertyKeys.lastUpdate.rawValue] = .init(
            rawValue.lastModificationDate
        )
        fields[SystemPropertyKeys.slug.rawValue] = .init(slug.value)
        fields[RootContextKeys.iterator.rawValue] = .init(isIterator)

        return fields
    }
}

public extension [Content] {
    /// Executes a `Query` against the current content collection, applying filtering,
    /// sorting, and pagination.
    ///
    /// - Parameters:
    ///   - query: The `Query` object containing filtering, ordering, and limit logic.
    ///   - now: The current timestamp used for time-based filtering.
    ///   - logger: A `Logger` instance for capturing logs.
    /// - Returns: A filtered, sorted, and paginated array of `Content` items.
    func run(
        query: Query,
        now: TimeInterval,
        logger: Logger
    ) -> [Content] {
        let contents = filter { query.contentType == $0.type.id }
        return filter(
            contents: contents,
            using: query.resolveFilterParameters(
                with: [
                    "date.now": .init(now)
                ]
            ),
            logger: logger
        )
    }

    /// Filters, sorts, and slices the given content array based on a query.
    private func filter(
        contents: [Content],
        using query: Query,
        logger: Logger
    ) -> [Content] {
        var filteredContents = contents.filter { element in
            evaluate(condition: query.filter, with: element.queryFields)
        }

        for order in query.orderBy.reversed() {
            filteredContents.sort { a, b in
                let propertyForOrderKey: (Content) -> AnyCodable? = { item in
                    guard let value = item.properties[order.key] else {
                        logger.warning(
                            "Missing order property key: `\(order.key)`.",
                            metadata: [
                                "slug": .string(item.slug.value),
                                "contentType": .string(query.contentType),
                            ]
                        )
                        return nil
                    }
                    return value
                }

                guard
                    let valueA = propertyForOrderKey(a),
                    let valueB = propertyForOrderKey(b)
                else {
                    return false
                }

                return compare(
                    valueA,
                    valueB,
                    ascending: order.direction == .asc
                )
            }
        }

        if let offset = query.offset {
            filteredContents = Array(filteredContents.dropFirst(offset))
        }

        if let limit = query.limit {
            filteredContents = Array(filteredContents.prefix(limit))
        }
        return filteredContents
    }

    /// Recursively evaluates a `Condition` tree against a set of content fields.
    private func evaluate(
        condition: Condition?,
        with props: [String: AnyCodable]
    ) -> Bool {
        guard let condition else { return true }

        switch condition {
        case let .field(key, `operator`, value):
            guard let fieldValue = props[key] else { return false }
            return evaluateField(
                fieldValue: fieldValue,
                operator: `operator`,
                value: value
            )

        case let .and(conditions):
            return conditions.allSatisfy {
                evaluate(condition: $0, with: props)
            }

        case let .or(conditions):
            return conditions.contains {
                evaluate(condition: $0, with: props)
            }
        }
    }

    /// Compares two values for equality, supporting multiple types.
    private func equals(_ valueA: AnyCodable, _ valueB: AnyCodable) -> Bool {
        if let a = valueA.value(as: Bool.self),
            let b = valueB.value(as: Bool.self)
        {
            return a == b
        }

        if let a = valueA.value(as: Int.self),
            let b = valueB.value(as: Int.self)
        {
            return a == b
        }

        if let a = valueA.value(as: Double.self),
            let b = valueB.value(as: Double.self)
        {
            return a == b
        }

        if let a = valueA.value(as: String.self),
            let b = valueB.value(as: String.self)
        {
            return a == b
        }

        return false
    }

    /// Performs numeric or string comparison between two values, with optional inclusiveness.
    private func compare(
        _ valueA: AnyCodable,
        _ valueB: AnyCodable,
        ascending: Bool,
        isInclusive: Bool = false
    ) -> Bool {
        if let a = valueA.value(as: Int.self),
            let b = valueB.value(as: Int.self)
        {
            return isInclusive
                ? (ascending ? a <= b : a >= b) : (ascending ? a < b : a > b)
        }

        if let a = valueA.value(as: Double.self),
            let b = valueB.value(as: Double.self)
        {
            return isInclusive
                ? (ascending ? a <= b : a >= b) : (ascending ? a < b : a > b)
        }

        if let a = valueA.value(as: String.self),
            let b = valueB.value(as: String.self)
        {
            return isInclusive
                ? (ascending ? a <= b : a >= b) : (ascending ? a < b : a > b)
        }

        return false
    }

    /// Evaluates a field condition against a value using the provided operator.
    private func evaluateField(
        fieldValue: AnyCodable,
        operator: Operator,
        value: AnyCodable
    ) -> Bool {
        switch `operator` {
        case .equals: return equals(fieldValue, value)

        case .notEquals: return !equals(fieldValue, value)

        case .lessThan: return compare(fieldValue, value, ascending: true)

        case .greaterThan: return compare(fieldValue, value, ascending: false)

        case .lessThanOrEquals:
            return compare(
                fieldValue,
                value,
                ascending: true,
                isInclusive: true
            )

        case .greaterThanOrEquals:
            return compare(
                fieldValue,
                value,
                ascending: false,
                isInclusive: true
            )

        case .like:
            return fieldValue.value(as: String.self)?
                .contains(value.value(as: String.self) ?? "") ?? false

        case .caseInsensitiveLike:
            return fieldValue.value(as: String.self)?
                .lowercased()
                .contains(value.value(as: String.self)?.lowercased() ?? "")
                ?? false

        case .in:
            if let v = fieldValue.value(as: Int.self),
                let arr = value.value(as: [Int].self)
            {
                return arr.contains(v)
            }
            if let v = fieldValue.value(as: Double.self),
                let arr = value.value(as: [Double].self)
            {
                return arr.contains(v)
            }
            if let v = fieldValue.value(as: String.self),
                let arr = value.value(as: [String].self)
            {
                return arr.contains(v)
            }
            return false

        case .contains:
            if let arr = fieldValue.value(as: [Int].self),
                let v = value.value(as: Int.self)
            {
                return arr.contains(v)
            }
            if let arr = fieldValue.value(as: [Double].self),
                let v = value.value(as: Double.self)
            {
                return arr.contains(v)
            }
            if let arr = fieldValue.value(as: [String].self),
                let v = value.value(as: String.self)
            {
                return arr.contains(v)
            }
            return false

        case .matching:
            if let arr = fieldValue.value(as: [Int].self),
                let other = value.value(as: [Int].self)
            {
                return !Set(arr).intersection(other).isEmpty
            }
            if let arr = fieldValue.value(as: [Double].self),
                let other = value.value(as: [Double].self)
            {
                return !Set(arr).intersection(other).isEmpty
            }
            if let arr = fieldValue.value(as: [String].self),
                let other = value.value(as: [String].self)
            {
                return !Set(arr).intersection(other).isEmpty
            }
            return false
        }
    }
}


================================================
FILE: Sources/ToucanSDK/Content/Content.swift
================================================
//
//  Content.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 01. 15..
//

import ToucanSource

/// Represents a unit of structured content, with associated metadata, relationships, and rendering information.
public struct Content {

    /// The content type definition that describes structure and expected fields.
    public var type: ContentType

    /// A globally unique string identifier for this content item.
    /// This value remains constant across contexts and is used for persistence or lookup.
    public var typeAwareID: String

    /// A URL-friendly slug that identifies the content in paths or links.
    public var slug: Slug

    /// The raw content representation, usually Markdown or HTML source.
    public var rawValue: RawContent

    /// A dictionary of properties that hold the parsed field values (e.g., title, date, body).
    /// Keys are field names as defined in the `ContentType`, and values are dynamically typed.
    public var properties: [String: AnyCodable]

    /// A dictionary of relations to other content items, keyed by relation name.
    /// The relation values may include identifiers or full references depending on usage.
    public var relations: [String: RelationValue]

    /// Arbitrary user-defined metadata not explicitly declared in the content definition.
    /// These are typically useful for extensibility or plugin features.
    public var userDefined: [String: AnyCodable]

    /// Optional iterator metadata if the content is generated through iteration (e.g., paginated or list item).
    public var iteratorInfo: IteratorInfo?

    /// A computed flag indicating whether this content instance was generated via iteration.
    public var isIterator: Bool { iteratorInfo != nil }

    /// Initializes a new `Content` instance.
    ///
    /// - Parameters:
    ///   - type: Structural schema for this content.
    ///   - typeAwareID: A unique identifier.
    ///   - slug: A human-readable URL slug.
    ///   - rawValue: The unparsed content.
    ///   - properties: Parsed content fields.
    ///   - relations: Links to other content.
    ///   - userDefined: Freeform or plugin-provided metadata.
    ///   - iteratorInfo: Optional info for repeated or generated content.
    public init(
        type: ContentType,
        typeAwareID: String,
        slug: Slug,
        rawValue: RawContent,
        properties: [String: AnyCodable],
        relations: [String: RelationValue],
        userDefined: [String: AnyCodable],
        iteratorInfo: IteratorInfo?
    ) {
        self.type = type
        self.typeAwareID = typeAwareID
        self.slug = slug
        self.rawValue = rawValue
        self.properties = properties
        self.relations = relations
        self.userDefined = userDefined
        self.iteratorInfo = iteratorInfo
    }
}


================================================
FILE: Sources/ToucanSDK/Content/ContentResolver.swift
================================================
//
//  ContentResolver.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 21..
//

import Foundation
import Logging
import ToucanCore
import ToucanSerialization
import ToucanSource

private extension Path {

    func getTypeAwareIdentifier() -> String {
        let newRawPath =
            value
            .split(separator: "/")
            .last
            .map(String.init) ?? ""
        return Path(newRawPath).trimmingBracketsContent()
    }
}

enum ContentResolverError: ToucanError {
    case contentType(ContentTypeResolverError)
    case missingProperty(String, String)
    case missingRelation(String, String)
    case invalidProperty(String, String, String)
    case invalidSlug(String)
    case unknown(Error)

    var underlyingErrors: [any Error] {
        switch self {
        case let .contentType(error):
            [error]
        case .missingProperty:
            []
        case .missingRelation:
            []
        case .invalidProperty:
            []
        case .invalidSlug:
            []
        case let .unknown(error):
            [error]
        }
    }

    var logMessage: String {
        switch self {
        case .contentType(_):
            "Content type related error."
        case let .missingProperty(name, slug):
            "Missing property `\(name)` for content: \(slug)."
        case let .missingRelation(name, slug):
            "Missing property `\(name)` for content: \(slug)."
        case let .invalidProperty(name, value, slug):
            "Invalid property `\(name): \(value)` for content: \(slug)."
        case let .invalidSlug(slug):
            "Invalid slug for content: \(slug)."
        case let .unknown(error):
            error.localizedDescription
        }
    }

    var userFriendlyMessage: String {
        switch self {
        case .contentType(_):
            "Content type related error."
        case let .missingProperty(name, slug):
            "Missing property `\(name)` for content: `\(slug)`."
        case let .missingRelation(name, slug):
            "Missing property `\(name)` for content: `\(slug)`."
        case let .invalidProperty(name, value, slug):
            "Invalid property `\(name): \(value)` for content: \(slug)."
        case let .invalidSlug(slug):
            "Invalid slug for content: \(slug)."
        case .unknown:
            "Unknown content conversion error."
        }
    }
}

struct ContentResolver {

    var contentTypeResolver: ContentTypeResolver
    var encoder: ToucanEncoder
    var decoder: ToucanDecoder
    var dateFormatter: ToucanInputDateFormatter
    var logger: Logger

    init(
        contentTypeResolver: ContentTypeResolver,
        encoder: ToucanEncoder,
        decoder: ToucanDecoder,
        dateFormatter: ToucanInputDateFormatter,
        logger: Logger = .subsystem("content-resolver")
    ) {
        self.contentTypeResolver = contentTypeResolver
        self.encoder = encoder
        self.decoder = decoder
        self.dateFormatter = dateFormatter
        self.logger = logger
    }

    private func rewrite(
        iteratorID: String,
        pageIndex: Int,
        _ value: inout String
    ) {
        value = value.replacing([
            "{{\(iteratorID)}}": String(pageIndex)
        ])
    }

    private func rewrite(
        number: Int,
        total: Int,
        _ array: inout [String: AnyCodable]
    ) {
        for (key, _) in array {
            if let stringValue = array[key]?.stringValue() {
                array[key] = .init(
                    replace(
                        in: stringValue,
                        number: number,
                        total: total
                    )
                )
            }
        }
    }

    private func replace(
        in value: String,
        number: Int,
        total: Int
    ) -> String {
        value.replacing([
            "{{number}}": String(number),
            "{{total}}": String(total),
        ])
    }

    private func createDictionaryValues(
        assetKeys: [String],
        array: [String]
    ) -> [String: AnyCodable] {
        var values: [String: AnyCodable] = [:]
        for i in 0..<array.count {
            values[assetKeys[i]] = .init(array[i])
        }
        return values
    }

    private func filterFilePaths(
        from paths: [String],
        input: Pipeline.Assets.Location
    ) -> [String] {
        paths.filter { filePath in
            guard let url = URL(string: filePath) else {
                return false
            }

            let path = url.deletingLastPathComponent().path
            let name = url.deletingPathExtension().lastPathComponent
            let ext = url.pathExtension

            let inputPath = input.path ?? ""
            let pathMatches =
                inputPath == "*" || inputPath.isEmpty || path == inputPath
            let nameMatches =
                input.name == "*" || input.name.isEmpty || name == input.name
            let extMatches =
                input.ext == "*" || input.ext.isEmpty || ext == input.ext
            return pathMatches && nameMatches && extMatches
        }
    }

    // MARK: - asset behaviors

    private func getNameAndExtension(
        from path: String
    ) -> (name: String, ext: String) {
        let safePath = path.split(separator: "/").last.map(String.init) ?? ""

        let parts = safePath.split(
            separator: ".",
            omittingEmptySubsequences: false
        )
        guard parts.count >= 2 else {
            return (String(safePath), "")  // No extension
        }

        let ext = String(parts.last!)
        let filename = parts.dropLast().joined(separator: ".")

        return (filename, ext)
    }

    // MARK: - conversion

    func convert(
        rawContents: [RawContent]
    ) throws(ContentResolverError) -> [Content] {
        do {
            return try rawContents.map {
                try convert(rawContent: $0)
            }
        }
        catch let error as ContentResolverError {
            throw error
        }
        catch {
            throw .unknown(error)
        }
    }

    // MARK: - error helper

    func getContentType(
        for origin: Origin,
        using id: String?
    ) throws(ContentResolverError) -> ContentType {
        do {
            return try contentTypeResolver.getContentType(
                for: origin,
                using: id
            )
        }
        catch {
            throw .contentType(error)
        }
    }

    // MARK: - conversion helpers

    func convert(
        property: Property,
        rawValue: AnyCodable?,
        forKey key: String,
        slug: String
    ) throws(ContentResolverError) -> AnyCodable? {
        let value = rawValue ?? property.defaultValue

        switch property.type {
        case let .date(config):
            guard
                let rawDateValue = value?.value(as: String.self)
            else {
                throw .invalidProperty(
                    key,
                    value?.stringValue() ?? "nil",
                    slug
                )
            }
            guard
                let date = dateFormatter.date(
                    from: rawDateValue,
                    using: config
                )
            else {
                throw .invalidProperty(
                    key,
                    value?.stringValue() ?? "nil",
                    slug
                )
            }
            return .init(date.timeIntervalSince1970)
        default:
            return value
        }
    }

    func convert(
        rawContent: RawContent
    ) throws(ContentResolverError) -> Content {
        let typeID = rawContent.markdown.frontMatter.string(
            SystemPropertyKeys.type.rawValue
        )

        let contentType = try getContentType(
            for: rawContent.origin,
            using: typeID
        )

        var properties: [String: AnyCodable] = [:]

        // validate properties
        let frontMatter = rawContent.markdown.frontMatter
        let missingProperties = contentType.properties
            .filter { name, property in
                let isRequiredButMissing =
                    property.required && frontMatter[name] == nil
                let hasNoDefaultValue = property.defaultValue?.value == nil
                let isNotSystemProperty = !SystemPropertyKeys.allCases
                    .map { $0.rawValue }
                    .contains(name)

                return isRequiredButMissing && hasNoDefaultValue
                    && isNotSystemProperty
            }

        for name in missingProperties.keys {
            throw .missingProperty(name, rawContent.origin.slug)
        }

        /// validate relations
        let missingRelations = contentType.relations.keys.filter {
            frontMatter[$0] == nil
        }

        for name in missingRelations {
            throw .missingRelation(name, rawContent.origin.slug)
        }

        // Extrant `id` from front matter or path or fallback to origin path
        var typeAwareID = rawContent.origin.path.getTypeAwareIdentifier()

        if let id = rawContent.markdown.frontMatter.string(
            SystemPropertyKeys.id.rawValue
        ) {
            typeAwareID = id
        }

        // Extract `slug` from front matter or fallback to origin slug
        var slug: String = rawContent.origin.slug
        if let rawSlug = rawContent.markdown.frontMatter.string(
            SystemPropertyKeys.slug.rawValue,
            allowingEmptyValue: true
        ) {
            guard rawSlug.containsOnlyValidURLCharacters() else {
                throw .invalidSlug(rawSlug)
            }
            slug = rawSlug  // .slugify()
        }

        // Convert schema-defined properties
        for (key, property) in contentType.properties.sorted(by: {
            $0.key < $1.key
        }) {
            var rawValue: AnyCodable?

            switch key {
            case SystemPropertyKeys.id.rawValue:
                rawValue = .init(typeAwareID)
            case SystemPropertyKeys.lastUpdate.rawValue:
                rawValue = .init(rawContent.lastModificationDate)
            case SystemPropertyKeys.slug.rawValue:
                rawValue = .init(slug)
            case SystemPropertyKeys.type.rawValue:
                rawValue = .init(typeID)
            default:
                rawValue = rawContent.markdown.frontMatter[key]
            }

            properties[key] = try convert(
                property: property,
                rawValue: rawValue,
                forKey: key,
                slug: rawContent.origin.slug
            )
        }

        // Convert schema-defined relations
        var relations: [String: RelationValue] = [:]
        for (key, relation) in contentType.relations.sorted(by: {
            $0.key < $1.key
        }) {
            let rawValue = rawContent.markdown.frontMatter[key]
            var identifiers: [String] = []

            switch relation.type {
            case .one:
                if let id = rawValue?.value as? String {
                    identifiers.append(id)
                }
            case .many:
                if let ids = rawValue?.value as? [String] {
                    identifiers.append(contentsOf: ids)
                }
            }

            relations[key] = .init(
                contentType: relation.references,
                type: relation.type,
                identifiers: identifiers
            )
        }

        // Filter out reserved keys and schema-mapped fields to extract user-defined fields
        let keysToRemove =
            SystemPropertyKeys.allCases
            .map { $0.rawValue }
            + contentType.properties.keys
            + contentType.relations.keys

        var userDefined = rawContent.markdown.frontMatter
        for key in keysToRemove {
            userDefined.removeValue(forKey: key)
        }

        logger.trace(
            "Converting content",
            metadata: [
                "type": .string(contentType.id),
                "typeAwareID": .string(typeAwareID),
                "slug": .string(slug),
                "origin": .dictionary(
                    [
                        "path": .string(rawContent.origin.path.value),
                        "slug": .string(rawContent.origin.slug),
                    ]
                ),
            ]
        )

        return .init(
            type: contentType,
            typeAwareID: typeAwareID,
            slug: .init(slug),
            rawValue: rawContent,
            properties: properties,
            relations: relations,
            userDefined: userDefined,
            iteratorInfo: nil
        )
    }

    // MARK: - filter

    /// Applies the filtering rules to the provided content items.
    ///
    /// - Parameters:
    ///   - filterRules: A dictionary mapping content type identifiers to filtering conditions.
    ///   - contents: The list of `Content` items to filter.
    ///   - now: The current timestamp used for time-based filtering.
    /// - Returns: A new list containing only the filtered content items.
    func apply(
        filterRules: [String: Condition],
        to contents: [Content],
        now: TimeInterval
    ) -> [Content] {
        let groups = Dictionary(grouping: contents, by: { $0.type.id })

        var result: [Content] = []
        for (id, contents) in groups {
            if let condition = filterRules[id] ?? filterRules["*"] {
                let items = contents.run(
                    query: .init(
                        contentType: id,
                        filter: condition
                    ),
                    now: now,
                    logger: logger
                )
                result.append(contentsOf: items)
            }
            else {
                result.append(contentsOf: contents)
            }
        }
        return result
    }

    // MARK: - iterators

    func apply(
        iterators: [String: Query],
        to contents: [Content],
        baseURL: String,
        now: TimeInterval
    ) -> [Content] {
        var finalContents: [Content] = []

        for content in contents {
            if let iteratorID = content.slug.extractIteratorID() {
                guard
                    let query = iterators[iteratorID]
                else {
                    continue
                }

                let countQuery = Query(
                    contentType: query.contentType,
                    scope: query.scope,
                    limit: nil,
                    offset: nil,
                    filter: query.filter,
                    orderBy: query.orderBy
                )

                let total =
                    contents.run(query: countQuery, now: now, logger: logger)
                    .count
                let limit = max(1, query.limit ?? 10)
                let numberOfPages = (total + limit - 1) / limit

                for i in 0..<numberOfPages {
                    let offset = i * limit
                    let currentPageIndex = i + 1

                    var alteredContent = content
                    rewrite(
                        iteratorID: iteratorID,
                        pageIndex: currentPageIndex,
                        &alteredContent.typeAwareID
                    )
                    rewrite(
                        iteratorID: iteratorID,
                        pageIndex: currentPageIndex,
                        &alteredContent.slug.value
                    )
                    rewrite(
                        number: currentPageIndex,
                        total: numberOfPages,
                        &alteredContent.properties
                    )
                    rewrite(
                        number: currentPageIndex,
                        total: numberOfPages,
                        &alteredContent.userDefined
                    )

                    if !alteredContent.rawValue.markdown.contents.isEmpty {
                        alteredContent.rawValue.markdown.contents = replace(
                            in: alteredContent.rawValue.markdown.contents,
                            number: currentPageIndex,
                            total: numberOfPages
                        )
                    }

                    let links = (0..<numberOfPages)
                        .map { i in
                            let pageIndex = i + 1
                            let permalink = content.slug.permalink(
                                baseURL: baseURL
                            )
                            return IteratorInfo.Link(
                                number: pageIndex,
                                permalink: permalink.replacing(
                                    ["{{\(iteratorID)}}": String(pageIndex)]
                                ),
                                isCurrent: pageIndex == currentPageIndex
                            )
                        }

                    let items = contents.run(
                        query: .init(
                            contentType: query.contentType,
                            limit: limit,
                            offset: offset,
                            filter: query.filter,
                            orderBy: query.orderBy
                        ),
                        now: now,
                        logger: logger
                    )

                    alteredContent.iteratorInfo = .init(
                        current: currentPageIndex,
                        total: numberOfPages,
                        limit: limit,
                        items: items,
                        links: links,
                        scope: query.scope
                    )

                    finalContents.append(alteredContent)
                }
            }
            else {
                finalContents.append(content)
            }
        }
        return finalContents
    }

    // MARK: - asset resolution

    func apply(
        assetProperties: [Pipeline.Assets.Property],
        to contents: [Content],
        contentsURL: URL,
        assetsPath: String,
        baseURL: String
    ) throws -> [Content] {
        var results: [Content] = []

        for content in contents {
            var item: Content = content

            for property in assetProperties {
                let path = item.rawValue.origin.path
                let url = contentsURL.appendingPathComponent(path.value)
                let assetsURL = url.appending(path: assetsPath)

                let filteredAssets = filterFilePaths(
                    from: content.rawValue.assets,
                    input: property.input
                )

                guard !filteredAssets.isEmpty else {
                    continue
                }

                let assetKeys =
                    filteredAssets.compactMap {
                        $0.split(separator: ".").first
                    }
                    .map(String.init)

                let resolvedAssets = filteredAssets.map {
                    "./\(assetsPath)/\($0)"
                        .resolveAsset(
                            baseURL: baseURL,
                            assetsPath: assetsPath,
                            slug: content.slug.value
                        )
                }

                let frontMatter = item.rawValue.markdown.frontMatter

                let finalAssets =
                    property.resolvePath ? resolvedAssets : filteredAssets

                switch property.action {
                case .add:
                    if let originalItems = frontMatter[property.property]?
                        .arrayValue(as: String.self)
                    {
                        item.properties[property.property] = .init(
                            originalItems + finalAssets
                        )
                    }
                    else {
                        item.properties[property.property] = .init(finalAssets)
                    }
                case .set:
                    if finalAssets.count == 1 {
                        let asset = finalAssets[0]
                        item.properties[property.property] = .init(asset)
                    }
                    else {
                        item.properties[property.property] = .init(
                            createDictionaryValues(
                                assetKeys: assetKeys,
                                array: finalAssets
                            )
                        )
                    }
                case .load:
                    if filteredAssets.count == 1 {
                        let asset = filteredAssets[0]
                        let url = assetsURL.appending(path: asset)
                        let contents = try String(
                            contentsOf: url,
                            encoding: .utf8
                        )
                        item.properties[property.property] = .init(contents)
                    }
                    else {
                        var values: [String: AnyCodable] = [:]
                        for i in 0..<filteredAssets.count {
                            let asset = filteredAssets[i]
                            let url = assetsURL.appending(path: asset)
                            let contents = try String(
                                contentsOf: url,
                                encoding: .utf8
                            )
                            values[assetKeys[i]] = .init(contents)
                        }
                        item.properties[property.property] = .init(values)
                    }
                // TODO: check extension, add json support
                case .parse:
                    if filteredAssets.count == 1 {
                        let asset = filteredAssets[0]
                        let url = assetsURL.appending(path: asset)
                        let data = try Data(contentsOf: url)
                        let yaml = try ToucanYAMLDecoder()
                            .decode(AnyCodable.self, from: data)
                        item.properties[property.property] = yaml
                    }
                    else {
                        var values: [String: AnyCodable] = [:]
                        for i in 0..<filteredAssets.count {
                            let asset = filteredAssets[i]
                            let url = assetsURL.appending(path: asset)
                            let data = try Data(contentsOf: url)
                            let yaml = try ToucanYAMLDecoder()
                                .decode(AnyCodable.self, from: data)
                            values[assetKeys[i]] = yaml
                        }
                        item.properties[property.property] = .init(values)
                    }
                }
            }
            results.append(item)
        }
        return results
    }

    func applyBehaviors(
        pipeline: Pipeline,
        to contents: [Content],
        contentsURL: URL,
        assetsPath: String
    ) throws -> [PipelineResult] {
        var results: [PipelineResult] = []

        for content in contents {
            var assetsReady: Set<String> = .init()

            for behavior in pipeline.assets.behaviors {
                let isAllowed = pipeline.contentTypes.isAllowed(
                    contentType: content.type.id
                )
                guard isAllowed else {
                    continue
                }
                let remainingAssets = Set(content.rawValue.assets)
                    .subtracting(assetsReady)

                let matchingRemainingAssets = filterFilePaths(
                    from: Array(remainingAssets),
                    input: behavior.input
                )

                guard !matchingRemainingAssets.isEmpty else {
                    continue
                }

                for inputAsset in matchingRemainingAssets {
                    let basePath = content.rawValue.origin.path

                    let sourcePath = [
                        basePath.value,
                        assetsPath,
                        inputAsset,
                    ]
                    .joined(separator: "/")

                    let file = getNameAndExtension(from: inputAsset)

                    let destPath = [
                        assetsPath,
                        content.slug.value,
                        inputAsset,
                    ]
                    .filter { !$0.isEmpty }
                    .joined(separator: "/")
                    .split(separator: "/")
                    .dropLast()
                    .joined(separator: "/")

                    logger.trace(
                        "Resolving matching asset behavior.",
                        metadata: [
                            "behavior": .string(behavior.id),
                            "source": .string(sourcePath),
                            "destination": .string(destPath),
                        ]
                    )

                    let fileURL = contentsURL.appending(path: sourcePath)

                    switch behavior.id {
                    case CompileSASSBehavior.id:
                        let script = try CompileSASSBehavior()
                        let css = try script.run(fileURL: fileURL)

                        // TODO: proper output management later on
                        results.append(
                            .init(
                                source: .asset(css),
                                destination: .init(
                                    path: destPath,
                                    file: behavior.output.name,
                                    ext: behavior.output.ext
                                )
                            )
                        )

                    case MinifyCSSBehavior.id:
                        let script = MinifyCSSBehavior()
                        let css = try script.run(fileURL: fileURL)

                        results.append(
                            .init(
                                source: .asset(css),
                                destination: .init(
                                    path: destPath,
                                    file: behavior.output.name,
                                    ext: behavior.output.ext
                                )
                            )
                        )

                    default:  // copy
                        results.append(
                            .init(
                                source: .assetFile(sourcePath),
                                destination: .init(
                                    path: destPath,
                                    file: file.name,
                                    ext: file.ext
                                )
                            )
                        )
                    }

                    assetsReady.insert(inputAsset)
                }
            }
        }

        return results
    }
}


================================================
FILE: Sources/ToucanSDK/Content/ContentTypeResolver.swift
================================================
//
//  ContentTypeResolver.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 30..
//

import ToucanCore
import ToucanSource

enum ContentTypeResolverError: ToucanError {
    case missingContentType(String, String)
    case unknown(Error)

    var underlyingErrors: [any Error] {
        switch self {
        case .missingContentType:
            []
        case let .unknown(error):
            [error]
        }
    }

    var logMessage: String {
        switch self {
        case let .missingContentType(id, path):
            "Missing content type for identifier: `\(id)` at `\(path)`."
        case let .unknown(error):
            error.localizedDescription
        }
    }

    var userFriendlyMessage: String {
        switch self {
        case .missingContentType:
            "Missing content type."
        case .unknown:
            "Unknown content conversion error."
        }
    }
}

struct ContentTypeResolver {

    let contentTypes: [ContentType]

    init(
        types: [ContentType],
        pipelines: [Pipeline]
    ) {
        let virtualTypes = pipelines.compactMap {
            $0.definesType ? ContentType(id: $0.id) : nil
        }

        self.contentTypes = (types + virtualTypes).sorted { $0.id < $1.id }
    }

    func getContentType(
        for origin: Origin,
        using id: String?
    ) throws(ContentTypeResolverError) -> ContentType {
        if let id {
            guard
                let result = contentTypes.first(where: { $0.id == id })
            else {
                throw .missingContentType(id, origin.path.value)
            }
            return result
        }

        if let type = contentTypes.first(
            where: { type in
                type.paths.contains { origin.path.value.hasPrefix($0) }
            }
        ) {
            return type
        }

        let results = contentTypes.filter(\.default)
        precondition(
            !results.isEmpty,
            "Don't forget to validate build target first."
        )
        return results[0]
    }
}


================================================
FILE: Sources/ToucanSDK/Content/IteratorInfo.swift
================================================
//
//  IteratorInfo.swift
//  Toucan
//
//  Created by gerp83 on 2025. 04. 17..
//

/// Provides pagination and iteration metadata for a content collection,
/// used when rendering paginated list views.
public struct IteratorInfo {
    /// Represents a navigation link within a paginated content sequence.
    public struct Link: Codable {
        /// The page number this link points to.
        public var number: Int

        /// The permalink URL for this page.
        public var permalink: String

        /// Whether this link refers to the currently active page.
        public var isCurrent: Bool

        /// Initializes a new pagination link.
        ///
        /// - Parameters:
        ///   - number: The page number.
        ///   - permalink: The URL for that page.
        ///   - isCurrent: Whether this link is for the current page.
        public init(
            number: Int,
            permalink: String,
            isCurrent: Bool
        ) {
            self.number = number
            self.permalink = permalink
            self.isCurrent = isCurrent
        }
    }

    /// The current page number (1-based).
    public var current: Int

    /// The total number of pages in the iterator.
    public var total: Int

    /// The number of items per page.
    public var limit: Int

    /// The subset of `Content` items that belong to the current page.
    public var items: [Content]

    /// A list of links to all available pages for UI navigation.
    public var links: [Link]

    /// An optional scope key used to identify the context or view this iterator belongs to.
    ///
    /// This can help differentiate between multiple iterators for the same content type
    /// (e.g., "allPosts", "featuredPosts").
    public var scope: String?

    /// Initializes a new iterator metadata structure.
    ///
    /// - Parameters:
    ///   - current: The current page number.
    ///   - total: The total number of pages.
    ///   - limit: Items per page.
    ///   - items: The content items for the current page.
    ///   - links: Pagination links to all pages.
    ///   - scope: An optional scope identifier.
    public init(
        current: Int,
        total: Int,
        limit: Int,
        items: [Content],
        links: [Link],
        scope: String?
    ) {
        self.current = current
        self.total = total
        self.limit = limit
        self.items = items
        self.links = links
        self.scope = scope
    }
}


================================================
FILE: Sources/ToucanSDK/Content/Query+Resolve.swift
================================================
//
//  Query+Resolve.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 16..
//

import ToucanSource

public extension Condition {
    /// Recursively resolves dynamic placeholders in the condition using a parameter map.
    ///
    /// Placeholders must be strings in the form `{{parameterKey}}` and will be
    /// replaced by values from the given parameters dictionary.
    ///
    /// - Parameter parameters: A dictionary of key-value pairs to substitute into the condition.
    /// - Returns: A new `Condition` with resolved values where applicable.
    func resolve(with parameters: [String: AnyCodable]) -> Self {
        switch self {
        case let .field(key, op, value):
            guard
                let stringValue = value.value(as: String.self),
                stringValue.count > 4,
                stringValue.hasPrefix("{{"),
                stringValue.hasSuffix("}}")
            else {
                return self
            }

            let paramKeyToUse = String(stringValue.dropFirst(2).dropLast(2))
            guard let newValue = parameters[paramKeyToUse] else {
                return self
            }

            return .field(key: key, operator: op, value: newValue)

        case let .and(conditions):
            return .and(conditions.map { $0.resolve(with: parameters) })

        case let .or(conditions):
            return .or(conditions.map { $0.resolve(with: parameters) })
        }
    }
}

public extension Query {
    /// Resolves dynamic filter parameters by injecting values into the filter condition tree.
    ///
    /// This is useful when filters include placeholders that need to be resolved at runtime.
    ///
    /// - Parameter parameters: A dictionary of key-value pairs to replace placeholders in the filter.
    /// - Returns: A new `Query` instance with resolved filter conditions.
    func resolveFilterParameters(
        with parameters: [String: AnyCodable]
    ) -> Self {
        .init(
            contentType: contentType,
            scope: scope,
            limit: limit,
            offset: offset,
            filter: filter?.resolve(with: parameters),
            orderBy: orderBy
        )
    }
}


================================================
FILE: Sources/ToucanSDK/Content/RelationValue.swift
================================================
//
//  RelationValue.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 01. 30..
//

import ToucanSource

/// Represents the resolved value of a relation in content, including the target content type,
/// the relation's cardinality, and the identifiers of related items.
public struct RelationValue {
    /// The type of content this relation points to (e.g., `"author"`, `"post"`, `"product"`).
    public var contentType: String

    /// The relation type indicating if it's a one-to-one or one-to-many relationship.
    public var type: RelationType

    /// A list of string identifiers for the related content items.
    /// For `.one`, this should typically contain a single ID; for `.many`, multiple.
    public var identifiers: [String]

    /// Initializes a new `RelationValue` representing the resolved target(s) of a content relation.
    ///
    /// - Parameters:
    ///   - contentType: The name of the target content type.
    ///   - type: The type of relation (single or multiple).
    ///   - identifiers: A list of string IDs pointing to related content.
    public init(
        contentType: String,
        type: RelationType,
        identifiers: [String]
    ) {
        self.contentType = contentType
        self.type = type
        self.identifiers = identifiers
    }
}


================================================
FILE: Sources/ToucanSDK/DateFormats/DateContext.swift
================================================
//
//  DateContext.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 12..
//

/// A configuration container for date, time, and custom date-time formatting patterns.
///
/// `DateFormats` includes predefined formatting levels (full, long, medium, short)
/// for both dates and times, as well as support for arbitrary format labels.
public struct DateContext: Codable {

    /// Represents standardized formatting levels for a date or time value.
    ///
    /// These levels mirror common locale-aware date style options.
    public struct Standard: Codable {
        /// A fully verbose date format (e.g., `"EEEE, MMMM d, yyyy"`).
        public var full: String

        /// A long-form date format (e.g., `"MMMM d, yyyy"`).
        public var long: String

        /// A medium-form date format (e.g., `"MMM d, yyyy"`).
        public var medium: String

        /// A short-form date format (e.g., `"M/d/yy"`).
        public var short: String

        /// Initializes a new `Standard` date format set.
        ///
        /// - Parameters:
        ///   - full: Full verbose date format string.
        ///   - long: Long format string.
        ///   - medium: Medium format string.
        ///   - short: Short format string.
        public init(
            full: String,
            long: String,
            medium: String,
            short: String
        ) {
            self.full = full
            self.long = long
            self.medium = medium
            self.short = short
        }
    }

    /// Standardized date format strings (e.g., full, medium, short).
    public var date: Standard

    /// Standardized time format strings (e.g., full, medium, short).
    public var time: Standard

    /// A standard iso8601 date string.
    public var iso8601: String

    /// A Unix timestamp representing a default or reference point in time.
    public var timestamp: Double

    /// Additional named date format strings keyed by label.
    ///
    /// These can be used for custom formatting beyond the standard levels.
    public var formats: [String: String]

    /// Initializes a `DateFormats` configuration.
    ///
    /// - Parameters:
    ///   - date: Standardized date formatting options.
    ///   - time: Standardized time formatting options.
    ///   - timestamp: A base or reference timestamp, typically in Unix format.
    ///   - iso8601: A standard iso8601 date string.
    ///   - formats: Custom named format strings for specialized use cases.
    public init(
        date: Standard,
        time: Standard,
        timestamp: Double,
        iso8601: String,
        formats: [String: String]
    ) {
        self.date = date
        self.time = time
        self.timestamp = timestamp
        self.iso8601 = iso8601
        self.formats = formats
    }
}


================================================
FILE: Sources/ToucanSDK/DateFormats/ToucanDateFormatters.swift
================================================
//
//  ToucanDateFormatters.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 05. 26..
//

import Foundation
import Logging
import ToucanCore
import ToucanSource

/// ```
/// target:
///     dev:
///        input: ./src
///        output: ./dist
///        config: ./src/config.dev.yml => auto lookup like this?
///    -> default looks up for config.yml
///
///     live:
///        config: ./src/config.live.yml
///
///    config.dev.yml:
///        url: http://localhost:3000/
///
///        # output date formats basis
///
///        date:
///           input:
///              # input date formats basis
///              locale: en-US
///              timezone: Americas/Los_Angeles
///              format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
///           output:
///              locale: en-US
///              timezone: Americas/Los_Angeles
///           formats:
///              year:
///                 format: "y"
///                 locale: hu-HU
///                 timezone: Europe/Budapest
///
///     pipeline -> overrides config completely
///        date:
///            input:
///                locale: ???
///                timezone: ???
///                format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
///            output:
///                locale: en-US
///                timezone: Americas/Los_Angeles
///            formats:
///               year:
///                 format: "y"
///                 locale: ???
///                 timezone: ???
///
///    # content type
///        post
///            publication:
///                type: date
///                config: # input
///                    format:
///                    locale:
///                    timeZone:
/// ```

/// Extension to configure `DateFormatter` with localization and config options.
private extension DateFormatter {
    /// Creates and configures a `DateFormatter`.
    ///
    /// - Parameters:
    ///   - localization: The locale and time zone settings to apply.
    ///   - block: A closure to further configure the formatter.
    /// - Returns: A fully configured `DateFormatter`.
    static func build(
        using localization: DateLocalization = .defaults,
        _ block: (inout DateFormatter) -> Void
    ) -> DateFormatter {
        var formatter = DateFormatter()
        formatter.use(localization: localization)
        formatter.dateStyle = .none
        formatter.timeStyle = .none
        block(&formatter)
        return formatter
    }

    /// Applies the given localization (locale and time zone) to the formatter.
    ///
    /// - Parameter localization: The locale and time zone options.
    func use(localization: DateLocalization) {
        let id = Locale.identifier(.icu, from: localization.locale)
        locale = .init(identifier: id)
        timeZone = .init(identifier: localization.timeZone)
    }

    /// Applies a `DateFormatterConfig` (format, locale, time zone) to the formatter.
    ///
    /// - Parameter config: The date formatter configuration.
    func use(config: DateFormatterConfig) {
        use(localization: config.localization)
        dateFormat = config.format
    }
}

/// Holds system date and time style `DateFormatter` instances and an ISO8601 formatter.
private struct SystemDateFormatters {

    struct Date {
        var full: DateFormatter
        var long: DateFormatter
        var medium: DateFormatter
        var short: DateFormatter
    }

    struct Time {
        var full: DateFormatter
        var long: DateFormatter
        var medium: DateFormatter
        var short: DateFormatter
    }

    var date: Date
    var time: Time
    var iso8601: DateFormatter
}

/// Main utility for parsing and formatting dates based on project configuration.
///
/// Combines input parsing, system-style formatters, and user-defined formats.
public struct ToucanInputDateFormatter {

    private var dateConfig: Config.DataTypes.Date
    private var inputFormatter: DateFormatter
    private var ephemeralFormatter: DateFormatter

    var logger: Logger

    /// Initializes the date formatter utility.
    ///
    /// - Parameters:
    ///   - dateConfig: The base date configuration from the project.
    ///   - logger: A logger instance for diagnostics.
    public init(
        dateConfig: Config.DataTypes.Date,
        logger: Logger = .subsystem("input-date-formatter")
    ) {
        self.dateConfig = dateConfig
        self.inputFormatter = .build { $0.use(config: dateConfig.input) }
        self.ephemeralFormatter = .build { $0.use(config: dateConfig.input) }
        self.logger = logger
    }

    /// Parses a date string into a `Date` object.
    ///
    /// - Parameters:
    ///   - string: The string representation of the date.
    ///   - config: Optional `DateFormatterConfig` to override the input format.
    /// - Returns: A `Date` if parsing succeeds, otherwise `nil`.
    public func date(
        from string: String,
        using config: DateFormatterConfig? = nil
    ) -> Date? {
        if let config {
            ephemeralFormatter.use(config: config)

            return ephemeralFormatter.date(from: string)
        }
        return inputFormatter.date(from: string)
    }

    /// Converts a date into a `String` object.
    ///
    /// - Parameters:
    ///   - date: The date representation.
    ///   - config: Optional `DateFormatterConfig` to override the input format.
    /// - Returns: A `String` using the provided date format config.
    public func string(
        from date: Date,
        using config: DateFormatterConfig? = nil
    ) -> String {
        if let config {
            ephemeralFormatter.use(config: config)

            return ephemeralFormatter.string(from: date)
        }
        return inputFormatter.string(from: date)
    }
}

/// Main utility for parsing and formatting dates based on project configuration.
///
/// Combines input parsing, system-style formatters, and user-defined formats.
public struct ToucanOutputDateFormatter {

    private var dateConfig: Config.DataTypes.Date
    private var pipelineDateConfig: Pipeline.DataTypes.Date?
    private var systemFormatters: SystemDateFormatters
    private var userFormatters: [String: DateFormatter]

    var logger: Logger

    /// Initializes the date formatter utility.
    ///
    /// - Parameters:
    ///   - dateConfig: The base date configuration from the project.
    ///   - pipelineDateConfig: Optional overrides for date configuration.
    ///   - logger: A logger instance for diagnostics.
    public init(
        dateConfig: Config.DataTypes.Date,
        pipelineDateConfig: Pipeline.DataTypes.Date? = nil,
        logger: Logger = .subsystem("date-formatter")
    ) {
        self.dateConfig = dateConfig
        self.pipelineDateConfig = pipelineDateConfig

        var localization = dateConfig.output
        if let outputLocalization = pipelineDateConfig?.output {
            localization = outputLocalization
        }

        self.systemFormatters = .init(
            date: .init(
                full: .build(using: localization) { $0.dateStyle = .full },
                long: .build(using: localization) { $0.dateStyle = .long },
                medium: .build(using: localization) { $0.dateStyle = .medium },
                short: .build(using: localization) { $0.dateStyle = .short }
            ),
            time: .init(
                full: .build(using: localization) { $0.timeStyle = .full },
                long: .build(using: localization) { $0.timeStyle = .long },
                medium: .build(using: localization) { $0.timeStyle = .medium },
                short: .build(using: localization) { $0.timeStyle = .short }
            ),
            iso8601: .build(using: localization) {
                $0.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
            }
        )
        self.userFormatters = [:]
        self.logger = logger

        let userFormatterConfig = dateConfig.formats.merging(
            (pipelineDateConfig?.formats ?? [:]),
            uniquingKeysWith: { _, new in new }
        )
        for (key, config) in userFormatterConfig {
            userFormatters[key] = .build(using: localization) {
                $0.use(config: config)
            }
        }
    }

    /// Formats a `Date` into a `DateContext`, providing multiple style outputs and custom formats.
    ///
    /// - Parameter date: The `Date` to format.
    /// - Returns: A `DateContext` containing formatted strings and timestamp.
    public func format(
        _ date: Date
    ) -> DateContext {
        .init(
            date: .init(
                full: systemFormatters.date.full.string(from: date),
                long: systemFormatters.date.long.string(from: date),
                medium: systemFormatters.date.medium.string(from: date),
                short: systemFormatters.date.short.string(from: date)
            ),
            time: .init(
                full: systemFormatters.time.full.string(from: date),
                long: systemFormatters.time.long.string(from: date),
                medium: systemFormatters.time.medium.string(from: date),
                short: systemFormatters.time.short.string(from: date)
            ),
            timestamp: date.timeIntervalSince1970,
            iso8601: systemFormatters.iso8601.string(from: date),
            formats: userFormatters.mapValues { $0.string(from: date) }
        )
    }

    /// Formats a time interval since 1970 into a `DateContext`.
    ///
    /// - Parameter timestamp: The time interval (seconds since 1970).
    /// - Returns: A `DateContext` with formatted outputs.
    public func format(
        _ timestamp: TimeInterval
    ) -> DateContext {
        format(.init(timeIntervalSince1970: timestamp))
    }
}


================================================
FILE: Sources/ToucanSDK/Models/ContextBundle.swift
================================================
//
//  ContextBundle.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 21..
//

import ToucanSource

/// A bundle containing a single content item, its rendering context, and its destination metadata.
///
/// `ContextBundle` is typically used as an input for template rendering or output generation,
/// combining the actual content with any supplemental data required for processing.
public struct ContextBundle {
    /// The primary content item to be rendered or processed.
    public var content: Content

    /// A key-value store representing the extended rendering context (e.g., metadata, global variables).
    /// These values can be used during template evaluation or logic processing.
    public var context: [String: AnyCodable]

    /// The intended destination of the output generated from this bundle.
    public var destination: Destination

    /// Initializes a new `ContextBundle` with content, context data, and a destination.
    ///
    /// - Parameters:
    ///   - content: The `Content` instance to render.
    ///   - context: A context dictionary providing additional rendering metadata or variables.
    ///   - destination: Where the rendered output should be saved.
    public init(
        content: Content,
        context: [String: AnyCodable],
        destination: Destination
    ) {
        self.content = content
        self.context = context
        self.destination = destination
    }
}


================================================
FILE: Sources/ToucanSDK/Models/Destination.swift
================================================
//
//  Destination.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 21..
//

/// Represents the destination location and filename for rendered or transformed content.
public struct Destination: Sendable {
    /// The relative or absolute path to the target directory where the file should be placed.
    public var path: String

    /// The base name of the file (without extension).
    public var file: String

    /// The file extension (e.g., "html", "json", "md").
    public var ext: String

    /// Initializes a new `Destination` describing where and how a file should be written.
    ///
    /// - Parameters:
    ///   - path: The directory path to write the file to.
    ///   - file: The base file name (without extension).
    ///   - ext: The file extension (e.g., `"html"`, `"json"`).
    public init(
        path: String,
        file: String,
        ext: String
    ) {
        self.path = path
        self.file = file
        self.ext = ext
    }
}


================================================
FILE: Sources/ToucanSDK/Models/PipelineResult.swift
================================================
//
//  PipelineResult.swift
//  Toucan
//
//  Created by Tibor Bödecs on 2025. 02. 21..
//

/// Represents the output of a content transformation pipeline, including the
/// transformed content and its intended destination.
public struct PipelineResult: Sendable {
    /// The source material for the pipeline result.
    public enum Source: Sendable {
        /// An asset source, that needs top be copied.
        case assetFile(String)
        /// A generated asset source
        case asset(String)
        /// The final transformed content (e.g., HTML, Markdown, etc.).
        case content(String)

        /// A Boolean value indicating whether the pipeline result's source is content-based.
        /// Returns `true` if the source is `.content`, otherwise `false`.
        public var isContent: Bool {
            !isAsset
        }

        /// A Boolean value indicating whether the pipeline result's source is an asset.
        /// Returns `true` if the source is `.asset`, otherwise `false`.
        public var isAsset: Bool {
            switch self {
            case .content:
                false
            case .assetFile:
                true
            case .asset:
                true
            }
        }
    }

    /// The source material.
    public var source: Source

    /// The destination metadata describing where or how the content should be output.
    public var destination: Destination

    /// Initializes a new `PipelineResult` with transformed content and a destination.
    ///
    /// - Parameters:
    ///   - source: The source material.
    ///   - destination: A `Destination` indicating where the result should be saved or rendered.
    public init(
        source: Source,
        destination: Destination
    ) {
        self.source = source
        self.destination = destination
    }
}


================================================
FILE: Sources/ToucanSDK/Models/Slug.swift
================================================
//
//  Slug.swift
//  Toucan
//
//  Created by gerp83 on 2025. 04. 17..
//

/// A value type representing a URL-friendly identifier for a content item.
public struct Slug: Equatable {

    /// The raw slug string (e.g., `"blog/welcome"`, `"about"`, `""`).
    public var value: String

    /// Initializes a new slug.
    ///
    /// - Parameter value: The raw slug string.
    public init(
        _ value: String
    ) {
        self.value = value
    }

    /// Extracts a dynamic iterator identifier from a slug value containing
    /// a templated range (e.g., `"blog/{{page}}"` → `"page"`).
    ///
    /// - Returns: The identifier inside `{{...}}`, or `nil` if not found.
    public func extractIteratorID() -> String? {
        guard
            let startRange = value.range(of: "{{"),
            let endRange = value.range(
                of: "}}",
                range: startRange.upperBound..<value.endIndex
            )
        else {
            return nil
        }
        return .init(value[startRange.upperBound..<endRange.lowerBound])
    }

    /// Constructs a permalink from the base URL and the slug.
    ///
    /// - Parameter baseURL: The base URL of the site (e.g., `"https://example.com"`).
    /// - Returns: A fully-qualified permalink string (e.g., `"https://example.com/blog/"`).
    public func permalink(baseURL: String) -> String {
        let components = value.split(separator: "/").map(String.init)
        if components.isEmpty {
            return baseURL.ensureTrailingSlash()
        }
        if components.last?.split(separator: ".").count ?? 0 > 1 {
            // If last segment has a file extension, return without trailing slash
            return ([baseURL] + components).joined(separator: "/")
        }
        return ([baseURL] + components)
            .joined(separator: "/")
            .ensureTrailingSlash()
    }
}

extension Slug: Codable {

    /// Generates a context-aware identifier string based on the last path component of a value.
    ///
    public func contextAwareIdentifier() -> String {
        .init(value.split(separator: "/").last ?? "")
    }

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer attempts to decode the value as a single string.
    ///
    /// - Parameter decoder: The decoder to read data from.
    /// - Throws: An error if reading from the decoder fails, or if the data is not a single string.
    public init(
        from decoder: Decoder
    ) throws {
        let container = try decoder.singleValueContainer()
        self.value = try container.decode(String.self)
    }

    /// Encodes this value into the given encoder.
    ///
    /// This method encodes the value as a single string.
    ///
    /// - Parameter encoder: The encoder to write data to.
    /// - Throws: An error if encoding fails.
    public func encode(
        to encoder: Encoder
    ) throws {
        var container = encoder.singleValueContainer()
        try container.encode(value)
    }
}


================================================
FILE: Sources/ToucanSDK/Outputs/ContextBundleToHTMLRenderer.swift
================================================
//
//  ContextBundleToHTMLRenderer.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2025. 05. 13..
//

import Foundation
import Logging
import ToucanSource

struct ContextBundleToHTMLRenderer {

    let mustacheRenderer: MustacheRenderer
    let engineContentTypesOptions: [String: AnyCodable]
    let pipelineViewKey: String
    let logger: Logger

    init(
        pipeline: Pipeline,
        templates: [String: String],
        logger: Logger = .subsystem("context-bundle-to-html-renderer")
    ) throws {
        self.mustacheRenderer = try MustacheRenderer(
            templates: templates.mapValues {
                try .init(string: $0)
            }
        )

        let engineOptions = pipeline.engine.options
        self.engineContentTypesOptions = engineOptions.dict("contentTypes")
        self.pipelineViewKey = [
            ViewFrontMatterKeys.views.rawValue, pipeline.id,
        ]
        .joined(separator: ".")
        self.logger = logger
    }

    func render(
        _ contextBundles: [ContextBundle]
    ) -> [PipelineResult] {
        contextBundles.compactMap { render($0) }
    }

    func render(
        _ contextBundle: ContextBundle
    ) -> PipelineResult? {
        let contentTypeOptions = engineContentTypesOptions.dict(
            contextBundle.content.type.id
        )
        let frontMatter = contextBundle.content.rawValue.markdown.frontMatter
        let contentTypeView = contentTypeOptions.string(
            ViewFrontMatterKeys.view.rawValue
        )
        let genericContentView = frontMatter.string(
            ViewFrontMatterKeys.any.rawValue
        )
        let contentView = frontMatter.string(pipelineViewKey)
        let viewId = contentView ?? genericContentView ?? contentTypeView

        guard let viewId, !viewId.isEmpty else {
            logger.warning(
                "No view has been specified for this content.",
                metadata: [
                    "slug": "\(contextBundle.content.slug.value)",
                    "type": "\(contextBundle.content.type.id)",
                ]
            )
            return nil
        }

        let html = mustacheRenderer.render(
            using: viewId,
            with: contextBundle.context
        )

        guard let html else {
            logger.warning(
                "Could not get valid HTML from content using view.",
                metadata: [
                    "slug": .string(contextBundle.content.slug.value),
                    "type": .string(contextBundle.content.type.id),
                    "view": .string(viewId),
                ]
            )
            return nil
        }

        return .init(
            source: .content(html),
            destination: contextBundle.destination
        )
    }
}


================================================
FILE: Sources/ToucanSDK/Outputs/ContextBundleToJSONRenderer.swift
================================================
//
//  ContextBundleToJSONRenderer.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2025. 05. 13..
//

import Foundation
import Logging
import ToucanSource

struct ContextBundleToJSONRenderer {
    let pipeline: Pipeline
    let encoder: JSONEncoder

    let logger: Logger

    let keyPath: String?
    let keyPaths: [String: AnyCodable]?

    init(
        pipeline: Pipeline,
        logger: Logger = .subsystem("context-bundle-to-json-renderer")
    ) {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [
            .prettyPrinted,
            .withoutEscapingSlashes,
            .sortedKeys,
        ]

        self.pipeline = pipeline
        self.encoder = encoder
        self.logger = logger

        self.keyPath = pipeline.engine.options.string("keyPath")
        self.keyPaths = pipeline.engine.options.value(
            "keyPaths",
            as: [String: AnyCodable].self
        )
    }

    private func data(
        from context: [String: Any],
        at keyPath: String?,
        using encoder: JSONEncoder
    ) throws -> Data? {
        guard let keyPath else {
            return nil
        }

        if let value = context.value(forKeyPath: keyPath) {
            return try encoder.encode(AnyCodable(value))
        }

        return nil
    }

    private func data(
        from context: [String: Any],
        keyPaths: [String: AnyCodable]?,
        using encoder: JSONEncoder
    ) throws -> Data? {
        var result: [String: AnyCodable] = [:]

        guard let keyPaths else {
            return nil
        }

        for (keyPath, value) in keyPaths {
            guard let newKeyPath = value.value(as: String.self) else {
                continue
            }

            if let value = context.value(forKeyPath: keyPath) {
                result[newKeyPath] = .init(value)
            }
        }

        return try encoder.encode(result)
    }

    func render(_ contextBundles: [ContextBundle]) -> [PipelineResult] {
        contextBundles.compactMap {
            render($0)
        }
    }

    func render(_ contextBundle: ContextBundle) -> PipelineResult? {
        let metadata: Logger.Metadata = [
            "slug": "\(contextBundle.content.slug.value)"
        ]

        let context = contextBundle.context
        let unboxedContext = context.unboxed(encoder)

        let encodedData = firstSucceeding([
            {
                try data(
                    from: unboxedContext,
                    keyPaths: keyPaths,
                    using: encoder
                )
            },
            { try data(from: unboxedContext, at: keyPath, using: encoder) },
            { try encoder.encode(context) },
        ])

        guard let encodedData else {
            logger.warning(
                "Could not encode context data as JSON object.",
                metadata: metadata
            )
            return nil
        }

        let json = String(data: encodedData, encoding: .utf8)

        guard let json else {
            logger.warning(
                "Could not encode context data as JSON output.",
                metadata: metadata
            )
            return nil
        }
        return .init(
            source: .content(json),
            destination: contextBundle.destination
        )
    }
}


================================================
FILE: Sources/ToucanSDK/Renderers/BuildTargetSourceRenderer.swift
================================================
//
//  BuildTargetSourceRenderer.swift
//  Toucan
//
//  Created by Viasz-Kádi Ferenc on 2025. 03. 25..
//

import Foundation
import Logging
import ToucanCore
import ToucanMarkdown
import ToucanSerialization
import ToucanSource

enum BuildTargetSourceRendererError: ToucanError {
    case invalidEngine(String)
    case unknown(Error)

    var underlyingErrors: [any Error] {
        switch self {
        case let .unknown(error):
            [error]
        default:
            []
        }
    }

    var logMessage: String {
        switch self {
        case let .invalidEngine(engine):
            "Invalid engine: `\(engine)`."
        case let .unknown(error):
            error.localizedDescription
        }
    }

    var userFriendlyMessage: String {
        switch self {
        case let .invalidEngine(engine):
            "Invalid engine: `\(engine)`."
        case .unknown:
            "Unknown source validator error."
        }
    }
}

/// Responsible for rendering the entire site bundle based on the `BuildTargetSource` configuration.
///
/// It processes content pipelines using the configured engine (Mustache, JSON, etc.),
/// resolves content and site-level context, and outputs rendered content using templates
/// or encoded formats.
public struct BuildTargetSourceRenderer {
    /// Site configuration + all raw content
    let buildTargetSource: BuildTargetSource
    /// Generator metadata (e.g., version, name)
    let generatorInfo: GeneratorInfo
    /// Logger for warnings and errors
    let logger: Logger
    /// Cache
    var contentContextCache: [String: [String: AnyCodable]] = [:]

    /// Initializes a renderer from a source bundle.
    ///
    /// - Parameters:
    ///   - buildTargetSource: The structured bundle containing settings, pipelines, and contents.
    ///   - generatorInfo: Info about the content generator (defaults to `.current`).
    ///   - logger: Logger for reporting issues or metrics.
    public init(
        buildTargetSource: BuildTargetSource,
        generatorInfo: GeneratorInfo = .current,
        logger: Logger = .subsystem("build-target-source-renderer")
    ) {
        self.buildTargetSource = buildTargetSource
        self.generatorInfo = generatorInfo
        self.logger = logger
    }

    /// Returns the last content update based on the pipeline config
    private func getLastContentUpdate(
        contents: [Content],
        pipeline: Pipeline,
        now: TimeInterval
    ) -> TimeInterval? {
        var updateTypes = Set(contents.map(\.type.id))
        if !pipeline.contentTypes.lastUpdate.isEmpty {
            updateTypes = updateTypes.filter {
                pipeline.contentTypes.lastUpdate.contains($0)
            }
        }
        let lastUpdate =
            updateTypes.compactMap {
                let items = contents.run(
                    query: .init(
                        contentType: $0,
                        scope: nil,
                        limit: 1,
                        orderBy: [
                            .init(
                                key: SystemPropertyKeys.lastUpdate.rawValue,
                                direction: .desc
                            )
                        ]
                    ),
                    now: now,
                    logger: logger
                )
                return items.first?.rawValue.lastModificationDate
            }
            .sorted(by: >).first

        logger.trace(
            "Last update for the pipeline.",
            metadata: [
                "pipeline": .string(pipeline.id),
                "lastUpdate": .string(
                    Date(timeIntervalSince1970: lastUpdate ?? 0).description
                ),
            ]
        )

        return lastUpdate
    }

    private func baseURL() -> String {
        buildTargetSource.target.url.dropTrailingSlash()
    }

    /// Returns the renderable context bundle for each content for a given pipeline using the global context
    m
Download .txt
gitextract__gpz1oly/

├── .github/
│   └── workflows/
│       ├── actions.yml
│       ├── linux.yml
│       ├── macos.yml
│       ├── pr-for-formula.yml
│       └── tag_actions.yml
├── .gitignore
├── .swift-format
├── .swiftformat
├── .swiftformatignore
├── .swiftheaderignore
├── Docker/
│   ├── Dockerfile
│   └── Dockerfile.testing
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   ├── ToucanCore/
│   │   ├── Extensions/
│   │   │   ├── Dictionary+Extensions.swift
│   │   │   ├── Logging+Extensions.swift
│   │   │   ├── String+Extensions.swift
│   │   │   └── URL+Extensions.swift
│   │   ├── GeneratorInfo.swift
│   │   ├── Logger.swift
│   │   └── ToucanError.swift
│   ├── ToucanMarkdown/
│   │   ├── Markdown/
│   │   │   ├── HTML.swift
│   │   │   ├── HTMLVisitor.swift
│   │   │   ├── MarkdownBlockDirective.swift
│   │   │   └── MarkdownToHTMLRenderer.swift
│   │   ├── MarkdownRenderer.swift
│   │   ├── Outline/
│   │   │   ├── Outline.swift
│   │   │   └── OutlineParser.swift
│   │   ├── ReadingTime/
│   │   │   └── ReadingTimeCalculator.swift
│   │   └── Transformers/
│   │       ├── ContentTransformer.swift
│   │       ├── TransformerExecutor.swift
│   │       └── TransformerPipeline.swift
│   ├── ToucanSDK/
│   │   ├── Behaviors/
│   │   │   ├── Behavior.swift
│   │   │   ├── CompileSASSBehavior.swift
│   │   │   └── MinifyCSSBehavior.swift
│   │   ├── Content/
│   │   │   ├── Content+Query.swift
│   │   │   ├── Content.swift
│   │   │   ├── ContentResolver.swift
│   │   │   ├── ContentTypeResolver.swift
│   │   │   ├── IteratorInfo.swift
│   │   │   ├── Query+Resolve.swift
│   │   │   └── RelationValue.swift
│   │   ├── DateFormats/
│   │   │   ├── DateContext.swift
│   │   │   └── ToucanDateFormatters.swift
│   │   ├── Models/
│   │   │   ├── ContextBundle.swift
│   │   │   ├── Destination.swift
│   │   │   ├── PipelineResult.swift
│   │   │   └── Slug.swift
│   │   ├── Outputs/
│   │   │   ├── ContextBundleToHTMLRenderer.swift
│   │   │   └── ContextBundleToJSONRenderer.swift
│   │   ├── Renderers/
│   │   │   ├── BuildTargetSourceRenderer.swift
│   │   │   └── MustacheRenderer.swift
│   │   ├── Toucan.swift
│   │   ├── Utilities/
│   │   │   ├── Any+AnyCodable.swift
│   │   │   ├── AnyCodable+Json.swift
│   │   │   ├── Array+AnyCodable.swift
│   │   │   ├── ContextKeys.swift
│   │   │   ├── CopyManager.swift
│   │   │   ├── Dictionary+AnyCodable.swift
│   │   │   ├── Encodable+Json.swift
│   │   │   └── FirstSucceeding.swift
│   │   └── Validators/
│   │       ├── BuildTargetSourceValidator.swift
│   │       └── TemplateValidator.swift
│   ├── ToucanSerialization/
│   │   ├── ToucanDecoder.swift
│   │   ├── ToucanDecoderError.swift
│   │   ├── ToucanEncoder.swift
│   │   ├── ToucanEncoderError.swift
│   │   ├── ToucanJSONDecoder.swift
│   │   ├── ToucanJSONEncoder.swift
│   │   ├── ToucanYAMLDecoder.swift
│   │   └── ToucanYAMLEncoder.swift
│   ├── ToucanSource/
│   │   ├── Errors/
│   │   │   ├── ObjectLoaderError.swift
│   │   │   ├── SourceLoaderError.swift
│   │   │   └── TemplateLoaderError.swift
│   │   ├── Extensions/
│   │   │   ├── Decoder+Validate.swift
│   │   │   ├── Dictionary+AnyCodable.swift
│   │   │   └── FileManagerKit+Extensions.swift
│   │   ├── Loaders/
│   │   │   ├── BuildTargetSourceLoader.swift
│   │   │   ├── ObjectLoader.swift
│   │   │   ├── RawContentLoader.swift
│   │   │   └── TemplateLoader.swift
│   │   ├── MarkdownParser.swift
│   │   ├── Models/
│   │   │   ├── BuildTargetSource.swift
│   │   │   ├── BuiltTargetSourceLocations.swift
│   │   │   ├── Markdown.swift
│   │   │   ├── Origin.swift
│   │   │   ├── Path.swift
│   │   │   ├── RawContent.swift
│   │   │   ├── Template.swift
│   │   │   └── View.swift
│   │   └── Objects/
│   │       ├── AnyCodable.swift
│   │       ├── Blocks/
│   │       │   ├── Block+Attribute.swift
│   │       │   ├── Block+Parameter.swift
│   │       │   └── Block.swift
│   │       ├── Config/
│   │       │   ├── Config+Blocks.swift
│   │       │   ├── Config+Contents.swift
│   │       │   ├── Config+DataTypes+Date.swift
│   │       │   ├── Config+DataTypes.swift
│   │       │   ├── Config+Location.swift
│   │       │   ├── Config+Pipelines.swift
│   │       │   ├── Config+Renderer+ParagraphStyles.swift
│   │       │   ├── Config+RendererConfig.swift
│   │       │   ├── Config+Site.swift
│   │       │   ├── Config+Templates.swift
│   │       │   ├── Config+Types.swift
│   │       │   └── Config.swift
│   │       ├── Date/
│   │       │   ├── DateFormatterConfig.swift
│   │       │   └── DateLocalization.swift
│   │       ├── Pipeline/
│   │       │   ├── Pipeline+Assets.swift
│   │       │   ├── Pipeline+ContentTypes.swift
│   │       │   ├── Pipeline+DataTypes+Date.swift
│   │       │   ├── Pipeline+DataTypes.swift
│   │       │   ├── Pipeline+Engine.swift
│   │       │   ├── Pipeline+Output.swift
│   │       │   ├── Pipeline+Scope+Context.swift
│   │       │   ├── Pipeline+Scope.swift
│   │       │   ├── Pipeline+Transformers+Transformer.swift
│   │       │   ├── Pipeline+Transformers.swift
│   │       │   └── Pipeline.swift
│   │       ├── Property/
│   │       │   ├── Property.swift
│   │       │   ├── PropertyType.swift
│   │       │   └── SystemPropertyKeys.swift
│   │       ├── Query/
│   │       │   ├── Condition.swift
│   │       │   ├── Direction.swift
│   │       │   ├── Operator.swift
│   │       │   ├── Order.swift
│   │       │   └── Query.swift
│   │       ├── Relation/
│   │       │   ├── Relation.swift
│   │       │   └── RelationType.swift
│   │       ├── Settings/
│   │       │   └── Settings.swift
│   │       ├── Target/
│   │       │   ├── Target.swift
│   │       │   └── TargetConfig.swift
│   │       └── Types/
│   │           └── ContentType.swift
│   ├── _GitCommitHash/
│   │   ├── git_commit_hash.c
│   │   └── include/
│   │       └── git_commit_hash.h
│   ├── toucan/
│   │   └── Entrypoint.swift
│   ├── toucan-generate/
│   │   └── Entrypoint.swift
│   ├── toucan-init/
│   │   ├── Download.swift
│   │   └── Entrypoint.swift
│   ├── toucan-serve/
│   │   ├── Entrypoint.swift
│   │   └── NotFoundMiddleware.swift
│   └── toucan-watch/
│       └── Entrypoint.swift
├── Tests/
│   ├── ToucanCoreTests/
│   │   ├── Extensions/
│   │   │   ├── StringExtensionsTestSuite.swift
│   │   │   └── URLExtensionsTestSuite.swift
│   │   └── ToucanCoreTestSuite.swift
│   ├── ToucanMarkdownTests/
│   │   ├── ContentRendererTestSuite.swift
│   │   ├── HTMLVisitorTestSuite.swift
│   │   ├── MarkdownBlockDirective+Mock.swift
│   │   ├── MarkdownBlockDirectiveTestSuite.swift
│   │   └── OutlineTestSuite.swift
│   ├── ToucanSDKTests/
│   │   ├── BuildTargetSource/
│   │   │   ├── BuildTargetSourceRendererTestSuite.swift
│   │   │   └── BuildTargetSourceValidatorTestSuite.swift
│   │   ├── Content/
│   │   │   ├── ContentQueryTestSuite.swift
│   │   │   └── ContentResolverTestSuite.swift
│   │   ├── DateFormatter/
│   │   │   └── ToucanDateFormatterTestSuite.swift
│   │   ├── E2ETestSuite.swift
│   │   ├── Files/
│   │   │   ├── MarkdownFile.swift
│   │   │   ├── MustacheFile.swift
│   │   │   ├── RawContentBundle.swift
│   │   │   └── YAMLFile.swift
│   │   ├── Mocks/
│   │   │   ├── Mocks+Blocks.swift
│   │   │   ├── Mocks+BuildTargetSources.swift
│   │   │   ├── Mocks+ContentTypes.swift
│   │   │   ├── Mocks+E2E.swift
│   │   │   ├── Mocks+Files.swift
│   │   │   ├── Mocks+Pipelines.swift
│   │   │   ├── Mocks+RawContents.swift
│   │   │   ├── Mocks+Templates.swift
│   │   │   ├── Mocks+Views.swift
│   │   │   └── Mocks.swift
│   │   ├── Template/
│   │   │   └── TemplateValidatorTestSuite.swift
│   │   ├── Toucan/
│   │   │   └── ToucanTestSuite.swift
│   │   └── Utilities/
│   │       ├── AnyCodableWrapTests.swift
│   │       ├── CopyManagerTestSuite.swift
│   │       ├── PrettyPrint.swift
│   │       ├── RecursiveMergeTests.swift
│   │       ├── SlugTests.swift
│   │       └── UnboxingTestSuite.swift
│   └── ToucanSourceTests/
│       ├── BuildTargetSourceLoaderTestSuite.swift
│       ├── Extensions/
│       │   └── FileManagerKitExtensionsTestSuite.swift
│       ├── Files/
│       │   └── YAMLFile.swift
│       ├── MarkdownParserTestSuite.swift
│       ├── Models/
│       │   └── BuildTargetSourceLocationsTestSuite.swift
│       ├── Objects/
│       │   ├── AnyCodableTestSuite.swift
│       │   ├── Config/
│       │   │   └── ConfigTestSuite.swift
│       │   ├── DateFormatting/
│       │   │   └── DateFormattingTestSuite.swift
│       │   ├── Pipeline/
│       │   │   ├── PipelineContentTypeTestSuite.swift
│       │   │   ├── PipelineScopeContextTestSuite.swift
│       │   │   ├── PipelineScopeTestSuite.swift
│       │   │   ├── PipelineTestSuite.swift
│       │   │   └── PipelineTransformersTestSuite.swift
│       │   ├── Property/
│       │   │   ├── PropertyTestSuite.swift
│       │   │   └── PropertyTypeTestSuite.swift
│       │   ├── Query/
│       │   │   ├── ConditionTestSuite.swift
│       │   │   ├── DirectionTestSuite.swift
│       │   │   ├── OperatorTestSuite.swift
│       │   │   ├── OrderTestSuite.swift
│       │   │   └── QueryTestSuite.swift
│       │   ├── Relation/
│       │   │   ├── RelationTestSuite.swift
│       │   │   └── RelationTypeTestSuite.swift
│       │   ├── Settings/
│       │   │   └── SettingsTestSuite.swift
│       │   ├── Target/
│       │   │   ├── TargetConfigTestSuite.swift
│       │   │   └── TargetTestSuite.swift
│       │   └── Types/
│       │       └── TypesTestSuite.swift
│       ├── RawContentLoaderTestSuite.swift
│       └── TemplateLoaderTestSuite.swift
└── scripts/
    ├── install-toucan.sh
    ├── packaging/
    │   ├── deb.sh
    │   ├── dmg.sh
    │   ├── pkg.sh
    │   ├── rpm.sh
    │   └── toucan.spec
    ├── run-chmod.sh
    └── uninstall-toucan.sh
Condensed preview — 217 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (989K chars).
[
  {
    "path": ".github/workflows/actions.yml",
    "chars": 1377,
    "preview": "name: Actions\n\non:\n  pull_request:\n    branches:\n      - main\n\njobs:\n\n  bb_checks:\n    name: BB Checks\n    uses: BinaryB"
  },
  {
    "path": ".github/workflows/linux.yml",
    "chars": 5622,
    "preview": "name: Build, Test and Upload Linux Binaries for tag\non:\n  workflow_call:\n    inputs:\n      version:\n        required: tr"
  },
  {
    "path": ".github/workflows/macos.yml",
    "chars": 6525,
    "preview": "name: Build and Publish macOS Binaries\non:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        ty"
  },
  {
    "path": ".github/workflows/pr-for-formula.yml",
    "chars": 3023,
    "preview": "name: Push Homebrew Formula\n\non:\n  workflow_call:\n    inputs:\n      version:\n        required: true\n        type: string"
  },
  {
    "path": ".github/workflows/tag_actions.yml",
    "chars": 1585,
    "preview": "name: Dispatch macOS and Linux Builds on new tag\n\non:\n  push:\n    tags:\n      - 'v*'\n      - '[0-9]*'\n\npermissions:\n  co"
  },
  {
    "path": ".gitignore",
    "chars": 99,
    "preview": ".DS_Store\n.swiftpm\n.build\n.vscode\n.obsidian\n**/dist\n**/docs\nTests/sites/benchmark/\nExamples/try-o/\n"
  },
  {
    "path": ".swift-format",
    "chars": 2124,
    "preview": "{\n  \"fileScopedDeclarationPrivacy\" : {\n    \"accessLevel\" : \"private\"\n  },\n  \"indentation\" : {\n    \"spaces\" : 4\n  },\n  \"m"
  },
  {
    "path": ".swiftformat",
    "chars": 995,
    "preview": "--swiftversion 6\n\n--indent 4\n    --indentstrings true\n    --smarttabs true\n    --xcodeindentation enabled\n\n--maxwidth 80"
  },
  {
    "path": ".swiftformatignore",
    "chars": 13,
    "preview": "Package.swift"
  },
  {
    "path": ".swiftheaderignore",
    "chars": 110,
    "preview": ".*\n*.c\n*.h\n*.txt\n*.html\n*.yaml\nREADME.md\nPackage.resolved\nMakefile\nLICENSE\nPackage.swift\nDocker/**\nscripts/**\n"
  },
  {
    "path": "Docker/Dockerfile",
    "chars": 1806,
    "preview": "FROM swift:6.3-noble AS build\n\nWORKDIR /build\nCOPY ./Package.* ./\nRUN swift package resolve\nCOPY Sources ./Sources\nCOPY "
  },
  {
    "path": "Docker/Dockerfile.testing",
    "chars": 179,
    "preview": "FROM swift:6.1\n\nWORKDIR /app\n\nCOPY . ./\n\nRUN swift package resolve\nRUN swift package clean\nRUN swift package update\n\nCMD"
  },
  {
    "path": "LICENSE",
    "chars": 1116,
    "preview": "MIT License\n\nCopyright (c) 2018-2022 Tibor Bödecs\nCopyright (c) 2022-2025 Binary Birds Ltd.\n\nPermission is hereby grante"
  },
  {
    "path": "Makefile",
    "chars": 1841,
    "preview": "SHELL=/bin/bash\n\n.PHONY: docker\n\nbaseUrl = https://raw.githubusercontent.com/BinaryBirds/github-workflows/refs/heads/mai"
  },
  {
    "path": "Package.resolved",
    "chars": 10584,
    "preview": "{\n  \"originHash\" : \"b0aa8e8208e365a7988670c760c7c1a87737dac7471c3bcff7b3381dbaabf37b\",\n  \"pins\" : [\n    {\n      \"identit"
  },
  {
    "path": "Package.swift",
    "chars": 9002,
    "preview": "// swift-tools-version: 6.1\nimport PackageDescription\n\nlet swiftSettings: [SwiftSetting] = [\n    .enableExperimentalFeat"
  },
  {
    "path": "README.md",
    "chars": 980,
    "preview": "# Toucan\n\nToucan is a markdown-based Static Site Generator (SSG) written in Swift.\n\n## Installation\n\n## Compile from sou"
  },
  {
    "path": "Sources/ToucanCore/Extensions/Dictionary+Extensions.swift",
    "chars": 2704,
    "preview": "//\n//  Dictionary+Extensions.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 03..\n//\n\npublic extension Dict"
  },
  {
    "path": "Sources/ToucanCore/Extensions/Logging+Extensions.swift",
    "chars": 3211,
    "preview": "//\n//  Logging+Extensions.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport class Foundation."
  },
  {
    "path": "Sources/ToucanCore/Extensions/String+Extensions.swift",
    "chars": 6761,
    "preview": "//\n//  String+Extensions.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\nimport Foundation\n\npublic"
  },
  {
    "path": "Sources/ToucanCore/Extensions/URL+Extensions.swift",
    "chars": 1082,
    "preview": "//\n//  URL+Extensions.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\nimport Foundation\n\npublic ex"
  },
  {
    "path": "Sources/ToucanCore/GeneratorInfo.swift",
    "chars": 2123,
    "preview": "//\n//  GeneratorInfo.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 19..\n//\n\nimport _GitCommitHash\nim"
  },
  {
    "path": "Sources/ToucanCore/Logger.swift",
    "chars": 545,
    "preview": "//\n//  Logger.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Logging\n\n/// A protocol for t"
  },
  {
    "path": "Sources/ToucanCore/ToucanError.swift",
    "chars": 6032,
    "preview": "//\n//  ToucanError.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 20..\n//\n\nimport Foundation\n\n/// A protoc"
  },
  {
    "path": "Sources/ToucanMarkdown/Markdown/HTML.swift",
    "chars": 1074,
    "preview": "//\n//  HTML.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 19..\n//\n\nstruct HTML {\n    enum TagType {\n     "
  },
  {
    "path": "Sources/ToucanMarkdown/Markdown/HTMLVisitor.swift",
    "chars": 14845,
    "preview": "//\n//  HTMLVisitor.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 19..\n//\n\nimport Logging\nimport Markdown\n"
  },
  {
    "path": "Sources/ToucanMarkdown/Markdown/MarkdownBlockDirective.swift",
    "chars": 3913,
    "preview": "//\n//  MarkdownBlockDirective.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 19..\n//\n\n/// A representation"
  },
  {
    "path": "Sources/ToucanMarkdown/Markdown/MarkdownToHTMLRenderer.swift",
    "chars": 2593,
    "preview": "//\n//  MarkdownToHTMLRenderer.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 19..\n//\n\nimport Logging\nimpor"
  },
  {
    "path": "Sources/ToucanMarkdown/MarkdownRenderer.swift",
    "chars": 7670,
    "preview": "//\n//  MarkdownRenderer.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 20..\n//\n\nimport Logging\nimport Touc"
  },
  {
    "path": "Sources/ToucanMarkdown/Outline/Outline.swift",
    "chars": 1368,
    "preview": "//\n//  Outline.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 04. 17..\n//\n\n/// A hierarchical representation o"
  },
  {
    "path": "Sources/ToucanMarkdown/Outline/OutlineParser.swift",
    "chars": 3271,
    "preview": "//\n//  OutlineParser.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2024. 10. 14..\n//\n\nimport Logging\nimport Sw"
  },
  {
    "path": "Sources/ToucanMarkdown/ReadingTime/ReadingTimeCalculator.swift",
    "chars": 1317,
    "preview": "//\n//  ReadingTimeCalculator.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2024. 10. 15..\n//\n\nimport Logging\ni"
  },
  {
    "path": "Sources/ToucanMarkdown/Transformers/ContentTransformer.swift",
    "chars": 871,
    "preview": "//\n//  ContentTransformer.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\n/// Represents a content"
  },
  {
    "path": "Sources/ToucanMarkdown/Transformers/TransformerExecutor.swift",
    "chars": 3857,
    "preview": "//\n//  TransformerExecutor.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2024. 10. 15..\n//\n\nimport Foundation\n"
  },
  {
    "path": "Sources/ToucanMarkdown/Transformers/TransformerPipeline.swift",
    "chars": 1167,
    "preview": "//\n//  TransformerPipeline.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\n/// Represents a sequen"
  },
  {
    "path": "Sources/ToucanSDK/Behaviors/Behavior.swift",
    "chars": 607,
    "preview": "//\n//  Behavior.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 16..\n//\n\nimport struct Foundation.URL\n\n/// "
  },
  {
    "path": "Sources/ToucanSDK/Behaviors/CompileSASSBehavior.swift",
    "chars": 1148,
    "preview": "//\n//  CompileSASSBehavior.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 12..\n//\n\nimport DartSass\nimport "
  },
  {
    "path": "Sources/ToucanSDK/Behaviors/MinifyCSSBehavior.swift",
    "chars": 457,
    "preview": "//\n//  MinifyCSSBehavior.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 12..\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "Sources/ToucanSDK/Content/Content+Query.swift",
    "chars": 10363,
    "preview": "//\n//  Content+Query.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 31..\n//\n\nimport Foundation\nimport Touc"
  },
  {
    "path": "Sources/ToucanSDK/Content/Content.swift",
    "chars": 2828,
    "preview": "//\n//  Content.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 15..\n//\n\nimport ToucanSource\n\n/// Represents"
  },
  {
    "path": "Sources/ToucanSDK/Content/ContentResolver.swift",
    "chars": 27307,
    "preview": "//\n//  ContentResolver.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport Foundation\nimport Lo"
  },
  {
    "path": "Sources/ToucanSDK/Content/ContentTypeResolver.swift",
    "chars": 2053,
    "preview": "//\n//  ContentTypeResolver.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 30..\n//\n\nimport ToucanCore\nimpor"
  },
  {
    "path": "Sources/ToucanSDK/Content/IteratorInfo.swift",
    "chars": 2482,
    "preview": "//\n//  IteratorInfo.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\n/// Provides pagination and iteratio"
  },
  {
    "path": "Sources/ToucanSDK/Content/Query+Resolve.swift",
    "chars": 2194,
    "preview": "//\n//  Query+Resolve.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 16..\n//\n\nimport ToucanSource\n\npublic e"
  },
  {
    "path": "Sources/ToucanSDK/Content/RelationValue.swift",
    "chars": 1304,
    "preview": "//\n//  RelationValue.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 30..\n//\n\nimport ToucanSource\n\n/// Repr"
  },
  {
    "path": "Sources/ToucanSDK/DateFormats/DateContext.swift",
    "chars": 2808,
    "preview": "//\n//  DateContext.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 12..\n//\n\n/// A configuration container f"
  },
  {
    "path": "Sources/ToucanSDK/DateFormats/ToucanDateFormatters.swift",
    "chars": 9753,
    "preview": "//\n//  ToucanDateFormatters.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 26..\n//\n\nimport Foundation\nimpo"
  },
  {
    "path": "Sources/ToucanSDK/Models/ContextBundle.swift",
    "chars": 1440,
    "preview": "//\n//  ContextBundle.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\nimport ToucanSource\n\n/// A bu"
  },
  {
    "path": "Sources/ToucanSDK/Models/Destination.swift",
    "chars": 982,
    "preview": "//\n//  Destination.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\n/// Represents the destination "
  },
  {
    "path": "Sources/ToucanSDK/Models/PipelineResult.swift",
    "chars": 1844,
    "preview": "//\n//  PipelineResult.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\n/// Represents the output of"
  },
  {
    "path": "Sources/ToucanSDK/Models/Slug.swift",
    "chars": 3022,
    "preview": "//\n//  Slug.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\n/// A value type representing a URL-friendly"
  },
  {
    "path": "Sources/ToucanSDK/Outputs/ContextBundleToHTMLRenderer.swift",
    "chars": 2779,
    "preview": "//\n//  ContextBundleToHTMLRenderer.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 13..\n//\n\nimport Fou"
  },
  {
    "path": "Sources/ToucanSDK/Outputs/ContextBundleToJSONRenderer.swift",
    "chars": 3327,
    "preview": "//\n//  ContextBundleToJSONRenderer.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 13..\n//\n\nimport Fou"
  },
  {
    "path": "Sources/ToucanSDK/Renderers/BuildTargetSourceRenderer.swift",
    "chars": 25316,
    "preview": "//\n//  BuildTargetSourceRenderer.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 25..\n//\n\nimport Found"
  },
  {
    "path": "Sources/ToucanSDK/Renderers/MustacheRenderer.swift",
    "chars": 2333,
    "preview": "//\n//  MustacheRenderer.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 16..\n//\n\nimport Foundation\nimport L"
  },
  {
    "path": "Sources/ToucanSDK/Toucan.swift",
    "chars": 11437,
    "preview": "//\n//  Toucan.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 04. 17..\n//\n\nimport FileManagerKit\nimport Foundat"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/Any+AnyCodable.swift",
    "chars": 2092,
    "preview": "//\n//  Any+AnyCodable.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 03. 06..\n//\n\nimport ToucanSource\n\n/// Rec"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/AnyCodable+Json.swift",
    "chars": 1012,
    "preview": "//\n//  AnyCodable+Json.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 12..\n//\n\nimport Foundation\nimpo"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/Array+AnyCodable.swift",
    "chars": 538,
    "preview": "//\n//  Array+AnyCodable.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 11..\n//\n\nimport Foundation\nimp"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/ContextKeys.swift",
    "chars": 1087,
    "preview": "//\n//  ContextKeys.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 09. 04..\n//\n\n/// Root-level keys used i"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/CopyManager.swift",
    "chars": 1404,
    "preview": "//\n//  CopyManager.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\nimport FileManagerKit\nimport Foundati"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/Dictionary+AnyCodable.swift",
    "chars": 589,
    "preview": "//\n//  Dictionary+AnyCodable.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 31..\n//\n\nimport Foundation\nimp"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/Encodable+Json.swift",
    "chars": 752,
    "preview": "//\n//  Encodable+Json.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 11..\n//\n\nimport Foundation\n\npubl"
  },
  {
    "path": "Sources/ToucanSDK/Utilities/FirstSucceeding.swift",
    "chars": 855,
    "preview": "//\n//  FirstSucceeding.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 11..\n//\n\n/// Attempts to execut"
  },
  {
    "path": "Sources/ToucanSDK/Validators/BuildTargetSourceValidator.swift",
    "chars": 6625,
    "preview": "//\n//  BuildTargetSourceValidator.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 23..\n//\n\nimport Foundatio"
  },
  {
    "path": "Sources/ToucanSDK/Validators/TemplateValidator.swift",
    "chars": 1940,
    "preview": "//\n//  TemplateValidator.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 06. 17..\n//\n\nimport Foundation\nim"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanDecoder.swift",
    "chars": 2222,
    "preview": "//\n//  ToucanDecoder.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 29..\n//\n\nimport struct Foundation.Data"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanDecoderError.swift",
    "chars": 2878,
    "preview": "//\n//  ToucanDecoderError.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\nimport ToucanCore\n\n/// Extensi"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanEncoder.swift",
    "chars": 1873,
    "preview": "//\n//  ToucanEncoder.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 03. 06..\n//\n\nimport struct Foundation.Data"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanEncoderError.swift",
    "chars": 1726,
    "preview": "//\n//  ToucanEncoderError.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\nimport ToucanCore\n\n/// Extensi"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanJSONDecoder.swift",
    "chars": 1125,
    "preview": "//\n//  ToucanJSONDecoder.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport struct Foundation.Dat"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanJSONEncoder.swift",
    "chars": 1621,
    "preview": "//\n//  ToucanJSONEncoder.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport struct Foundation."
  },
  {
    "path": "Sources/ToucanSerialization/ToucanYAMLDecoder.swift",
    "chars": 1038,
    "preview": "//\n//  ToucanYAMLDecoder.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport struct Foundation.Dat"
  },
  {
    "path": "Sources/ToucanSerialization/ToucanYAMLEncoder.swift",
    "chars": 954,
    "preview": "//\n//  ToucanYAMLEncoder.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 03. 06..\n//\n\nimport struct Foundation."
  },
  {
    "path": "Sources/ToucanSource/Errors/ObjectLoaderError.swift",
    "chars": 1273,
    "preview": "//\n//  ObjectLoaderError.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport struct Foundation."
  },
  {
    "path": "Sources/ToucanSource/Errors/SourceLoaderError.swift",
    "chars": 1357,
    "preview": "//\n//  SourceLoaderError.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport ToucanCore\n\n/// A "
  },
  {
    "path": "Sources/ToucanSource/Errors/TemplateLoaderError.swift",
    "chars": 1372,
    "preview": "//\n//  TemplateLoaderError.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport ToucanCore\n\n/// "
  },
  {
    "path": "Sources/ToucanSource/Extensions/Decoder+Validate.swift",
    "chars": 1921,
    "preview": "//\n//  Decoder+Validate.swift\n//  Toucan\n//\n//  Created by Ferenc Viasz-Kadi on 2025. 08. 19..\n//\n\nextension Decoder {\n\n"
  },
  {
    "path": "Sources/ToucanSource/Extensions/Dictionary+AnyCodable.swift",
    "chars": 1765,
    "preview": "//\n//  Dictionary+AnyCodable.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport struct Foundat"
  },
  {
    "path": "Sources/ToucanSource/Extensions/FileManagerKit+Extensions.swift",
    "chars": 2885,
    "preview": "//\n//  FileManagerKit+Extensions.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport FileManagerKi"
  },
  {
    "path": "Sources/ToucanSource/Loaders/BuildTargetSourceLoader.swift",
    "chars": 9836,
    "preview": "//\n//  BuildTargetSourceLoader.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 04. 04..\n//\n\nimport FileManagerK"
  },
  {
    "path": "Sources/ToucanSource/Loaders/ObjectLoader.swift",
    "chars": 4049,
    "preview": "//\n//  ObjectLoader.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 16..\n//\n\nimport Foundation\nimport Loggi"
  },
  {
    "path": "Sources/ToucanSource/Loaders/RawContentLoader.swift",
    "chars": 12090,
    "preview": "//\n//  RawContentLoader.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 03..\n//\n\nimport FileManagerKit"
  },
  {
    "path": "Sources/ToucanSource/Loaders/TemplateLoader.swift",
    "chars": 5618,
    "preview": "//\n//  TemplateLoader.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport FileManagerKit\nimport"
  },
  {
    "path": "Sources/ToucanSource/MarkdownParser.swift",
    "chars": 3017,
    "preview": "//\n//  MarkdownParser.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 04. 17..\n//\n\nimport Logging\nimport Toucan"
  },
  {
    "path": "Sources/ToucanSource/Models/BuildTargetSource.swift",
    "chars": 2502,
    "preview": "//\n//  BuildTargetSource.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 31..\n//\n\nimport struct Foundation."
  },
  {
    "path": "Sources/ToucanSource/Models/BuiltTargetSourceLocations.swift",
    "chars": 4608,
    "preview": "//\n//  BuiltTargetSourceLocations.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 01..\n//\n\nimport stru"
  },
  {
    "path": "Sources/ToucanSource/Models/Markdown.swift",
    "chars": 1089,
    "preview": "//\n//  Markdown.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\n/// A representation of a Markdown"
  },
  {
    "path": "Sources/ToucanSource/Models/Origin.swift",
    "chars": 940,
    "preview": "//\n//  Origin.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 30..\n//\n\n/// Represents the source origin of "
  },
  {
    "path": "Sources/ToucanSource/Models/Path.swift",
    "chars": 2833,
    "preview": "//\n//  Path.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 04..\n//\n\nimport Foundation\n\n/// A value type re"
  },
  {
    "path": "Sources/ToucanSource/Models/RawContent.swift",
    "chars": 1691,
    "preview": "//\n//  RawContent.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 30..\n//\n\n/// Represents the raw, unproces"
  },
  {
    "path": "Sources/ToucanSource/Models/Template.swift",
    "chars": 6358,
    "preview": "//\n//  Template.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport struct Foundation.URL\nimpor"
  },
  {
    "path": "Sources/ToucanSource/Models/View.swift",
    "chars": 929,
    "preview": "//\n//  View.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 31..\n//\n\nimport Foundation\n\n/// Represents the "
  },
  {
    "path": "Sources/ToucanSource/Objects/AnyCodable.swift",
    "chars": 9883,
    "preview": "//\n//  AnyCodable.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport Foundation\n\n// public protoc"
  },
  {
    "path": "Sources/ToucanSource/Objects/Blocks/Block+Attribute.swift",
    "chars": 815,
    "preview": "//\n//  Block+Attribute.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\npublic extension Block {\n  "
  },
  {
    "path": "Sources/ToucanSource/Objects/Blocks/Block+Parameter.swift",
    "chars": 1179,
    "preview": "//\n//  Block+Parameter.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\npublic extension Block {\n  "
  },
  {
    "path": "Sources/ToucanSource/Objects/Blocks/Block.swift",
    "chars": 2102,
    "preview": "//\n//  Block.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\n/// A representation of a custom bloc"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Blocks.swift",
    "chars": 1621,
    "preview": "//\n//  Config+Blocks.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 04. 18..\n//\n\npublic extension Config "
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Contents.swift",
    "chars": 2284,
    "preview": "//\n//  Config+Contents.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Config {\n "
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+DataTypes+Date.swift",
    "chars": 3082,
    "preview": "//\n//  Config+DataTypes+Date.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Conf"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+DataTypes.swift",
    "chars": 1797,
    "preview": "//\n//  Config+DataTypes.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 16..\n//\n\npublic extension Config {\n"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Location.swift",
    "chars": 574,
    "preview": "//\n//  Config+Location.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Config {\n "
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Pipelines.swift",
    "chars": 1648,
    "preview": "//\n//  Config+Pipelines.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Config {\n"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Renderer+ParagraphStyles.swift",
    "chars": 2232,
    "preview": "//\n//  Config+Renderer+ParagraphStyles.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 17..\n//\n\npublic extension "
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+RendererConfig.swift",
    "chars": 3280,
    "preview": "//\n//  Config+RendererConfig.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 03. 28..\n//\n\npublic extension Config {\n "
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Site.swift",
    "chars": 2084,
    "preview": "//\n//  Config+Site.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Config {\n    /"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Templates.swift",
    "chars": 3528,
    "preview": "//\n//  Config+Templates.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 01..\n//\n\nimport Foundation\n\npu"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config+Types.swift",
    "chars": 1637,
    "preview": "//\n//  Config+Types.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 04. 18..\n//\n\npublic extension Config {"
  },
  {
    "path": "Sources/ToucanSource/Objects/Config/Config.swift",
    "chars": 4867,
    "preview": "//\n//  Config.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 29..\n//\n\nimport Foundation\n\n/// Represents th"
  },
  {
    "path": "Sources/ToucanSource/Objects/Date/DateFormatterConfig.swift",
    "chars": 3012,
    "preview": "//\n//  DateFormatterConfig.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 28..\n//\n\n/// A configuratio"
  },
  {
    "path": "Sources/ToucanSource/Objects/Date/DateLocalization.swift",
    "chars": 3957,
    "preview": "//\n//  DateLocalization.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 28..\n//\n\nimport struct Foundation.L"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Assets.swift",
    "chars": 8060,
    "preview": "//\n//  Pipeline+Assets.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 04. 19..\n//\n\npublic extension Pipeline {"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+ContentTypes.swift",
    "chars": 4480,
    "preview": "//\n//  Pipeline+ContentTypes.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 03..\n//\n\npublic extension Pipe"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+DataTypes+Date.swift",
    "chars": 2458,
    "preview": "//\n//  Pipeline+DataTypes+Date.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 30..\n//\n\npublic extension Pi"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+DataTypes.swift",
    "chars": 1801,
    "preview": "//\n//  Pipeline+DataTypes.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 30..\n//\n\npublic extension Pipelin"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Engine.swift",
    "chars": 1920,
    "preview": "//\n//  Pipeline+Engine.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 03..\n//\n\npublic extension Pipeline {"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Output.swift",
    "chars": 2076,
    "preview": "//\n//  Pipeline+Output.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 16..\n//\n\npublic extension Pipeline {"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Scope+Context.swift",
    "chars": 6030,
    "preview": "//\n//  Pipeline+Scope+Context.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 03..\n//\n\npublic extension Pip"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Scope.swift",
    "chars": 3756,
    "preview": "//\n//  Pipeline+Scope.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 03..\n//\n\npublic extension Pipeline {\n"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Transformers+Transformer.swift",
    "chars": 1834,
    "preview": "//\n//  Pipeline+Transformers+Transformer.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 16..\n//\n\nimport Fo"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline+Transformers.swift",
    "chars": 1276,
    "preview": "//\n//  Pipeline+Transformers.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 21..\n//\n\npublic extension Pipe"
  },
  {
    "path": "Sources/ToucanSource/Objects/Pipeline/Pipeline.swift",
    "chars": 6239,
    "preview": "//\n//  Pipeline.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 16..\n//\n\n/// Represents a full content tran"
  },
  {
    "path": "Sources/ToucanSource/Objects/Property/Property.swift",
    "chars": 3033,
    "preview": "//\n//  Property.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents a single content pr"
  },
  {
    "path": "Sources/ToucanSource/Objects/Property/PropertyType.swift",
    "chars": 3544,
    "preview": "//\n//  PropertyType.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents the type of a c"
  },
  {
    "path": "Sources/ToucanSource/Objects/Property/SystemPropertyKeys.swift",
    "chars": 501,
    "preview": "//\n//  SystemPropertyKeys.swift\n//  Toucan\n//\n//  Created by Ferenc Viasz-Kadi on 2025. 08. 22..\n//\n\n/// Represents pred"
  },
  {
    "path": "Sources/ToucanSource/Objects/Query/Condition.swift",
    "chars": 3014,
    "preview": "//\n//  Condition.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents a logical conditio"
  },
  {
    "path": "Sources/ToucanSource/Objects/Query/Direction.swift",
    "chars": 493,
    "preview": "//\n//  Direction.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents the direction for "
  },
  {
    "path": "Sources/ToucanSource/Objects/Query/Operator.swift",
    "chars": 926,
    "preview": "//\n//  Operator.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents a comparison or fil"
  },
  {
    "path": "Sources/ToucanSource/Objects/Query/Order.swift",
    "chars": 2112,
    "preview": "//\n//  Order.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 15..\n//\n\n/// Represents a sorting rule for ord"
  },
  {
    "path": "Sources/ToucanSource/Objects/Query/Query.swift",
    "chars": 3132,
    "preview": "//\n//  Query.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 15..\n//\n\n/// Represents a content query used t"
  },
  {
    "path": "Sources/ToucanSource/Objects/Relation/Relation.swift",
    "chars": 2405,
    "preview": "//\n//  Relation.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Represents a relationship betw"
  },
  {
    "path": "Sources/ToucanSource/Objects/Relation/RelationType.swift",
    "chars": 453,
    "preview": "//\n//  RelationType.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 21..\n//\n\n/// Defines the cardinality of"
  },
  {
    "path": "Sources/ToucanSource/Objects/Settings/Settings.swift",
    "chars": 2201,
    "preview": "//\n//  Settings.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 11..\n//\n\n/// A custom coding key type for e"
  },
  {
    "path": "Sources/ToucanSource/Objects/Target/Target.swift",
    "chars": 4053,
    "preview": "//\n//  Target.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 15..\n//\n\n/// Represents a deployment target c"
  },
  {
    "path": "Sources/ToucanSource/Objects/Target/TargetConfig.swift",
    "chars": 2805,
    "preview": "//\n//  TargetConfig.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 15..\n//\n\n/// A structure that holds a l"
  },
  {
    "path": "Sources/ToucanSource/Objects/Types/ContentType.swift",
    "chars": 4696,
    "preview": "//\n//  ContentType.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 15..\n//\n\n/// Describes a content type de"
  },
  {
    "path": "Sources/_GitCommitHash/git_commit_hash.c",
    "chars": 97,
    "preview": "#include \"git_commit_hash.h\"\n\nconst char * git_commit_hash(void)\n{\n    return GIT_COMMIT_HASH;\n}\n"
  },
  {
    "path": "Sources/_GitCommitHash/include/git_commit_hash.h",
    "chars": 110,
    "preview": "#if !defined(GIT_COMMIT_HASH_H)\n#define GIT_COMMIT_HASH_H\n\nextern const char * git_commit_hash(void);\n\n#endif\n"
  },
  {
    "path": "Sources/toucan/Entrypoint.swift",
    "chars": 3058,
    "preview": "//\n//  Entrypoint.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport ArgumentParser\nimport Dispat"
  },
  {
    "path": "Sources/toucan-generate/Entrypoint.swift",
    "chars": 1691,
    "preview": "//\n//  Entrypoint.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport ArgumentParser\nimport Loggin"
  },
  {
    "path": "Sources/toucan-init/Download.swift",
    "chars": 2303,
    "preview": "//\n//  Download.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 31..\n\nimport FileManagerKit\nimport Foundati"
  },
  {
    "path": "Sources/toucan-init/Entrypoint.swift",
    "chars": 2449,
    "preview": "//\n//  Entrypoint.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport ArgumentParser\nimport FileMa"
  },
  {
    "path": "Sources/toucan-serve/Entrypoint.swift",
    "chars": 1835,
    "preview": "//\n//  Entrypoint.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport ArgumentParser\nimport Founda"
  },
  {
    "path": "Sources/toucan-serve/NotFoundMiddleware.swift",
    "chars": 786,
    "preview": "//\n//  NotFoundMiddleware.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 23..\n//\n\nimport Hummingbird\n\nstru"
  },
  {
    "path": "Sources/toucan-watch/Entrypoint.swift",
    "chars": 4463,
    "preview": "//\n//  Entrypoint.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport ArgumentParser\nimport FileMo"
  },
  {
    "path": "Tests/ToucanCoreTests/Extensions/StringExtensionsTestSuite.swift",
    "chars": 3288,
    "preview": "//\n//  StringExtensionsTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\nimport Testing\n\n@"
  },
  {
    "path": "Tests/ToucanCoreTests/Extensions/URLExtensionsTestSuite.swift",
    "chars": 876,
    "preview": "//\n//  URLExtensionsTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\nimport Foundation\nim"
  },
  {
    "path": "Tests/ToucanCoreTests/ToucanCoreTestSuite.swift",
    "chars": 456,
    "preview": "//\n//  ToucanCoreTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 17..\n//\n\nimport Testing\n@testabl"
  },
  {
    "path": "Tests/ToucanMarkdownTests/ContentRendererTestSuite.swift",
    "chars": 1634,
    "preview": "//\n//  ContentRendererTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport Foundation\nimp"
  },
  {
    "path": "Tests/ToucanMarkdownTests/HTMLVisitorTestSuite.swift",
    "chars": 18340,
    "preview": "//\n//  HTMLVisitorTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n\nimport Logging\nimport Mar"
  },
  {
    "path": "Tests/ToucanMarkdownTests/MarkdownBlockDirective+Mock.swift",
    "chars": 1764,
    "preview": "//\n//  MarkdownBlockDirective+Mock.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport Foundation\n"
  },
  {
    "path": "Tests/ToucanMarkdownTests/MarkdownBlockDirectiveTestSuite.swift",
    "chars": 5830,
    "preview": "//\n//  MarkdownBlockDirectiveTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport Logging"
  },
  {
    "path": "Tests/ToucanMarkdownTests/OutlineTestSuite.swift",
    "chars": 1546,
    "preview": "//\n//  OutlineTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 02. 20..\n\nimport Testing\n\n@testable imp"
  },
  {
    "path": "Tests/ToucanSDKTests/BuildTargetSource/BuildTargetSourceRendererTestSuite.swift",
    "chars": 32327,
    "preview": "//\n//  BuildTargetSourceRendererTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 09..\n//\n\nimport F"
  },
  {
    "path": "Tests/ToucanSDKTests/BuildTargetSource/BuildTargetSourceValidatorTestSuite.swift",
    "chars": 3843,
    "preview": "//\n//  BuildTargetSourceValidatorTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 23..\n//\n//\nimpor"
  },
  {
    "path": "Tests/ToucanSDKTests/Content/ContentQueryTestSuite.swift",
    "chars": 28398,
    "preview": "//\n//  ContentQueryTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n//\nimport Foundation\nimpo"
  },
  {
    "path": "Tests/ToucanSDKTests/Content/ContentResolverTestSuite.swift",
    "chars": 42461,
    "preview": "//\n//  ContentResolverTestSuite.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 02. 20..\n//\n//\nimport Foun"
  },
  {
    "path": "Tests/ToucanSDKTests/DateFormatter/ToucanDateFormatterTestSuite.swift",
    "chars": 2337,
    "preview": "//\n//  ToucanDateFormatterTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n//\n\nimport Foundat"
  },
  {
    "path": "Tests/ToucanSDKTests/E2ETestSuite.swift",
    "chars": 63257,
    "preview": "//\n//  E2ETestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 11..\n//\n\nimport FileManagerKitBuilder\ni"
  },
  {
    "path": "Tests/ToucanSDKTests/Files/MarkdownFile.swift",
    "chars": 1147,
    "preview": "//\n//  MarkdownFile.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 04..\n//\n\nimport FileManagerKit\nimport F"
  },
  {
    "path": "Tests/ToucanSDKTests/Files/MustacheFile.swift",
    "chars": 681,
    "preview": "//\n//  MustacheFile.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 04..\n//\n\nimport FileManagerKit\nimport F"
  },
  {
    "path": "Tests/ToucanSDKTests/Files/RawContentBundle.swift",
    "chars": 914,
    "preview": "//\n//  RawContentBundle.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 04..\n//\n\nimport FileManagerKit\nimpo"
  },
  {
    "path": "Tests/ToucanSDKTests/Files/YAMLFile.swift",
    "chars": 738,
    "preview": "//\n//  YAMLFile.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 04..\n//\n\nimport FileManagerKit\nimport FileM"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+Blocks.swift",
    "chars": 2359,
    "preview": "//\n//  Mocks+Blocks.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport ToucanSource\n\nextension"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+BuildTargetSources.swift",
    "chars": 6009,
    "preview": "//\n//  Mocks+BuildTargetSources.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport Foundation\n"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+ContentTypes.swift",
    "chars": 11747,
    "preview": "//\n//  Mocks+ContentTypes.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport ToucanSource\n\next"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+E2E.swift",
    "chars": 19916,
    "preview": "//\n//  Mocks+E2E.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 08..\n//\n\nimport FileManagerKitBuilder\nimpo"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+Files.swift",
    "chars": 9224,
    "preview": "//\n//  Mocks+Files.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 15..\n//\n\nimport FileManagerKitBuilder\nimport F"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+Pipelines.swift",
    "chars": 14854,
    "preview": "//\n//  Mocks+Pipelines.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport ToucanSource\nimport "
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+RawContents.swift",
    "chars": 11625,
    "preview": "//\n//  Mocks+RawContents.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+Templates.swift",
    "chars": 2795,
    "preview": "//\n//  Mocks+Templates.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 06. 17..\n//\n\nimport ToucanCore\n@tes"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks+Views.swift",
    "chars": 22560,
    "preview": "//\n//  Mocks+Views.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\n@testable import ToucanSource\n\n"
  },
  {
    "path": "Tests/ToucanSDKTests/Mocks/Mocks.swift",
    "chars": 244,
    "preview": "//\n//  Mocks.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 30..\n//\n\nenum Mocks {\n    enum ContentTypes {}"
  },
  {
    "path": "Tests/ToucanSDKTests/Template/TemplateValidatorTestSuite.swift",
    "chars": 3943,
    "preview": "//\n//  TemplateValidatorTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 23..\n//\n//\nimport Foundat"
  },
  {
    "path": "Tests/ToucanSDKTests/Toucan/ToucanTestSuite.swift",
    "chars": 2333,
    "preview": "//\n//  ToucanTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 06. 20..\n//\n\nimport FileManagerKitBuilde"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/AnyCodableWrapTests.swift",
    "chars": 2389,
    "preview": "//\n//  AnyCodableWrapTests.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 28..\n//\n\nimport Foundation\nimport Test"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/CopyManagerTestSuite.swift",
    "chars": 2610,
    "preview": "//\n//  CopyManagerTestSuite.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 03. 04..\n//\n\nimport FileManage"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/PrettyPrint.swift",
    "chars": 802,
    "preview": "//\n//  PrettyPrint.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 02. 11..\n//\n\nimport Foundation\nimport Toucan"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/RecursiveMergeTests.swift",
    "chars": 1040,
    "preview": "//\n//  RecursiveMergeTests.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n//\n\nimport Testing\nimport T"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/SlugTests.swift",
    "chars": 603,
    "preview": "//\n//  SlugTests.swift\n//  Toucan\n//\n//  Created by gerp83 on 2025. 04. 04..\n//\n\nimport Testing\nimport ToucanSDK\n\n@Suite"
  },
  {
    "path": "Tests/ToucanSDKTests/Utilities/UnboxingTestSuite.swift",
    "chars": 9670,
    "preview": "//\n//  UnboxingTestSuite.swift\n//  Toucan\n//\n//  Created by Viasz-Kádi Ferenc on 2025. 05. 09..\n//\n//\n\nimport Foundation"
  },
  {
    "path": "Tests/ToucanSourceTests/BuildTargetSourceLoaderTestSuite.swift",
    "chars": 9821,
    "preview": "//\n//  BuildTargetSourceLoaderTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 04..\n\nimport FileMa"
  },
  {
    "path": "Tests/ToucanSourceTests/Extensions/FileManagerKitExtensionsTestSuite.swift",
    "chars": 2070,
    "preview": "//\n//  FileManagerKitExtensionsTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 04..\n\nimport FileM"
  },
  {
    "path": "Tests/ToucanSourceTests/Files/YAMLFile.swift",
    "chars": 738,
    "preview": "//\n//  YAMLFile.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 20..\n//\n\nimport FileManagerKit\nimport FileM"
  },
  {
    "path": "Tests/ToucanSourceTests/MarkdownParserTestSuite.swift",
    "chars": 5986,
    "preview": "//\n//  MarkdownParserTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 15..\n//\nimport Logging\nimpor"
  },
  {
    "path": "Tests/ToucanSourceTests/Models/BuildTargetSourceLocationsTestSuite.swift",
    "chars": 3203,
    "preview": "//\n//  BuildTargetSourceLocationsTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 21..\n//\n\nimport "
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/AnyCodableTestSuite.swift",
    "chars": 11086,
    "preview": "//\n//  AnyCodableTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 30..\n\nimport Foundation\nimport T"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Config/ConfigTestSuite.swift",
    "chars": 5182,
    "preview": "//\n//  ConfigTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 04. 17..\n\nimport Foundation\nimport Testi"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/DateFormatting/DateFormattingTestSuite.swift",
    "chars": 5937,
    "preview": "//\n//  DateFormattingTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 28..\n//\n\nimport Foundation\ni"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Pipeline/PipelineContentTypeTestSuite.swift",
    "chars": 1587,
    "preview": "//\n//  PipelineContentTypeTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 12..\n\nimport Foundation"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Pipeline/PipelineScopeContextTestSuite.swift",
    "chars": 2173,
    "preview": "//\n//  PipelineScopeContextTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 12..\n\nimport Foundatio"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Pipeline/PipelineScopeTestSuite.swift",
    "chars": 1699,
    "preview": "//\n//  PipelineScopeTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 12..\n\nimport Foundation\nimpor"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Pipeline/PipelineTestSuite.swift",
    "chars": 6821,
    "preview": "//\n//  PipelineTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 30..\n\nimport Foundation\nimport Tes"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Pipeline/PipelineTransformersTestSuite.swift",
    "chars": 794,
    "preview": "//\n//  PipelineTransformersTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Founda"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Property/PropertyTestSuite.swift",
    "chars": 2656,
    "preview": "//\n//  PropertyTestSuite.swift\n//  Toucan\n//\n//  Created by Binary Birds on 2025. 03. 30..\n\nimport Foundation\nimport Tes"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Property/PropertyTypeTestSuite.swift",
    "chars": 5063,
    "preview": "//\n//  PropertyTypeTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 01. 31..\n//\n\nimport Foundation\nimp"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Query/ConditionTestSuite.swift",
    "chars": 9674,
    "preview": "//\n//  ConditionTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Foundation\nimport"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Query/DirectionTestSuite.swift",
    "chars": 1371,
    "preview": "//\n//  DirectionTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Foundation\nimport"
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Query/OperatorTestSuite.swift",
    "chars": 3766,
    "preview": "//\n//  OperatorTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "Tests/ToucanSourceTests/Objects/Query/OrderTestSuite.swift",
    "chars": 1497,
    "preview": "//\n//  OrderTestSuite.swift\n//  Toucan\n//\n//  Created by Tibor Bödecs on 2025. 05. 18..\n//\n\nimport Foundation\nimport Tes"
  }
]

// ... and 17 more files (download for full content)

About this extraction

This page contains the full source code of the toucansites/toucan GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 217 files (908.3 KB), approximately 197.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!