├── .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 | 
7 | [](https://gofiber.io/discord)
8 | 
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 | 
7 | [](https://gofiber.io/discord)
8 | 
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 | 
7 | [](https://gofiber.io/discord)
8 | 
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 | {{ "