├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.yaml │ ├── feature-request.yaml │ └── question.yaml ├── data │ ├── Extended-TimeperOperation.png │ ├── Fiber Template Benchmarks.xlsx │ └── Simple-TimeperOperation.png ├── dependabot.yml ├── labeler.yml ├── logo-dark.svg ├── logo.svg ├── release-drafter-template.yml ├── scripts │ └── sync_docs.sh └── workflows │ ├── auto-labeler.yml │ ├── benchmark.yml │ ├── dependabot_automerge.yml │ ├── golangci-lint.yml │ ├── gosec.yml │ ├── govulncheck.yml │ ├── release-drafter.yml │ ├── sync-docs.yml │ ├── test-ace.yml │ ├── test-amber.yml │ ├── test-django.yml │ ├── test-handlebars.yml │ ├── test-html.yml │ ├── test-jet.yml │ ├── test-mustache.yml │ ├── test-pug.yml │ └── test-slim.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── ace ├── README.md ├── ace.go ├── ace_test.go ├── go.mod ├── go.sum └── views │ ├── errors │ └── 404.ace │ ├── extended.ace │ ├── index.ace │ ├── layouts │ └── main.ace │ ├── partials │ ├── footer.ace │ └── header.ace │ ├── reload.ace │ └── simple.ace ├── amber ├── README.md ├── amber.go ├── amber_test.go ├── embed_views │ ├── embed.amber │ ├── footer.amber │ ├── header.amber │ └── layouts │ │ └── main.amber ├── go.mod ├── go.sum └── views │ ├── errors │ └── 404.amber │ ├── extended.amber │ ├── index.amber │ ├── layouts │ └── main.amber │ ├── partials │ ├── footer.amber │ └── header.amber │ ├── reload.amber │ └── simple.amber ├── django ├── README.md ├── django.go ├── django_test.go ├── go.mod ├── go.sum └── views │ ├── admin.django │ ├── ancestor.django │ ├── descendant.django │ ├── errors │ └── 404.django │ ├── extended.django │ ├── index.django │ ├── layouts │ └── main.django │ ├── partials │ ├── footer.django │ └── header.django │ ├── reload.django │ └── simple.django ├── go.mod ├── go.work ├── handlebars ├── README.md ├── go.mod ├── go.sum ├── handlebars.go ├── handlebars_test.go └── views │ ├── errors │ └── 404.hbs │ ├── extended.hbs │ ├── index.hbs │ ├── layouts │ └── main.hbs │ ├── partials │ ├── footer.hbs │ └── header.hbs │ ├── reload.hbs │ └── simple.hbs ├── html ├── README.md ├── TEMPLATES_CHEATSHEET.md ├── go.mod ├── go.sum ├── html.go ├── html_test.go └── views │ ├── admin.html │ ├── errors │ └── 404.html │ ├── extended.html │ ├── index.html │ ├── layouts │ ├── main.html │ └── nested │ │ ├── base.html │ │ └── main.html │ ├── partials │ ├── footer.html │ └── header.html │ ├── reload.html │ └── simple.html ├── jet ├── README.md ├── go.mod ├── go.sum ├── jet.go ├── jet_test.go └── views │ ├── errors │ └── 404.jet │ ├── extended.jet │ ├── index.jet │ ├── layouts │ └── main.jet │ ├── partials │ ├── footer.jet │ └── header.jet │ ├── reload.jet │ └── simple.jet ├── mustache ├── README.md ├── go.mod ├── go.sum ├── mustache.go ├── mustache_test.go └── views │ ├── errors │ └── 404.mustache │ ├── index.mustache │ ├── layouts │ └── main.mustache │ ├── partials │ ├── footer.mustache │ └── header.mustache │ ├── reload.mustache │ └── simple.mustache ├── pug ├── README.md ├── go.mod ├── go.sum ├── pug.go ├── pug_test.go └── views │ ├── errors │ └── 404.pug │ ├── extended.pug │ ├── index.pug │ ├── layouts │ └── main.pug │ ├── partials │ ├── footer.pug │ ├── header.pug │ └── meta.pug │ ├── reload.pug │ └── simple.pug ├── slim ├── README.md ├── go.mod ├── go.sum ├── slim.go ├── slim_test.go └── views │ ├── errors │ └── 404.slim │ ├── index.slim │ ├── layouts │ └── main.slim │ ├── partials │ ├── footer.slim │ └── header.slim │ ├── reload.slim │ └── simple.slim └── template.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gofiber/maintainers -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41B Bug Report" 2 | title: "\U0001F41B [Bug]: " 3 | description: Create a bug report to help us fix it. 4 | labels: ["☢️ Bug"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think Fiber template don't have a nice feature that you think, open the issue with **✏️ Feature Request** template. 14 | - Write your issue with clear and understandable English. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: "Bug Description" 19 | description: "A clear and detailed description of what the bug is." 20 | placeholder: "Explain your problem as clear and detailed." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: how-to-reproduce 25 | attributes: 26 | label: How to Reproduce 27 | description: "Steps to reproduce the behavior and what should be observed in the end." 28 | placeholder: "Tell us step by step how we can replicate your problem and what we should see in the end." 29 | value: | 30 | Steps to reproduce the behavior: 31 | 1. Go to '....' 32 | 2. Click on '....' 33 | 3. Do '....' 34 | 4. See '....' 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: expected-behavior 39 | attributes: 40 | label: Expected Behavior 41 | description: "A clear and detailed description of what you think should happens." 42 | placeholder: "Tell us what template should normally do." 43 | validations: 44 | required: true 45 | - type: input 46 | id: version 47 | attributes: 48 | label: "Template package Version" 49 | description: "Some bugs may be fixed in future template releases, so we have to know your template package version." 50 | placeholder: "Write your template version. (v1.0.0, v1.1.0...)" 51 | validations: 52 | required: true 53 | - type: textarea 54 | id: snippet 55 | attributes: 56 | label: "Code Snippet (optional)" 57 | description: "For some issues, we need to know some parts of your code." 58 | placeholder: "Share a code you think related to the issue." 59 | render: go 60 | value: | 61 | package main 62 | 63 | import "github.com/gofiber/template/%package%" 64 | 65 | func main() { 66 | // Steps to reproduce 67 | } 68 | - type: checkboxes 69 | id: terms 70 | attributes: 71 | label: "Checklist:" 72 | description: "By submitting this issue, you confirm that:" 73 | options: 74 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 75 | required: true 76 | - label: "I have checked for existing issues that describe my problem prior to opening this one." 77 | required: true 78 | - label: "I understand that improperly formatted bug reports may be closed without explanation." 79 | required: true 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 Feature Request" 2 | title: "\U0001F680 [Feature]: " 3 | description: Suggest an idea to improve this project. 4 | labels: ["✏️ Feature"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think this is just a bug, open the issue with **☢️ Bug Report** template. 14 | - Write your issue with clear and understandable English. 15 | - type: textarea 16 | id: description 17 | attributes: 18 | label: "Feature Description" 19 | description: "A clear and detailed description of the feature we need to do." 20 | placeholder: "Explain your feature as clear and detailed." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: additional-context 25 | attributes: 26 | label: "Additional Context (optional)" 27 | description: "If you have something else to describe, write them here." 28 | placeholder: "Write here what you can describe differently." 29 | - type: textarea 30 | id: snippet 31 | attributes: 32 | label: "Code Snippet (optional)" 33 | description: "Code snippet may be really helpful to describe some features." 34 | placeholder: "Share a code to explain the feature better." 35 | render: go 36 | value: | 37 | package main 38 | 39 | import "github.com/gofiber/template/%package%" 40 | 41 | func main() { 42 | // Steps to reproduce 43 | } 44 | - type: checkboxes 45 | id: terms 46 | attributes: 47 | label: "Checklist:" 48 | description: "By submitting this issue, you confirm that:" 49 | options: 50 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 51 | required: true 52 | - label: "I have checked for existing issues that describe my suggestion prior to opening this one." 53 | required: true 54 | - label: "I understand that improperly formatted feature requests may be closed without explanation." 55 | required: true 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yaml: -------------------------------------------------------------------------------- 1 | name: "🤔 Question" 2 | title: "\U0001F917 [Question]: " 3 | description: Ask a question so we can help you easily. 4 | labels: ["🤔 Question"] 5 | 6 | body: 7 | - type: markdown 8 | id: notice 9 | attributes: 10 | value: | 11 | ### Notice 12 | - Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord). 13 | - If you think this is just a bug, open the issue with **☢️ Bug Report** template. 14 | - If you think Fiber template don't have a nice feature that you think, open the issue with **✏️ Feature Request** template. 15 | - Write your issue with clear and understandable English. 16 | - type: textarea 17 | id: description 18 | attributes: 19 | label: "Question Description" 20 | description: "A clear and detailed description of the question." 21 | placeholder: "Explain your question as clear and detailed." 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: snippet 26 | attributes: 27 | label: "Code Snippet (optional)" 28 | description: "Code snippet may be really helpful to describe some features." 29 | placeholder: "Share a code to explain the feature better." 30 | render: go 31 | value: | 32 | package main 33 | 34 | import "github.com/gofiber/template/%package%" 35 | 36 | func main() { 37 | // Steps to reproduce 38 | } 39 | - type: checkboxes 40 | id: terms 41 | attributes: 42 | label: "Checklist:" 43 | description: "By submitting this issue, you confirm that:" 44 | options: 45 | - label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)." 46 | required: true 47 | - label: "I have checked for existing issues that describe my questions prior to opening this one." 48 | required: true 49 | - label: "I understand that improperly formatted questions may be closed without explanation." 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/data/Extended-TimeperOperation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofiber/template/0d8a8ca850b99877931bb7ab1a413d7a1251f7d3/.github/data/Extended-TimeperOperation.png -------------------------------------------------------------------------------- /.github/data/Fiber Template Benchmarks.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofiber/template/0d8a8ca850b99877931bb7ab1a413d7a1251f7d3/.github/data/Fiber Template Benchmarks.xlsx -------------------------------------------------------------------------------- /.github/data/Simple-TimeperOperation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofiber/template/0d8a8ca850b99877931bb7ab1a413d7a1251f7d3/.github/data/Simple-TimeperOperation.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#directories 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | open-pull-requests-limit: 15 8 | directory: "/" 9 | labels: 10 | - "🤖 Dependencies" 11 | schedule: 12 | interval: "daily" 13 | - package-ecosystem: "gomod" 14 | open-pull-requests-limit: 15 15 | directories: 16 | - "**/*" 17 | labels: 18 | - "🤖 Dependencies" 19 | schedule: 20 | interval: "daily" 21 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | version: v1 2 | labels: 3 | - label: '📒 Documentation' 4 | matcher: 5 | title: '\b(docs|doc:|\[doc\]|README|typos|comment|documentation)\b' 6 | - label: '☢️ Bug' 7 | matcher: 8 | title: '\b(fix|race|bug|missing|correct)\b' 9 | - label: '🧹 Updates' 10 | matcher: 11 | title: '\b(improve|update|refactor|deprecated|remove|unused|test)\b' 12 | - label: '🤖 Dependencies' 13 | matcher: 14 | title: '\b(bumb|bdependencies)\b' 15 | - label: '✏️ Feature' 16 | matcher: 17 | title: '\b(feature|feat|create|implement|add)\b' 18 | - label: '🤔 Question' 19 | matcher: 20 | title: '\b(question|how)\b' 21 | -------------------------------------------------------------------------------- /.github/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/release-drafter-template.yml: -------------------------------------------------------------------------------- 1 | name-template: '{{FOLDER}} - v$RESOLVED_VERSION' 2 | tag-template: '{{FOLDER}}/v$RESOLVED_VERSION' 3 | tag-prefix: {{FOLDER}}/v 4 | include-paths: 5 | - {{FOLDER}} 6 | categories: 7 | - title: '❗ Breaking Changes' 8 | labels: 9 | - '❗ BreakingChange' 10 | - title: '🚀 New' 11 | labels: 12 | - '✏️ Feature' 13 | - title: '🧹 Updates' 14 | labels: 15 | - '🧹 Updates' 16 | - '🤖 Dependencies' 17 | - title: '🐛 Fixes' 18 | labels: 19 | - '☢️ Bug' 20 | - title: '📚 Documentation' 21 | labels: 22 | - '📒 Documentation' 23 | change-template: '- $TITLE (#$NUMBER)' 24 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 25 | exclude-contributors: 26 | - dependabot 27 | - dependabot[bot] 28 | version-resolver: 29 | major: 30 | labels: 31 | - 'major' 32 | - '❗ BreakingChange' 33 | minor: 34 | labels: 35 | - 'minor' 36 | - '✏️ Feature' 37 | patch: 38 | labels: 39 | - 'patch' 40 | - '📒 Documentation' 41 | - '☢️ Bug' 42 | - '🤖 Dependencies' 43 | - '🧹 Updates' 44 | default: patch 45 | template: | 46 | $CHANGES 47 | 48 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...{{FOLDER}}/v$RESOLVED_VERSION 49 | 50 | Thank you $CONTRIBUTORS for making this update possible. 51 | -------------------------------------------------------------------------------- /.github/scripts/sync_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Some env variables 5 | BRANCH="master" 6 | REPO_URL="github.com/gofiber/docs.git" 7 | AUTHOR_EMAIL="github-actions[bot]@users.noreply.github.com" 8 | AUTHOR_USERNAME="github-actions[bot]" 9 | VERSION_FILE="template_versions.json" 10 | REPO_DIR="template" 11 | COMMIT_URL="https://github.com/gofiber/template" 12 | DOCUSAURUS_COMMAND="npm run docusaurus -- docs:version:template" 13 | 14 | # Set commit author 15 | git config --global user.email "${AUTHOR_EMAIL}" 16 | git config --global user.name "${AUTHOR_USERNAME}" 17 | 18 | git clone https://${TOKEN}@${REPO_URL} fiber-docs 19 | 20 | # Handle push event 21 | if [ "$EVENT" == "push" ]; then 22 | latest_commit=$(git rev-parse --short HEAD) 23 | 24 | for f in $(find . -type f -name "*.md" -not -path "./fiber-docs/*"); do 25 | log_output=$(git log --oneline "${BRANCH}" HEAD~1..HEAD --name-status -- "${f}") 26 | 27 | if [[ $log_output != "" || ! -f "fiber-docs/docs/${REPO_DIR}/$f" ]]; then 28 | mkdir -p fiber-docs/docs/${REPO_DIR}/$(dirname $f) 29 | cp "${f}" fiber-docs/docs/${REPO_DIR}/$f 30 | fi 31 | done 32 | 33 | # Handle release event 34 | elif [ "$EVENT" == "release" ]; then 35 | # Extract package name from tag 36 | package_name="${TAG_NAME%/*}" 37 | major_version="${TAG_NAME#*/}" 38 | major_version="${major_version%%.*}" 39 | 40 | # Form new version name 41 | new_version="${package_name}_${major_version}.x.x" 42 | 43 | cd fiber-docs/ || true 44 | npm ci 45 | 46 | # Check if contrib_versions.json exists and modify it if required 47 | if [[ -f $VERSION_FILE ]]; then 48 | jq --arg new_version "$new_version" 'del(.[] | select(. == $new_version))' $VERSION_FILE > temp.json && mv temp.json $VERSION_FILE 49 | fi 50 | 51 | # Run docusaurus versioning command 52 | $DOCUSAURUS_COMMAND "${new_version}" 53 | 54 | if [[ -f $VERSION_FILE ]]; then 55 | jq 'sort | reverse' ${VERSION_FILE} > temp.json && mv temp.json ${VERSION_FILE} 56 | fi 57 | fi 58 | 59 | # Push changes 60 | cd fiber-docs/ || true 61 | git add . 62 | if [[ $EVENT == "push" ]]; then 63 | git commit -m "Add docs from ${COMMIT_URL}/commit/${latest_commit}" 64 | elif [[ $EVENT == "release" ]]; then 65 | git commit -m "Sync docs for release ${COMMIT_URL}/releases/tag/${TAG_NAME}" 66 | fi 67 | 68 | MAX_RETRIES=5 69 | DELAY=5 70 | retry=0 71 | 72 | while ((retry < MAX_RETRIES)) 73 | do 74 | git push https://${TOKEN}@${REPO_URL} && break 75 | retry=$((retry + 1)) 76 | git pull --rebase 77 | sleep $DELAY 78 | done 79 | 80 | if ((retry == MAX_RETRIES)) 81 | then 82 | echo "Failed to push after $MAX_RETRIES attempts. Exiting with 1." 83 | exit 1 84 | fi 85 | 86 | -------------------------------------------------------------------------------- /.github/workflows/auto-labeler.yml: -------------------------------------------------------------------------------- 1 | name: Auto labeler 2 | on: 3 | issues: 4 | types: [ opened, edited, milestoned ] 5 | pull_request_target: 6 | types: [ opened ] 7 | permissions: 8 | contents: read 9 | issues: write 10 | pull-requests: write 11 | statuses: write 12 | checks: write 13 | jobs: 14 | labeler: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.actor != 'dependabot[bot]' }} 17 | steps: 18 | - name: Check Labels 19 | id: labeler 20 | uses: fuxingloh/multi-labeler@v4 21 | with: 22 | github-token: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "main" 8 | paths: 9 | - "**.go" 10 | - "**/go.mod" 11 | - ".github/workflows/benchmark.yml" 12 | pull_request: 13 | branches: 14 | - "*" 15 | paths: 16 | - "**.go" 17 | - "**/go.mod" 18 | - ".github/workflows/benchmark.yml" 19 | 20 | permissions: 21 | deployments: write 22 | contents: write 23 | 24 | jobs: 25 | Compare: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | 31 | - name: Install Go 32 | uses: actions/setup-go@v5 33 | with: 34 | # NOTE: Keep this in sync with the version from go.mod 35 | go-version: "1.20.x" 36 | cache: false 37 | 38 | - name: Run Benchmarks 39 | run: | 40 | set -o pipefail 41 | for d in */ ; do 42 | cd "$d" 43 | go test ./... -benchmem -run=^$ -bench . | tee -a ../output.txt 44 | cd .. 45 | done 46 | shell: bash 47 | 48 | - name: Get Previous Benchmark Results 49 | uses: actions/cache@v4 50 | with: 51 | path: ./cache 52 | key: ${{ runner.os }}-benchmark 53 | 54 | - name: Save Benchmark Results 55 | uses: benchmark-action/github-action-benchmark@v1.20.4 56 | with: 57 | # What benchmark tool the output.txt came from 58 | tool: 'go' 59 | # Where the output from the benchmark tool is stored 60 | output-file-path: output.txt 61 | # Where the previous data file is stored 62 | external-data-json-path: ./cache/benchmark-data.json 63 | # Secret for Github 64 | github-token: ${{ secrets.BENCHMARK_TOKEN }} 65 | # Directory that contains benchmark files on the GitHub pages branch 66 | benchmark-data-dir-path: "benchmarks" 67 | # Workflow will fail when an alert happens 68 | fail-on-alert: true 69 | comment-on-alert: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} 70 | #summary-always: ${{ github.event_name != 'push' && github.event_name != 'workflow_dispatch' }} 71 | auto-push: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} 72 | save-data-file: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} 73 | -------------------------------------------------------------------------------- /.github/workflows/dependabot_automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | 3 | on: 4 | pull_request 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | jobs: 11 | wait_for_checks: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.actor == 'dependabot[bot]' }} 14 | steps: 15 | - name: Wait for check is finished 16 | uses: lewagon/wait-on-check-action@v1.3.4 17 | id: wait_for_checks 18 | with: 19 | ref: ${{ github.event.pull_request.head.sha || github.sha }} 20 | running-workflow-name: wait_for_checks 21 | check-regexp: Tests 22 | repo-token: ${{ secrets.PR_TOKEN }} 23 | wait-interval: 10 24 | dependabot: 25 | needs: [wait_for_checks] 26 | name: Dependabot auto-merge 27 | runs-on: ubuntu-latest 28 | if: ${{ github.actor == 'dependabot[bot]' }} 29 | steps: 30 | - name: Dependabot metadata 31 | id: metadata 32 | uses: dependabot/fetch-metadata@v2.3.0 33 | with: 34 | github-token: "${{ secrets.PR_TOKEN }}" 35 | - name: Enable auto-merge for Dependabot PRs 36 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}} 37 | run: | 38 | gh pr review --approve "$PR_URL" 39 | gh pr merge --auto --merge "$PR_URL" 40 | env: 41 | PR_URL: ${{github.event.pull_request.html_url}} 42 | GITHUB_TOKEN: ${{secrets.PR_TOKEN}} 43 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: Golangci-Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "main" 8 | paths-ignore: 9 | - "**.md" 10 | - LICENSE 11 | - ".github/ISSUE_TEMPLATE/*.yml" 12 | - ".github/dependabot.yml" 13 | pull_request: 14 | branches: 15 | - "*" 16 | paths-ignore: 17 | - "**.md" 18 | - LICENSE 19 | - ".github/ISSUE_TEMPLATE/*.yml" 20 | - ".github/dependabot.yml" 21 | 22 | permissions: 23 | contents: read 24 | pull-requests: read 25 | checks: write 26 | 27 | jobs: 28 | golangci-lint: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Fetch Repository 32 | uses: actions/checkout@v4 33 | - name: Setup Go 34 | uses: actions/setup-go@v5 35 | with: 36 | # NOTE: Keep this in sync with the version from go.mod 37 | go-version: '1.20.x' 38 | cache: false 39 | - name: golangci-lint 40 | uses: golangci/golangci-lint-action@v6 41 | with: 42 | # NOTE: Keep this in sync with the version from .golangci.yml 43 | version: 'v1.56.2' 44 | -------------------------------------------------------------------------------- /.github/workflows/gosec.yml: -------------------------------------------------------------------------------- 1 | name: Gosec Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "main" 8 | paths-ignore: 9 | - "**.md" 10 | - LICENSE 11 | - ".github/ISSUE_TEMPLATE/*.yml" 12 | - ".github/dependabot.yml" 13 | pull_request: 14 | branches: 15 | - "*" 16 | paths-ignore: 17 | - "**.md" 18 | - LICENSE 19 | - ".github/ISSUE_TEMPLATE/*.yml" 20 | - ".github/dependabot.yml" 21 | 22 | jobs: 23 | detect-changes: 24 | runs-on: ubuntu-latest 25 | outputs: 26 | matrix: ${{ steps.changed-files.outputs.all_changed_files }} 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | - name: Changed Files 32 | uses: tj-actions/changed-files@v46 33 | id: changed-files 34 | with: 35 | files_ignore: | 36 | .github/** 37 | **.md 38 | json: true 39 | escape_json: false 40 | dir_names: true 41 | dir_names_max_depth: '1' 42 | dir_names_exclude_current_dir: true 43 | gosec-scan: 44 | runs-on: ubuntu-latest 45 | needs: detect-changes 46 | if: ${{ needs.detect-changes.outputs.matrix != 'null' }} 47 | env: 48 | GO111MODULE: on 49 | strategy: 50 | matrix: 51 | modules: ${{ fromJSON(needs.detect-changes.outputs.matrix) }} 52 | steps: 53 | - name: Fetch Repository 54 | uses: actions/checkout@v4 55 | - name: Install Go 56 | uses: actions/setup-go@v5 57 | with: 58 | go-version: '^1.21.x' 59 | check-latest: true 60 | cache: false 61 | - name: Install gosec 62 | run: go install github.com/securego/gosec/v2/cmd/gosec@latest 63 | - name: Run gosec 64 | working-directory: ${{ matrix.modules }} 65 | run: gosec ./... 66 | -------------------------------------------------------------------------------- /.github/workflows/govulncheck.yml: -------------------------------------------------------------------------------- 1 | name: Govulncheck Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "main" 8 | paths-ignore: 9 | - "**.md" 10 | - LICENSE 11 | - ".github/ISSUE_TEMPLATE/*.yml" 12 | - ".github/dependabot.yml" 13 | pull_request: 14 | branches: 15 | - "*" 16 | paths-ignore: 17 | - "**.md" 18 | - LICENSE 19 | - ".github/ISSUE_TEMPLATE/*.yml" 20 | - ".github/dependabot.yml" 21 | 22 | jobs: 23 | govulncheck-check: 24 | runs-on: ubuntu-latest 25 | env: 26 | GO111MODULE: on 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: 'stable' 34 | check-latest: true 35 | cache: false 36 | - name: Install Govulncheck 37 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 38 | - name: Run Govulncheck 39 | run: govulncheck ./... -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter (All) 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | changes: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | pull-requests: read 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Generate filters 19 | id: filter-setup 20 | run: | 21 | filters=$(find . -maxdepth 1 -type d ! -path ./.git ! -path . -exec basename {} \; | grep -v '^\.' | awk '{printf "%s: \"%s/**\"\n", $1, $1}') 22 | echo "filters<> $GITHUB_OUTPUT 23 | echo "$filters" >> $GITHUB_OUTPUT 24 | echo "EOF" >> $GITHUB_OUTPUT 25 | shell: bash 26 | - name: Filter changes 27 | id: filter 28 | uses: dorny/paths-filter@v3 29 | with: 30 | filters: ${{ steps.filter-setup.outputs.filters }} 31 | 32 | outputs: 33 | packages: ${{ steps.filter.outputs.changes || '[]' }} 34 | 35 | release-drafter: 36 | needs: changes 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 30 39 | if: needs.changes.outputs.packages != '[]' # Ensure job runs only if there are changes 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | strategy: 43 | matrix: 44 | package: ${{ fromJSON(needs.changes.outputs.packages || '[]') }} 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v4 48 | 49 | - name: Generate dynamic config from template 50 | id: generate-config 51 | run: | 52 | folder="${{ matrix.package }}" 53 | sed "s|{{FOLDER}}|$folder|g" .github/release-drafter-template.yml > .github/release-drafter-$folder.yml 54 | echo "config<> $GITHUB_OUTPUT 55 | cat .github/release-drafter-$folder.yml >> $GITHUB_OUTPUT 56 | echo "EOF" >> $GITHUB_OUTPUT 57 | 58 | - name: Use dynamic release-drafter configuration 59 | uses: ReneWerner87/release-drafter@6dec4ceb1fb86b6514f11a2e7a39e1dedce709d0 60 | with: 61 | config: ${{ steps.generate-config.outputs.config }} 62 | -------------------------------------------------------------------------------- /.github/workflows/sync-docs.yml: -------------------------------------------------------------------------------- 1 | name: 'Sync docs' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | paths: 9 | - '**/*.md' 10 | release: 11 | types: [published] 12 | branches: 13 | - '*/v[0-9]+.[0-9]+.[0-9]+' 14 | 15 | jobs: 16 | sync-docs: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | ref: ${{ github.event.pull_request.head.sha }} 23 | fetch-depth: 2 24 | 25 | - name: Setup Node.js environment 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '18' 29 | 30 | - name: Sync docs 31 | run: ./.github/scripts/sync_docs.sh 32 | env: 33 | EVENT: ${{ github.event_name }} 34 | TAG_NAME: ${{ github.ref_name }} 35 | TOKEN: ${{ secrets.DOC_SYNC_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/test-ace.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'ace/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'ace/**' 13 | name: Tests Ace 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./ace && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-amber.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'amber/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'amber/**' 13 | name: Tests Amber 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./amber && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-django.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'django/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'django/**' 13 | name: Tests Django 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.18.x 20 | - 1.19.x 21 | - 1.20.x 22 | - 1.21.x 23 | - 1.22.x 24 | platform: [ ubuntu-latest, windows-latest ] 25 | runs-on: ${{ matrix.platform }} 26 | steps: 27 | - name: Fetch Repository 28 | uses: actions/checkout@v4 29 | - name: Install Go 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version: '${{ matrix.go-version }}' 33 | - name: Run Test 34 | run: cd ./django && go test ./... -race -v 35 | -------------------------------------------------------------------------------- /.github/workflows/test-handlebars.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'handlebars/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'handlebars/**' 13 | name: Tests Handlebars 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./handlebars && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-html.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'html/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'html/**' 13 | name: Tests Html 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./html && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-jet.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'jet/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'jet/**' 13 | name: Tests Jet 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./jet && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-mustache.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'mustache/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'mustache/**' 13 | name: Tests Mustache 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./mustache && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-pug.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'pug/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'pug/**' 13 | name: Tests Pug 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./pug && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.github/workflows/test-slim.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | - main 6 | paths: 7 | - 'slim/**' 8 | pull_request: 9 | branches: 10 | - '*' 11 | paths: 12 | - 'slim/**' 13 | name: Tests Slim 14 | jobs: 15 | Tests: 16 | strategy: 17 | matrix: 18 | go-version: 19 | - 1.17.x 20 | - 1.18.x 21 | - 1.19.x 22 | - 1.20.x 23 | - 1.21.x 24 | - 1.22.x 25 | platform: [ ubuntu-latest, windows-latest ] 26 | runs-on: ${{ matrix.platform }} 27 | steps: 28 | - name: Fetch Repository 29 | uses: actions/checkout@v4 30 | - name: Install Go 31 | uses: actions/setup-go@v5 32 | with: 33 | go-version: '${{ matrix.go-version }}' 34 | - name: Run Test 35 | run: cd ./slim && go test ./... -race -v 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | *.tmp 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # IDE files 16 | .vscode 17 | .DS_Store 18 | .idea 19 | 20 | # Misc 21 | *.fiber.gz 22 | *.fasthttp.gz 23 | *.pprof 24 | *.workspace 25 | 26 | # Dependencies 27 | /vendor/ 28 | vendor/ 29 | vendor 30 | /Godeps/ 31 | 32 | # test files 33 | */views/ShouldReload.* 34 | 35 | # Go workspace file 36 | go.work.sum 37 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Created based on v1.56.1 2 | # NOTE: Keep this in sync with the version in .github/workflows/linter.yml 3 | 4 | run: 5 | modules-download-mode: readonly 6 | skip-dirs-use-default: false 7 | skip-dirs: 8 | - internal 9 | 10 | output: 11 | sort-results: true 12 | 13 | linters-settings: 14 | errcheck: 15 | check-type-assertions: true 16 | check-blank: true 17 | disable-default-exclusions: true 18 | exclude-functions: 19 | - '(*bytes.Buffer).Write' # always returns nil error 20 | - '(*github.com/valyala/bytebufferpool.ByteBuffer).Write' # always returns nil error 21 | - '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteByte' # always returns nil error 22 | - '(*github.com/valyala/bytebufferpool.ByteBuffer).WriteString' # always returns nil error 23 | 24 | errchkjson: 25 | report-no-exported: true 26 | 27 | exhaustive: 28 | default-signifies-exhaustive: true 29 | 30 | forbidigo: 31 | forbid: 32 | - ^(fmt\.Print(|f|ln)|print|println)$ 33 | - 'http\.Default(Client|Transport)' 34 | # TODO: Eventually enable these patterns 35 | # - 'time\.Sleep' 36 | # - 'panic' 37 | 38 | gocritic: 39 | disabled-checks: 40 | - ifElseChain 41 | 42 | gofumpt: 43 | module-path: github.com/gofiber/template 44 | extra-rules: true 45 | 46 | gosec: 47 | excludes: 48 | - G104 # Provided by errcheck 49 | config: 50 | global: 51 | audit: true 52 | 53 | depguard: 54 | rules: 55 | main: 56 | deny: 57 | - pkg: flag 58 | desc: '`flag` package is only allowed in main.go' 59 | - pkg: io/ioutil 60 | desc: '`io/ioutil` package is deprecated, use the `io` and `os` package instead' 61 | 62 | govet: 63 | check-shadowing: true 64 | enable-all: true 65 | disable: 66 | - shadow 67 | - fieldalignment 68 | - loopclosure 69 | 70 | grouper: 71 | import-require-single-import: true 72 | import-require-grouping: true 73 | 74 | misspell: 75 | locale: US 76 | 77 | nolintlint: 78 | require-explanation: true 79 | require-specific: true 80 | 81 | nonamedreturns: 82 | report-error-in-defer: true 83 | 84 | predeclared: 85 | q: true 86 | 87 | promlinter: 88 | strict: true 89 | 90 | revive: 91 | enable-all-rules: true 92 | rules: 93 | # Provided by gomnd linter 94 | - name: add-constant 95 | disabled: true 96 | - name: argument-limit 97 | disabled: true 98 | # Provided by bidichk 99 | - name: banned-characters 100 | disabled: true 101 | - name: cognitive-complexity 102 | disabled: true 103 | - name: comment-spacings 104 | disabled: true # TODO https://github.com/gofiber/fiber/issues/2816 105 | - name: cyclomatic 106 | disabled: true 107 | - name: early-return 108 | severity: warning 109 | disabled: true 110 | - name: exported 111 | disabled: true 112 | - name: file-header 113 | disabled: true 114 | - name: function-result-limit 115 | disabled: true 116 | - name: function-length 117 | disabled: true 118 | - name: line-length-limit 119 | disabled: true 120 | - name: max-public-structs 121 | disabled: true 122 | - name: modifies-parameter 123 | disabled: true 124 | - name: nested-structs 125 | disabled: true 126 | - name: package-comments 127 | disabled: true 128 | - name: unchecked-type-assertion 129 | disabled: true # TODO https://github.com/gofiber/fiber/issues/2816 130 | # Provided by errcheck 131 | - name: unhandled-error 132 | disabled: true 133 | - name: use-any # TODO Enable for v3 release 134 | disabled: true 135 | 136 | stylecheck: 137 | checks: 138 | - all 139 | - -ST1000 140 | - -ST1020 141 | - -ST1021 142 | - -ST1022 143 | 144 | tagliatelle: 145 | case: 146 | rules: 147 | json: snake 148 | 149 | tenv: 150 | all: true 151 | 152 | #unparam: 153 | # check-exported: true 154 | 155 | wrapcheck: 156 | ignorePackageGlobs: 157 | - github.com/gofiber/fiber/* 158 | - github.com/valyala/fasthttp 159 | 160 | issues: 161 | exclude-use-default: false 162 | 163 | linters: 164 | disable: 165 | - spancheck 166 | enable: 167 | - asasalint 168 | - asciicheck 169 | - bidichk 170 | - bodyclose 171 | - containedctx 172 | - contextcheck 173 | - depguard 174 | - dogsled 175 | - durationcheck 176 | - errcheck 177 | - errchkjson 178 | - errname 179 | - errorlint 180 | - execinquery 181 | - exhaustive 182 | - exportloopref 183 | - forbidigo 184 | - forcetypeassert 185 | - gochecksumtype 186 | - goconst 187 | - gocritic 188 | - gofmt 189 | - gofumpt 190 | - goimports 191 | - gomoddirectives 192 | - goprintffuncname 193 | - gosec 194 | - gosimple 195 | - gosmopolitan 196 | - govet 197 | - grouper 198 | - inamedparam 199 | - loggercheck 200 | - mirror 201 | - misspell 202 | - nakedret 203 | - nilerr 204 | - nilnil 205 | - noctx 206 | - nolintlint 207 | - nonamedreturns 208 | - nosprintfhostport 209 | - perfsprint 210 | - predeclared 211 | - promlinter 212 | - reassign 213 | - revive 214 | - rowserrcheck 215 | - sqlclosecheck 216 | - staticcheck 217 | - stylecheck 218 | - tagalign 219 | - tagliatelle 220 | - testifylint 221 | - testpackage 222 | - thelper 223 | - tparallel 224 | - typecheck 225 | - unconvert 226 | - unparam 227 | - unused 228 | - usestdlibvars 229 | - wastedassign 230 | - whitespace 231 | - wrapcheck 232 | - tenv 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fiber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ace/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: ace 3 | title: Ace 4 | --- 5 | 6 | ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=ace*) 7 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 8 | ![Test](https://github.com/gofiber/template/workflows/Tests%20Ace/badge.svg) 9 | 10 | Ace is a template engine create by [yossi](https://github.com/yosssi/ace), to see the original syntax documentation please [click here](https://github.com/yosssi/ace/blob/master/documentation/syntax.md) 11 | 12 | ### Basic Example 13 | 14 | _**./views/index.ace**_ 15 | ```html 16 | = include ./views/partials/header . 17 | 18 | h1 {{.Title}} 19 | 20 | = include ./views/partials/footer . 21 | ``` 22 | _**./views/partials/header.ace**_ 23 | ```html 24 | h1 Header 25 | ``` 26 | _**./views/partials/footer.ace**_ 27 | ```html 28 | h1 Footer 29 | ``` 30 | _**./views/layouts/main.ace**_ 31 | ```html 32 | = doctype html 33 | html 34 | head 35 | title Main 36 | body 37 | {{embed}} 38 | ``` 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "log" 45 | 46 | "github.com/gofiber/fiber/v2" 47 | "github.com/gofiber/template/ace/v2" 48 | ) 49 | 50 | func main() { 51 | // Create a new engine 52 | engine := ace.New("./views", ".ace") 53 | 54 | // Or from an embedded system 55 | // See github.com/gofiber/embed for examples 56 | // engine := html.NewFileSystem(http.Dir("./views", ".ace")) 57 | 58 | // Pass the engine to the Views 59 | app := fiber.New(fiber.Config{ 60 | Views: engine, 61 | }) 62 | 63 | app.Get("/", func(c *fiber.Ctx) error { 64 | // Render index 65 | return c.Render("index", fiber.Map{ 66 | "Title": "Hello, World!", 67 | }) 68 | }) 69 | 70 | app.Get("/layout", func(c *fiber.Ctx) error { 71 | // Render index within layouts/main 72 | return c.Render("index", fiber.Map{ 73 | "Title": "Hello, World!", 74 | }, "layouts/main") 75 | }) 76 | 77 | log.Fatal(app.Listen(":3000")) 78 | } 79 | 80 | ``` 81 | -------------------------------------------------------------------------------- /ace/ace.go: -------------------------------------------------------------------------------- 1 | package ace 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | core "github.com/gofiber/template" 14 | "github.com/gofiber/utils" 15 | "github.com/yosssi/ace" 16 | ) 17 | 18 | // Engine struct 19 | type Engine struct { 20 | core.Engine 21 | // templates 22 | Templates *template.Template 23 | } 24 | 25 | // New returns an Ace render engine for Fiber 26 | func New(directory, extension string) *Engine { 27 | engine := &Engine{ 28 | Engine: core.Engine{ 29 | Left: "{{", 30 | Right: "}}", 31 | Directory: directory, 32 | Extension: extension, 33 | LayoutName: "embed", 34 | Funcmap: make(map[string]interface{}), 35 | }, 36 | } 37 | engine.AddFunc(engine.LayoutName, func() error { 38 | return fmt.Errorf("content called unexpectedly") 39 | }) 40 | return engine 41 | } 42 | 43 | // NewFileSystem returns an Ace render engine for Fiber with file system 44 | func NewFileSystem(fs http.FileSystem, extension string) *Engine { 45 | engine := &Engine{ 46 | Engine: core.Engine{ 47 | Left: "{{", 48 | Right: "}}", 49 | Directory: "/", 50 | FileSystem: fs, 51 | Extension: extension, 52 | LayoutName: "embed", 53 | Funcmap: make(map[string]interface{}), 54 | }, 55 | } 56 | engine.AddFunc(engine.LayoutName, func() error { 57 | return fmt.Errorf("content called unexpectedly") 58 | }) 59 | return engine 60 | } 61 | 62 | // Load parses the templates to the engine. 63 | func (e *Engine) Load() error { 64 | // race safe 65 | e.Mutex.Lock() 66 | defer e.Mutex.Unlock() 67 | 68 | e.Templates = template.New(e.Directory) 69 | 70 | e.Templates.Delims(e.Left, e.Right) 71 | e.Templates.Funcs(e.Funcmap) 72 | 73 | // Loop trough each directory and register template files 74 | walkFn := func(path string, info os.FileInfo, err error) error { 75 | path = strings.TrimRight(path, ".") 76 | // Return error if exist 77 | if err != nil { 78 | return err 79 | } 80 | // Skip file if it's a directory or has no file info 81 | if info == nil || info.IsDir() { 82 | return nil 83 | } 84 | // Skip file if it does not equal the given template extension 85 | if len(e.Extension) >= len(path) || path[len(path)-len(e.Extension):] != e.Extension { 86 | return nil 87 | } 88 | // Get the relative file path 89 | // ./views/html/index.tmpl -> index.tmpl 90 | rel, err := filepath.Rel(e.Directory, path) 91 | if err != nil { 92 | return err 93 | } 94 | // Reverse slashes '\' -> '/' and 95 | // partials\footer.tmpl -> partials/footer.tmpl 96 | name := filepath.ToSlash(rel) 97 | // Remove ext from name 'index.tmpl' -> 'index' 98 | name = strings.TrimSuffix(name, e.Extension) 99 | // name = strings.Replace(name, e.Extension, "", -1) 100 | // Read the file 101 | // #gosec G304 102 | buf, err := utils.ReadFile(path, e.FileSystem) 103 | if err != nil { 104 | return err 105 | } 106 | baseFile := name + ".ace" 107 | base := ace.NewFile(baseFile, buf) 108 | inner := ace.NewFile("", []byte{}) 109 | src := ace.NewSource(base, inner, []*ace.File{}) 110 | rslt, err := ace.ParseSource(src, nil) 111 | if err != nil { 112 | return err 113 | } 114 | atmpl, err := ace.CompileResult(name, rslt, &ace.Options{ 115 | Extension: e.Extension[1:], 116 | FuncMap: e.Funcmap, 117 | DelimLeft: e.Left, 118 | DelimRight: e.Right, 119 | }) 120 | if err != nil { 121 | return err 122 | } 123 | _, err = e.Templates.New(name).Parse(atmpl.Lookup(name).Tree.Root.String()) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | if e.Verbose { 129 | log.Printf("views: parsed template: %s\n", name) 130 | } 131 | return err 132 | } 133 | 134 | // notify Engine that we parsed all templates 135 | e.Loaded = true 136 | 137 | if e.FileSystem != nil { 138 | return utils.Walk(e.FileSystem, e.Directory, walkFn) 139 | } 140 | return filepath.Walk(e.Directory, walkFn) 141 | } 142 | 143 | // Render will render the template by name 144 | func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error { 145 | // Check if templates need to be loaded/reloaded 146 | if e.PreRenderCheck() { 147 | ace.FlushCache() 148 | if err := e.Load(); err != nil { 149 | return err 150 | } 151 | } 152 | 153 | // Acquire read lock for accessing the template 154 | e.Mutex.RLock() 155 | tmpl := e.Templates.Lookup(name) 156 | e.Mutex.RUnlock() 157 | 158 | if tmpl == nil { 159 | return fmt.Errorf("render: template %s does not exist", name) 160 | } 161 | 162 | // Lock while executing layout 163 | e.Mutex.Lock() 164 | defer e.Mutex.Unlock() 165 | 166 | // Handle layout if specified 167 | if len(layout) > 0 && layout[0] != "" { 168 | lay := e.Templates.Lookup(layout[0]) 169 | if lay == nil { 170 | return fmt.Errorf("render: layout %s does not exist", layout[0]) 171 | } 172 | 173 | lay.Funcs(map[string]interface{}{ 174 | e.LayoutName: func() error { 175 | return tmpl.Execute(out, binding) 176 | }, 177 | }) 178 | return lay.Execute(out, binding) 179 | } 180 | 181 | return tmpl.Execute(out, binding) 182 | } 183 | -------------------------------------------------------------------------------- /ace/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/template/ace/v2 2 | 3 | go 1.17 4 | 5 | require github.com/yosssi/ace v0.0.5 6 | 7 | require ( 8 | github.com/gofiber/template v1.8.3 9 | github.com/gofiber/utils v1.1.0 10 | github.com/stretchr/testify v1.10.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | 19 | replace github.com/gofiber/template => ../. 20 | -------------------------------------------------------------------------------- /ace/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= 5 | github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= 6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 10 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 11 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 12 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 13 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 14 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 15 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 16 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 17 | github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= 18 | github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 23 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | -------------------------------------------------------------------------------- /ace/views/errors/404.ace: -------------------------------------------------------------------------------- 1 | h1 {{.Title}} -------------------------------------------------------------------------------- /ace/views/extended.ace: -------------------------------------------------------------------------------- 1 | = include partials/header . 2 | {{if isAdmin .User}} 3 | h1 Hello, Admin! 4 | {{else}} 5 | h1 Access denied! 6 | {{end}} 7 | = include partials/footer . 8 | -------------------------------------------------------------------------------- /ace/views/index.ace: -------------------------------------------------------------------------------- 1 | = include partials/header . 2 | h1 {{.Title}} 3 | = include partials/footer . 4 | -------------------------------------------------------------------------------- /ace/views/layouts/main.ace: -------------------------------------------------------------------------------- 1 | = doctype html 2 | html 3 | head 4 | title Main 5 | body 6 | {{embed}} 7 | -------------------------------------------------------------------------------- /ace/views/partials/footer.ace: -------------------------------------------------------------------------------- 1 | h2 Footer -------------------------------------------------------------------------------- /ace/views/partials/header.ace: -------------------------------------------------------------------------------- 1 | h2 Header -------------------------------------------------------------------------------- /ace/views/reload.ace: -------------------------------------------------------------------------------- 1 | before reload 2 | -------------------------------------------------------------------------------- /ace/views/simple.ace: -------------------------------------------------------------------------------- 1 | h1 {{.Title}} 2 | -------------------------------------------------------------------------------- /amber/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: amber 3 | title: Amber 4 | --- 5 | 6 | ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=amber*) 7 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 8 | ![Test](https://github.com/gofiber/template/workflows/Tests%20Amber/badge.svg) 9 | 10 | Amber is a template engine create by [eknkc](https://github.com/eknkc/amber), to see the original syntax documentation please [click here](https://github.com/eknkc/amber#tags) 11 | 12 | ### Basic Example 13 | 14 | _**./views/index.amber**_ 15 | ```html 16 | import ./views/partials/header 17 | 18 | h1 #{Title} 19 | 20 | import ./views/partials/footer 21 | ``` 22 | _**./views/partials/header.amber**_ 23 | ```html 24 | h1 Header 25 | ``` 26 | _**./views/partials/footer.amber**_ 27 | ```html 28 | h1 Footer 29 | ``` 30 | _**./views/layouts/main.amber**_ 31 | ```html 32 | doctype html 33 | html 34 | head 35 | title Main 36 | body 37 | #{embed()} 38 | ``` 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "log" 45 | 46 | "github.com/gofiber/fiber/v2" 47 | "github.com/gofiber/template/amber/v2" 48 | ) 49 | 50 | func main() { 51 | // Create a new engine 52 | engine := amber.New("./views", ".amber") 53 | 54 | // Or from an embedded system 55 | // See github.com/gofiber/embed for examples 56 | // engine := html.NewFileSystem(http.Dir("./views", ".amber")) 57 | 58 | // Pass the engine to the Views 59 | app := fiber.New(fiber.Config{ 60 | Views: engine, 61 | }) 62 | 63 | app.Get("/", func(c *fiber.Ctx) error { 64 | // Render index 65 | return c.Render("index", fiber.Map{ 66 | "Title": "Hello, World!", 67 | }) 68 | }) 69 | 70 | app.Get("/layout", func(c *fiber.Ctx) error { 71 | // Render index within layouts/main 72 | return c.Render("index", fiber.Map{ 73 | "Title": "Hello, World!", 74 | }, "layouts/main") 75 | }) 76 | 77 | log.Fatal(app.Listen(":3000")) 78 | } 79 | 80 | ``` 81 | -------------------------------------------------------------------------------- /amber/amber.go: -------------------------------------------------------------------------------- 1 | package amber 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/eknkc/amber" 14 | core "github.com/gofiber/template" 15 | "github.com/gofiber/utils" 16 | ) 17 | 18 | // Engine struct 19 | type Engine struct { 20 | core.Engine 21 | // templates 22 | Templates map[string]*template.Template 23 | } 24 | 25 | // New returns an Amber render engine for Fiber 26 | func New(directory, extension string) *Engine { 27 | engine := &Engine{ 28 | Engine: core.Engine{ 29 | Directory: directory, 30 | Extension: extension, 31 | LayoutName: "embed", 32 | Funcmap: make(map[string]interface{}), 33 | }, 34 | } 35 | engine.AddFunc(engine.LayoutName, func() error { 36 | return fmt.Errorf("layoutName called unexpectedly") 37 | }) 38 | return engine 39 | } 40 | 41 | // NewFileSystem returns an Amber render engine for Fiber with file system 42 | func NewFileSystem(fs http.FileSystem, extension string) *Engine { 43 | engine := &Engine{ 44 | Engine: core.Engine{ 45 | Directory: "/", 46 | FileSystem: fs, 47 | Extension: extension, 48 | LayoutName: "embed", 49 | Funcmap: make(map[string]interface{}), 50 | }, 51 | } 52 | engine.AddFunc(engine.LayoutName, func() error { 53 | return fmt.Errorf("layoutName called unexpectedly") 54 | }) 55 | return engine 56 | } 57 | 58 | // Load parses the templates to the engine. 59 | func (e *Engine) Load() error { 60 | // race safe 61 | e.Mutex.Lock() 62 | defer e.Mutex.Unlock() 63 | 64 | e.Templates = make(map[string]*template.Template) 65 | 66 | // Set template settings 67 | // prepare the global amber funcs 68 | funcs := template.FuncMap{} 69 | 70 | for k, v := range amber.FuncMap { // add the amber's default funcs 71 | funcs[k] = v 72 | } 73 | 74 | for k, v := range e.Funcmap { 75 | funcs[k] = v 76 | } 77 | 78 | amber.FuncMap = funcs //nolint:reassign // this is fine, as long as it's not run in parallel in a test. 79 | 80 | // Loop trough each directory and register template files 81 | walkFn := func(path string, info os.FileInfo, err error) error { 82 | // Return error if exist 83 | if err != nil { 84 | return err 85 | } 86 | // Skip file if it's a directory or has no file info 87 | if info == nil || info.IsDir() { 88 | return nil 89 | } 90 | // Skip file if it does not equal the given template extension 91 | if len(e.Extension) >= len(path) || path[len(path)-len(e.Extension):] != e.Extension { 92 | return nil 93 | } 94 | // Get the relative file path 95 | // ./views/html/index.tmpl -> index.tmpl 96 | rel, err := filepath.Rel(e.Directory, path) 97 | if err != nil { 98 | return err 99 | } 100 | // Reverse slashes '\' -> '/' and 101 | // partials\footer.tmpl -> partials/footer.tmpl 102 | name := filepath.ToSlash(rel) 103 | // Remove ext from name 'index.tmpl' -> 'index' 104 | name = strings.TrimSuffix(name, e.Extension) 105 | // name = strings.Replace(name, e.Extension, "", -1) 106 | // Read the file 107 | // #gosec G304 108 | buf, err := utils.ReadFile(path, e.FileSystem) 109 | if err != nil { 110 | return err 111 | } 112 | // Create new template associated with the current one 113 | // This enable use to invoke other templates {{ template .. }} 114 | option := amber.DefaultOptions 115 | if e.FileSystem != nil { 116 | option.VirtualFilesystem = e.FileSystem 117 | } 118 | tmpl, err := amber.CompileData(buf, name, option) 119 | if err != nil { 120 | return err 121 | } 122 | e.Templates[name] = tmpl 123 | 124 | if e.Verbose { 125 | log.Printf("views: parsed template: %s\n", name) 126 | } 127 | return err 128 | } 129 | 130 | // notify Engine that we parsed all templates 131 | e.Loaded = true 132 | 133 | if e.FileSystem != nil { 134 | return utils.Walk(e.FileSystem, e.Directory, walkFn) 135 | } 136 | return filepath.Walk(e.Directory, walkFn) 137 | } 138 | 139 | // Render will execute the template name along with the given values. 140 | func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error { 141 | // Check if templates need to be loaded/reloaded 142 | if e.PreRenderCheck() { 143 | if err := e.Load(); err != nil { 144 | return err 145 | } 146 | } 147 | 148 | // Acquire read lock for accessing the template 149 | e.Mutex.RLock() 150 | tmpl := e.Templates[name] 151 | e.Mutex.RUnlock() 152 | 153 | if tmpl == nil { 154 | return fmt.Errorf("render: template %s does not exist", name) 155 | } 156 | 157 | // Lock while executing layout 158 | e.Mutex.Lock() 159 | defer e.Mutex.Unlock() 160 | 161 | if len(layout) > 0 && layout[0] != "" { 162 | lay := e.Templates[layout[0]] 163 | if lay == nil { 164 | return fmt.Errorf("render: LayoutName %s does not exist", layout[0]) 165 | } 166 | lay.Funcs(map[string]interface{}{ 167 | e.LayoutName: func() error { 168 | return tmpl.Execute(out, binding) 169 | }, 170 | }) 171 | return lay.Execute(out, binding) 172 | } 173 | return tmpl.Execute(out, binding) 174 | } 175 | -------------------------------------------------------------------------------- /amber/amber_test.go: -------------------------------------------------------------------------------- 1 | //nolint:paralleltest // running these in parallel causes a data race 2 | package amber 3 | 4 | import ( 5 | "bytes" 6 | "net/http" 7 | "os" 8 | "regexp" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | const ( 16 | admin = "admin" 17 | complexexpect = `Main

Header

Hello, World!

Footer

` 18 | ) 19 | 20 | func trim(str string) string { 21 | trimmed := strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(str, " ")) 22 | trimmed = strings.ReplaceAll(trimmed, " <", "<") 23 | trimmed = strings.ReplaceAll(trimmed, "> ", ">") 24 | return trimmed 25 | } 26 | 27 | func Test_Render(t *testing.T) { 28 | engine := New("./views", ".amber") 29 | require.NoError(t, engine.Load()) 30 | 31 | // Partials 32 | var buf bytes.Buffer 33 | err := engine.Render(&buf, "index", map[string]interface{}{ 34 | "Title": "Hello, World!", 35 | }) 36 | require.NoError(t, err) 37 | 38 | expect := `

Header

Hello, World!

Footer

` 39 | result := trim(buf.String()) 40 | require.Equal(t, expect, result) 41 | 42 | // Single 43 | buf.Reset() 44 | err = engine.Render(&buf, "errors/404", map[string]interface{}{ 45 | "Title": "Hello, World!", 46 | }) 47 | require.NoError(t, err) 48 | 49 | expect = `

Hello, World!

` 50 | result = trim(buf.String()) 51 | require.Equal(t, expect, result) 52 | } 53 | 54 | func Test_Layout(t *testing.T) { 55 | engine := New("./views", ".amber") 56 | engine.Debug(true) 57 | require.NoError(t, engine.Load()) 58 | 59 | var buf bytes.Buffer 60 | err := engine.Render(&buf, "index", map[string]interface{}{ 61 | "Title": "Hello, World!", 62 | }, "layouts/main") 63 | require.NoError(t, err) 64 | 65 | result := trim(buf.String()) 66 | require.Equal(t, complexexpect, result) 67 | } 68 | 69 | func Test_Empty_Layout(t *testing.T) { 70 | engine := New("./views", ".amber") 71 | engine.Debug(true) 72 | require.NoError(t, engine.Load()) 73 | 74 | var buf bytes.Buffer 75 | err := engine.Render(&buf, "index", map[string]interface{}{ 76 | "Title": "Hello, World!", 77 | }, "") 78 | require.NoError(t, err) 79 | 80 | expect := `

Header

Hello, World!

Footer

` 81 | result := trim(buf.String()) 82 | require.Equal(t, expect, result) 83 | } 84 | 85 | func Test_FileSystem(t *testing.T) { 86 | engine := NewFileSystem(http.Dir("./views"), ".amber") 87 | engine.Debug(true) 88 | require.NoError(t, engine.Load()) 89 | 90 | var buf bytes.Buffer 91 | err := engine.Render(&buf, "index", map[string]interface{}{ 92 | "Title": "Hello, World!", 93 | }, "layouts/main") 94 | require.NoError(t, err) 95 | 96 | result := trim(buf.String()) 97 | require.Equal(t, complexexpect, result) 98 | } 99 | 100 | func Test_Reload(t *testing.T) { 101 | engine := NewFileSystem(http.Dir("./views"), ".amber") 102 | engine.Reload(true) // Optional. Default: false 103 | 104 | engine.AddFunc("isAdmin", func(user string) bool { 105 | return user == "admin" 106 | }) 107 | require.NoError(t, engine.Load()) 108 | 109 | err := os.WriteFile("./views/ShouldReload.amber", []byte("after ShouldReload\n"), 0o600) 110 | require.NoError(t, err) 111 | 112 | defer func() { 113 | err := os.WriteFile("./views/ShouldReload.amber", []byte("before ShouldReload\n"), 0o600) 114 | require.NoError(t, err) 115 | }() 116 | 117 | require.NoError(t, engine.Load()) 118 | 119 | var buf bytes.Buffer 120 | err = engine.Render(&buf, "ShouldReload", nil) 121 | require.NoError(t, err) 122 | 123 | expect := "ShouldReload" 124 | result := trim(buf.String()) 125 | require.Equal(t, expect, result) 126 | } 127 | 128 | func Test_AddFuncMap(t *testing.T) { 129 | // Create a temporary directory 130 | dir, err := os.MkdirTemp(".", "") 131 | require.NoError(t, err) 132 | 133 | defer func() { 134 | err := os.RemoveAll(dir) 135 | require.NoError(t, err) 136 | }() 137 | 138 | // Create a temporary template file. 139 | err = os.WriteFile(dir+"/func_map.amber", []byte(` 140 | h2 #{lower(Var1)} 141 | p #{upper(Var2)}`), 0o600) 142 | require.NoError(t, err) 143 | 144 | engine := New(dir, ".amber") 145 | 146 | fm := map[string]interface{}{ 147 | "lower": strings.ToLower, 148 | "upper": strings.ToUpper, 149 | } 150 | 151 | engine.AddFuncMap(fm) 152 | require.NoError(t, engine.Load()) 153 | 154 | var buf bytes.Buffer 155 | err = engine.Render(&buf, "func_map", map[string]interface{}{ 156 | "Var1": "LOwEr", 157 | "Var2": "upPEr", 158 | }) 159 | require.NoError(t, err) 160 | 161 | expect := `

lower

UPPER

` 162 | result := trim(buf.String()) 163 | require.Equal(t, expect, result) 164 | 165 | // FuncMap 166 | fm2 := engine.FuncMap() 167 | _, ok := fm2["lower"] 168 | require.True(t, ok) 169 | _, ok = fm2["upper"] 170 | require.True(t, ok) 171 | } 172 | 173 | func Benchmark_Amber(b *testing.B) { 174 | expectSimple := `

Hello, World!

` 175 | expectExtended := `Main

Header

Hello, Admin!

Footer

` 176 | engine := New("./views", ".amber") 177 | engine.AddFunc("isAdmin", func(user string) bool { 178 | return user == admin 179 | }) 180 | require.NoError(b, engine.Load()) 181 | 182 | b.Run("simple", func(bb *testing.B) { 183 | bb.ReportAllocs() 184 | bb.ResetTimer() 185 | var buf bytes.Buffer 186 | for i := 0; i < bb.N; i++ { 187 | buf.Reset() 188 | //nolint:gosec,errcheck // Return value not needed for benchmark 189 | _ = engine.Render(&buf, "simple", map[string]interface{}{ 190 | "Title": "Hello, World!", 191 | }) 192 | } 193 | }) 194 | 195 | b.Run("extended", func(bb *testing.B) { 196 | bb.ReportAllocs() 197 | bb.ResetTimer() 198 | var buf bytes.Buffer 199 | for i := 0; i < bb.N; i++ { 200 | buf.Reset() 201 | //nolint:gosec,errcheck // Return value not needed for benchmark 202 | _ = engine.Render(&buf, "extended", map[string]interface{}{ 203 | "User": admin, 204 | }, "layouts/main") 205 | } 206 | }) 207 | 208 | b.Run("simple_asserted", func(bb *testing.B) { 209 | bb.ReportAllocs() 210 | bb.ResetTimer() 211 | var buf bytes.Buffer 212 | for i := 0; i < bb.N; i++ { 213 | buf.Reset() 214 | err := engine.Render(&buf, "simple", map[string]interface{}{ 215 | "Title": "Hello, World!", 216 | }) 217 | require.NoError(bb, err) 218 | require.Equal(bb, expectSimple, trim(buf.String())) 219 | } 220 | }) 221 | 222 | b.Run("extended_asserted", func(bb *testing.B) { 223 | bb.ReportAllocs() 224 | bb.ResetTimer() 225 | var buf bytes.Buffer 226 | for i := 0; i < bb.N; i++ { 227 | buf.Reset() 228 | err := engine.Render(&buf, "extended", map[string]interface{}{ 229 | "User": admin, 230 | }, "layouts/main") 231 | require.NoError(bb, err) 232 | require.Equal(bb, expectExtended, trim(buf.String())) 233 | } 234 | }) 235 | } 236 | 237 | func Benchmark_Amber_Parallel(b *testing.B) { 238 | expectSimple := `

Hello, Parallel!

` 239 | expectExtended := `Main

Header

Hello, Admin!

Footer

` 240 | engine := New("./views", ".amber") 241 | engine.AddFunc("isAdmin", func(user string) bool { 242 | return user == "admin" 243 | }) 244 | require.NoError(b, engine.Load()) 245 | 246 | b.Run("simple", func(bb *testing.B) { 247 | bb.ReportAllocs() 248 | bb.ResetTimer() 249 | bb.RunParallel(func(pb *testing.PB) { 250 | for pb.Next() { 251 | var buf bytes.Buffer 252 | //nolint:gosec,errcheck // Return value not needed for benchmark 253 | _ = engine.Render(&buf, "simple", map[string]interface{}{ 254 | "Title": "Hello, Parallel!", 255 | }) 256 | } 257 | }) 258 | }) 259 | 260 | b.Run("extended", func(bb *testing.B) { 261 | bb.ReportAllocs() 262 | bb.ResetTimer() 263 | bb.RunParallel(func(pb *testing.PB) { 264 | for pb.Next() { 265 | var buf bytes.Buffer 266 | //nolint:gosec,errcheck // Return value not needed for benchmark 267 | _ = engine.Render(&buf, "extended", map[string]interface{}{ 268 | "User": admin, 269 | }, "layouts/main") 270 | } 271 | }) 272 | }) 273 | 274 | b.Run("simple_asserted", func(bb *testing.B) { 275 | bb.ReportAllocs() 276 | bb.ResetTimer() 277 | bb.RunParallel(func(pb *testing.PB) { 278 | for pb.Next() { 279 | var buf bytes.Buffer 280 | err := engine.Render(&buf, "simple", map[string]interface{}{ 281 | "Title": "Hello, Parallel!", 282 | }) 283 | require.NoError(bb, err) 284 | require.Equal(bb, expectSimple, trim(buf.String())) 285 | } 286 | }) 287 | }) 288 | 289 | b.Run("extended_asserted", func(bb *testing.B) { 290 | bb.ReportAllocs() 291 | bb.ResetTimer() 292 | bb.RunParallel(func(pb *testing.PB) { 293 | for pb.Next() { 294 | var buf bytes.Buffer 295 | err := engine.Render(&buf, "extended", map[string]interface{}{ 296 | "User": admin, 297 | }, "layouts/main") 298 | require.NoError(bb, err) 299 | require.Equal(bb, expectExtended, trim(buf.String())) 300 | } 301 | }) 302 | }) 303 | } 304 | -------------------------------------------------------------------------------- /amber/embed_views/embed.amber: -------------------------------------------------------------------------------- 1 | import header 2 | 3 | h1 #{Title} 4 | 5 | import footer -------------------------------------------------------------------------------- /amber/embed_views/footer.amber: -------------------------------------------------------------------------------- 1 | h2 Footer -------------------------------------------------------------------------------- /amber/embed_views/header.amber: -------------------------------------------------------------------------------- 1 | h2 Header -------------------------------------------------------------------------------- /amber/embed_views/layouts/main.amber: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Main 5 | body 6 | #{embed()} 7 | -------------------------------------------------------------------------------- /amber/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gofiber/template/amber/v2 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 7 | github.com/gofiber/template v1.8.3 8 | github.com/gofiber/utils v1.1.0 9 | github.com/stretchr/testify v1.10.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | 18 | replace github.com/gofiber/template => ../. 19 | -------------------------------------------------------------------------------- /amber/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= 4 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= 5 | github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= 6 | github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 10 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 13 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 14 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 15 | -------------------------------------------------------------------------------- /amber/views/errors/404.amber: -------------------------------------------------------------------------------- 1 | h1 #{Title} -------------------------------------------------------------------------------- /amber/views/extended.amber: -------------------------------------------------------------------------------- 1 | import ./views/partials/header 2 | 3 | if isAdmin(User) 4 | h1 Hello, Admin! 5 | else 6 | h1 Access Denied 7 | 8 | import ./views/partials/footer 9 | -------------------------------------------------------------------------------- /amber/views/index.amber: -------------------------------------------------------------------------------- 1 | import ./views/partials/header 2 | 3 | h1 #{Title} 4 | 5 | import ./views/partials/footer -------------------------------------------------------------------------------- /amber/views/layouts/main.amber: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Main 5 | body 6 | #{embed()} 7 | -------------------------------------------------------------------------------- /amber/views/partials/footer.amber: -------------------------------------------------------------------------------- 1 | h2 Footer -------------------------------------------------------------------------------- /amber/views/partials/header.amber: -------------------------------------------------------------------------------- 1 | h2 Header -------------------------------------------------------------------------------- /amber/views/reload.amber: -------------------------------------------------------------------------------- 1 | before reload 2 | -------------------------------------------------------------------------------- /amber/views/simple.amber: -------------------------------------------------------------------------------- 1 | h1 #{Title} 2 | -------------------------------------------------------------------------------- /django/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: django 3 | title: Django 4 | --- 5 | 6 | ![Release](https://img.shields.io/github/v/tag/gofiber/template?filter=django*) 7 | [![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) 8 | ![Test](https://github.com/gofiber/template/workflows/Tests%20Django/badge.svg) 9 | 10 | Django is a template engine create by [flosch](https://github.com/flosch/pongo2), to see the original syntax documentation please [click here](https://docs.djangoproject.com/en/dev/topics/templates/) 11 | 12 | ### Basic Example 13 | 14 | _**./views/index.django**_ 15 | ```html 16 | {% include "partials/header.django" %} 17 | 18 |

{{ Title }}

19 | 20 | {% include "partials/footer.django" %} 21 | ``` 22 | _**./views/partials/header.django**_ 23 | ```html 24 |

Header

25 | ``` 26 | _**./views/partials/footer.django**_ 27 | ```html 28 |

Footer

29 | ``` 30 | _**./views/layouts/main.django**_ 31 | ```html 32 | 33 | 34 | 35 | 36 | Main 37 | 38 | 39 | 40 | {{embed}} 41 | 42 | 43 | 44 | ``` 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "log" 51 | 52 | "github.com/gofiber/fiber/v2" 53 | "github.com/gofiber/template/django/v3" 54 | ) 55 | 56 | func main() { 57 | // Create a new engine 58 | engine := django.New("./views", ".django") 59 | 60 | // Or from an embedded system 61 | // See github.com/gofiber/embed for examples 62 | // engine := html.NewFileSystem(http.Dir("./views", ".django")) 63 | 64 | // Pass the engine to the Views 65 | app := fiber.New(fiber.Config{ 66 | Views: engine, 67 | }) 68 | 69 | app.Get("/", func(c *fiber.Ctx) error { 70 | // Render index 71 | return c.Render("index", fiber.Map{ 72 | "Title": "Hello, World!", 73 | }) 74 | }) 75 | 76 | app.Get("/layout", func(c *fiber.Ctx) error { 77 | // Render index within layouts/main 78 | return c.Render("index", fiber.Map{ 79 | "Title": "Hello, World!", 80 | }, "layouts/main") 81 | }) 82 | 83 | log.Fatal(app.Listen(":3000")) 84 | } 85 | 86 | ``` 87 | ### Using embedded file system (1.16+ only) 88 | 89 | When using the `// go:embed` directive, resolution of inherited templates using django's `{% extend '' %}` keyword fails when instantiating the template engine with `django.NewFileSystem()`. In that case, use the `django.NewPathForwardingFileSystem()` function to instantiate the template engine. 90 | 91 | This function provides the proper configuration for resolving inherited templates. 92 | 93 | Assume you have the following files: 94 | 95 | - [views/ancenstor.django](https://github.com/gofiber/template/blob/master/django/views/ancestor.django) 96 | - [views/descendant.djando](https://github.com/gofiber/template/blob/master/django/views/descendant.django) 97 | 98 | then 99 | 100 | ```go 101 | package main 102 | 103 | import ( 104 | "log" 105 | "embed" 106 | "net/http" 107 | 108 | "github.com/gofiber/fiber/v2" 109 | "github.com/gofiber/template/django/v3" 110 | ) 111 | 112 | //go:embed views 113 | var viewsAsssets embed.FS 114 | 115 | func main() { 116 | // Create a new engine 117 | engine := django.NewPathForwardingFileSystem(http.FS(viewsAsssets), "/views", ".django") 118 | 119 | // Pass the engine to the Views 120 | app := fiber.New(fiber.Config{ 121 | Views: engine, 122 | }) 123 | 124 | app.Get("/", func(c *fiber.Ctx) error { 125 | // Render descendant 126 | return c.Render("descendant", fiber.Map{ 127 | "greeting": "World", 128 | }) 129 | }) 130 | 131 | log.Fatal(app.Listen(":3000")) 132 | } 133 | 134 | ``` 135 | 136 | ### Register and use custom functions 137 | ```go 138 | // My custom function 139 | func Nl2brHtml(value interface{}) string { 140 | if str, ok := value.(string); ok { 141 | return strings.Replace(str, "\n", "
", -1) 142 | } 143 | return "" 144 | } 145 | 146 | // Create a new engine 147 | engine := django.New("./views", ".django") 148 | 149 | // register functions 150 | engine.AddFunc("nl2br", Nl2brHtml) 151 | 152 | // Pass the engine to the Views 153 | app := fiber.New(fiber.Config{Views: engine}) 154 | ``` 155 | _**in the handler**_ 156 | ```go 157 | c.Render("index", fiber.Map{ 158 | "Fiber": "Hello, World!\n\nGreetings from Fiber Team", 159 | }) 160 | ``` 161 | 162 | _**./views/index.django**_ 163 | ```html 164 | 165 | 166 | 167 | 168 | {{ nl2br(Fiber) }} 169 | 170 | 171 | ``` 172 | **Output:** 173 | ```html 174 | 175 | 176 | 177 | 178 | Hello, World!

Greetings from Fiber Team 179 | 180 | 181 | ``` 182 | 183 | ### Important Information on Template Data Binding 184 | 185 | When working with Pongo2 and this template engine, it's crucial to understand the specific rules for data binding. Only keys that match the following regular expression are supported: `^[a-zA-Z0-9_]+$`. 186 | 187 | This means that keys with special characters or punctuation, such as `my-key` or `my.key`, are not compatible and will not be bound to the template. This is a restriction imposed by the underlying Pongo2 template engine. Please ensure your keys adhere to these rules to avoid any binding issues. 188 | 189 | If you need to access a value in the template that doesn't adhere to the key naming restrictions imposed by the Pongo2 template engine, you can bind the value to a new field when calling `fiber.Render`. Here's an example: 190 | 191 | ```go 192 | c.Render("index", fiber.Map{ 193 | "Fiber": "Hello, World!\n\nGreetings from Fiber Team", 194 | "MyKey": c.Locals("my-key"), 195 | }) 196 | ``` 197 | 198 | ### AutoEscape is enabled by default 199 | 200 | When you create a new instance of the `Engine`, the auto-escape is **enabled by default**. This setting automatically escapes output, providing a critical security measure against Cross-Site Scripting (XSS) attacks. 201 | 202 | ### Disabling Auto-Escape 203 | 204 | Auto-escaping can be disabled if necessary, using the `SetAutoEscape` method: 205 | 206 | ```go 207 | engine := django.New("./views", ".django") 208 | engine.SetAutoEscape(false) 209 | ``` 210 | 211 | ### Setting AutoEscape using Django built-in template tags 212 | 213 | - Explicitly turning off autoescaping for a section: 214 | ```django 215 | {% autoescape off %} 216 | {{ "" }} 217 | {% endautoescape %} 218 | ``` 219 | 220 | - Turning autoescaping back on for a section: 221 | ```django 222 | {% autoescape on %} 223 | {{ "" }} 224 | {% endautoescape %} 225 | ``` 226 | - It can also be done on a per variable basis using the *safe* built-in: 227 | ```django 228 |

{{ someSafeVar | safe }}

229 | {{ "