[
  {
    "path": ".github/CODEOWNERS",
    "content": "# Default owner\n*       @gobuffalo/core-managers"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: markbates\npatreon: buffalo\n"
  },
  {
    "path": ".github/workflows/standard-go-test.yml",
    "content": "name: Standard Test\n\non:\n  push:\n    branches: [main v1]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  call-standard-test:\n    name: Test\n    uses: gobuffalo/.github/.github/workflows/go-test.yml@v1.8\n    secrets: inherit\n\n  govulncheck:\n    name: govulncheck\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.26\"\n\n      - name: Install govulncheck\n        run: go install golang.org/x/vuln/cmd/govulncheck@latest\n\n      - name: Run govulncheck\n        run: govulncheck ./...\n"
  },
  {
    "path": ".github/workflows/standard-stale.yml",
    "content": "name: Standard Autocloser\n\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  call-standard-autocloser:\n    name: Autocloser\n    uses: gobuffalo/.github/.github/workflows/stale.yml@v1\n    secrets: inherit\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\n.DS_Store\ndoc\ntmp\npkg\n*.gem\n*.pid\ncoverage\ncoverage.data\n*.pbxuser\n*.mode1v3\n.svn\nprofile\n.console_history\n.sass-cache/*\n.rake_tasks~\n*.log.lck\nsolr/\n.jhw-cache/\njhw.*\n*.sublime*\nnode_modules/\ndist/\ngenerated/\n.vendor/\nbin/*\ngin-bin\n.idea/\n.vscode/settings.json\r\n"
  },
  {
    "path": "BACKERS.md",
    "content": "# Financial Backers of the Buffalo Project\n\nBuffalo is a community-driven project that is run by individuals who believe that Buffalo is the way to quickly, and easily, build high quality, scalable applications in Go.\n\nFinancial contributions to the Buffalo go towards ongoing development costs, servers, swag, conferences, etc...\n\nIf you, or your company, use Buffalo, please consider supporting this effort to make rapid web development in Go, simple, easy, and fun!\n\n[http://patreon.com/buffalo](http://patreon.com/buffalo)\n\n---\n\n## Platinum Sponsors\n\n* **[Gopher Guides](https://www.gopherguides.com)**\n* **[PaperCall.io](https://www.papercall.io)**\n* **[Wawandco](https://wawand.co)**\n* **[Symbolsecurity](https://symbolsecurity.com)**\n* [Your Company Here](http://patreon.com/buffalo)\n\n### Gold Sponsors\n\n* [Your Company Here](http://patreon.com/buffalo)\n\n### Premium Backers\n\n* [Your Company Here](http://patreon.com/buffalo)\n\n#### Generous Backers\n\n* **[Zhorty](https://zhorty.com)**\n* [Your Company Here](http://patreon.com/buffalo)\n\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM gobuffalo/buffalo:latest\n\nARG CODECOV_TOKEN\n\nENV GOPROXY         https://proxy.golang.org\nENV BP              /src/buffalo\n\nRUN rm -rf $BP\nRUN mkdir -p $BP\n\nWORKDIR $BP\nCOPY . .\n\nRUN go mod tidy\nRUN go test -tags \"sqlite integration_test\" -cover -race -v ./...\n"
  },
  {
    "path": "Dockerfile.build",
    "content": "FROM golang:1.17\n\nEXPOSE 3000\n\nENV GOPROXY=https://proxy.golang.org\n\nRUN apt-get update \\\n    && apt-get install -y -q build-essential sqlite3 libsqlite3-dev postgresql libpq-dev vim\n\n# Installing Node 12\nRUN curl -sL https://deb.nodesource.com/setup_12.x | bash \nRUN apt-get update && apt-get install nodejs\n\n# Installing Postgres\nRUN sh -c 'echo \"deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main\" >> /etc/apt/sources.list.d/pgdg.list' \\\n    && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | apt-key add - \\\n    && apt-get update \\\n    && apt-get install -y -q postgresql postgresql-contrib libpq-dev\\\n    && rm -rf /var/lib/apt/lists/* \\\n    && service postgresql start && \\\n    # Setting up password for postgres\n    su -c \"psql -c \\\"ALTER USER postgres  WITH PASSWORD 'postgres';\\\"\" - postgres\n\n# Installing yarn\nRUN npm install -g --no-progress yarn \\\n    && yarn config set yarn-offline-mirror /npm-packages-offline-cache \\\n    && yarn config set yarn-offline-mirror-pruning true\n\n# Install golangci\nRUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0\n# Installing buffalo binary\nRUN go install github.com/gobuffalo/cli/cmd/buffalo@latest\nRUN go get github.com/gobuffalo/buffalo-pop/v2\n\nRUN mkdir /src\nWORKDIR /src\n"
  },
  {
    "path": "Dockerfile.slim.build",
    "content": "FROM golang:1.17-alpine\n\nEXPOSE 3000\n\nENV GOPROXY=https://proxy.golang.org \n\nRUN apk add --no-cache --upgrade apk-tools \\\n    && apk add --no-cache bash curl openssl git build-base nodejs npm sqlite sqlite-dev mysql-client vim postgresql libpq postgresql-contrib libc6-compat\n\n# Installing linter\nRUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh \\\n    | sh -s -- -b $(go env GOPATH)/bin v1.24.0\n\n# Installing Yarn\nRUN npm i -g --no-progress yarn \\\n    && yarn config set yarn-offline-mirror /npm-packages-offline-cache \\\n    && yarn config set yarn-offline-mirror-pruning true\n\n# Installing buffalo binary\nRUN go install github.com/gobuffalo/cli/cmd/buffalo@latest\nRUN go get github.com/gobuffalo/buffalo-pop/v2\n\nRUN mkdir /src\nWORKDIR /src\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\nCopyright (c) 2016 Mark Bates\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"https://raw.githubusercontent.com/gobuffalo/buffalo/master/logo.svg\" width=\"360\"></p>\n\n<p align=\"center\">\n<a href=\"https://pkg.go.dev/github.com/gobuffalo/buffalo\"><img src=\"https://pkg.go.dev/badge/github.com/gobuffalo/buffalo\" alt=\"PkgGoDev\"></a>\n<a href=\"https://github.com/gobuffalo/buffalo/actions/workflows/standard-go-test.yml\"><img src=\"https://github.com/gobuffalo/buffalo/actions/workflows/standard-go-test.yml/badge.svg\"></a>\n<a href=\"https://goreportcard.com/report/github.com/gobuffalo/buffalo\"><img src=\"https://goreportcard.com/badge/github.com/gobuffalo/buffalo\" alt=\"Go Report Card\" /></a>\n<a href=\"https://www.codetriage.com/gobuffalo/buffalo\"><img src=\"https://www.codetriage.com/gobuffalo/buffalo/badges/users.svg\" alt=\"Open Source Helpers\" /></a>\n</p>\n\n# Buffalo\n\nA Go web development eco-system, designed to make your project easier.\n\nBuffalo helps you to generate a web project that already has everything from front-end (JavaScript, SCSS, etc.) to the back-end (database, routing, etc.) already hooked up and ready to run. From there it provides easy APIs to build your web application quickly in Go.\n\nBuffalo **isn't just a framework**; it's a holistic web development environment and project structure that **lets developers get straight to the business** of, well, building their business.\n\n> I :heart: web dev in go again - Brian Ketelsen\n\n## Versions\n\nThe current stable version of Buffalo core is v1 (`v1` branch).\n\nVersions (branches):\n* `main` is for the current mainstream development.\n* `v1` is the current stable release.\n\n## ⚠️ Important\n\nBuffalo works only with Go [modules](https://blog.golang.org/using-go-modules). `GOPATH` mode is likely to break most of the functionality of the Buffalo eco-system. Please see [this blog post](https://blog.gobuffalo.io/the-road-to-1-0-requiring-modules-5672c6b015e5) for more information.\n\nAlso, the Buffalo team actively gives support to the last 2 versions of Go, which at the moment are Go 1.23 and 1.24. While Buffalo `may` work on older versions, we encourage you to upgrade to latest 2 versions of Go for a better development experience.\n\n## Documentation\n\nPlease visit [http://gobuffalo.io](http://gobuffalo.io) for the latest documentation, examples, and more.\n\n### Quick Start\n\n- [Installation](https://gobuffalo.io/documentation/getting_started/installation)\n- [Create a new project](https://gobuffalo.io/documentation/getting_started/new-project)\n- [Tutorials](https://gobuffalo.io/documentation/tutorials/)\n\n## Shoulders of Giants\n\nBuffalo would not be possible if not for all of the great projects it depends on. Please see [SHOULDERS.md](SHOULDERS.md) to see a list of them.\n\n### Templating\n\n[github.com/gobuffalo/plush](https://github.com/gobuffalo/plush) - This templating package was chosen over the standard Go `html/template` package for a variety of reasons. The biggest of which is that it is significantly more flexible and easy to work with.\n\n### Routing\n\n[github.com/gorilla/mux](https://github.com/gorilla/mux) - This router was chosen because of its stability and flexibility. There might be faster routers out there, but this one is definitely the most powerful!\n\n### Models/ORM (Optional)\n\n[github.com/gobuffalo/pop](https://github.com/gobuffalo/pop) - Accessing databases is nothing new in web applications. Pop, and its command line tool, Soda, were chosen because they strike a nice balance between simplifying common tasks, being idiomatic, and giving you the flexibility you need to build your app. Pop and Soda share the same core philosophies as Buffalo, so they were a natural choice.\n\n### Sessions, Cookies, WebSockets, and more\n\n[github.com/gorilla](https://github.com/gorilla) - The Gorilla toolkit is a great set of packages designed to improve upon the standard library for a variety of web-related packages. With these high-quality packages Buffalo can keep its \"core\" code to a minimum and focus on its goal of gluing them all together to make your life better.\n\n## Benchmarks\n\nOh, yeah, everyone wants benchmarks! What would a web framework be without its benchmarks? Well, guess what? I'm not giving you any! That's right. This is Go! I assure you that it is plenty fast enough for you. If you want benchmarks you can either a) check out any benchmarks that the [GIANTS](SHOULDERS.md) Buffalo is built upon having published, or b) run your own. I have no interest in playing the benchmark game, and neither should you.\n\n## Contributing\n\nFirst, thank you so much for wanting to contribute! It means so much that you care enough to want to contribute. We appreciate every PR from the smallest of typos to the be biggest of features.\n\n**Here are the core rules to respect**:\n\n- If you have any question, please consider using the\n  [Slack channel](https://gophers.slack.com/messages/buffalo/) (-#buffalo-,\n  *#buffalo_fr* or *#buffalo-dev* for contribution related questions) or\n  [Stack Overflow](https://stackoverflow.com/questions/tagged/buffalo).\n  We use GitHub issues for **bug reports and feature requests only**.\n- All contributors of this project are working on their free time: be patient\n  and kind. :-\n- Consider opening an issue **BEFORE** creating a Pull request (PR): you won't\n  lose your time on fixing non-existing bugs, or fixing the wrong bug. Also we\n  can help you to produce the best PR!\n- Open a PR against the `main` branch if your PR is for mainstream or version\n  specific branch e.g. `v1` if your PR is for specific version.\n  Note that the valid branch for a new feature request PR should be `main`\n  while a PR against a version specific branch are allowed only for bugfixes.\n\nFor the full contribution guidelines, please read [CONTRIBUTING](.github/CONTRIBUTING.md).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 1.x.x   | :white_check_mark: |\n\n## Reporting a Vulnerability\n\nContact @paganotoni or @sio4 on the [Gophers Slack](https://gophers.slack.com).\n"
  },
  {
    "path": "SHOULDERS.md",
    "content": "# Buffalo Stands on the Shoulders of Giants\n\nBuffalo does not try to reinvent the wheel! Instead, it uses the already great wheels developed by the Go community and puts them all together in the best way possible. Without these giants, this project would not be possible. Please make sure to check them out and thank them for all of their hard work.\n\nThank you to the following **GIANTS**:\n\n* [github.com/BurntSushi/toml](https://godoc.org/github.com/BurntSushi/toml)\n* [github.com/aymerick/douceur](https://godoc.org/github.com/aymerick/douceur)\n* [github.com/cpuguy83/go-md2man/v2](https://godoc.org/github.com/cpuguy83/go-md2man/v2)\n* [github.com/davecgh/go-spew](https://godoc.org/github.com/davecgh/go-spew)\n* [github.com/dustin/go-humanize](https://godoc.org/github.com/dustin/go-humanize)\n* [github.com/fatih/color](https://godoc.org/github.com/fatih/color)\n* [github.com/fatih/structs](https://godoc.org/github.com/fatih/structs)\n* [github.com/felixge/httpsnoop](https://godoc.org/github.com/felixge/httpsnoop)\n* [github.com/fsnotify/fsnotify](https://godoc.org/github.com/fsnotify/fsnotify)\n* [github.com/go-sql-driver/mysql](https://godoc.org/github.com/go-sql-driver/mysql)\n* [github.com/joho/godotenv](https://godoc.org/github.com/joho/godotenv)\n* [github.com/gobuffalo/events](https://godoc.org/github.com/gobuffalo/events)\n* [github.com/gobuffalo/flect](https://godoc.org/github.com/gobuffalo/flect)\n* [github.com/gobuffalo/github_flavored_markdown](https://godoc.org/github.com/gobuffalo/github_flavored_markdown)\n* [github.com/gobuffalo/helpers](https://godoc.org/github.com/gobuffalo/helpers)\n* [github.com/gobuffalo/here](https://godoc.org/github.com/gobuffalo/here)\n* [github.com/gobuffalo/httptest](https://godoc.org/github.com/gobuffalo/httptest)\n* [github.com/gobuffalo/logger](https://godoc.org/github.com/gobuffalo/logger)\n* [github.com/gobuffalo/meta](https://godoc.org/github.com/gobuffalo/meta)\n* [github.com/gobuffalo/nulls](https://godoc.org/github.com/gobuffalo/nulls)\n* [github.com/gobuffalo/plush/v5](https://godoc.org/github.com/gobuffalo/plush/v5)\n* [github.com/gobuffalo/refresh](https://godoc.org/github.com/gobuffalo/refresh)\n* [github.com/gobuffalo/tags/v3](https://godoc.org/github.com/gobuffalo/tags/v3)\n* [github.com/gobuffalo/validate/v3](https://godoc.org/github.com/gobuffalo/validate/v3)\n* [github.com/gofrs/uuid](https://godoc.org/github.com/gofrs/uuid)\n* [github.com/google/go-cmp](https://godoc.org/github.com/google/go-cmp)\n* [github.com/gorilla/css](https://godoc.org/github.com/gorilla/css)\n* [github.com/gorilla/handlers](https://godoc.org/github.com/gorilla/handlers)\n* [github.com/gorilla/mux](https://godoc.org/github.com/gorilla/mux)\n* [github.com/gorilla/securecookie](https://godoc.org/github.com/gorilla/securecookie)\n* [github.com/gorilla/sessions](https://godoc.org/github.com/gorilla/sessions)\n* [github.com/inconshreveable/mousetrap](https://godoc.org/github.com/inconshreveable/mousetrap)\n* [github.com/jmoiron/sqlx](https://godoc.org/github.com/jmoiron/sqlx)\n* [github.com/joho/godotenv](https://godoc.org/github.com/joho/godotenv)\n* [github.com/kr/pretty](https://godoc.org/github.com/kr/pretty)\n* [github.com/kr/pty](https://godoc.org/github.com/kr/pty)\n* [github.com/kr/text](https://godoc.org/github.com/kr/text)\n* [github.com/lib/pq](https://godoc.org/github.com/lib/pq)\n* [github.com/mattn/go-colorable](https://godoc.org/github.com/mattn/go-colorable)\n* [github.com/mattn/go-isatty](https://godoc.org/github.com/mattn/go-isatty)\n* [github.com/mattn/go-sqlite3](https://godoc.org/github.com/mattn/go-sqlite3)\n* [github.com/microcosm-cc/bluemonday](https://godoc.org/github.com/microcosm-cc/bluemonday)\n* [github.com/mitchellh/go-homedir](https://godoc.org/github.com/mitchellh/go-homedir)\n* [github.com/monoculum/formam](https://godoc.org/github.com/monoculum/formam)\n* [github.com/pkg/diff](https://godoc.org/github.com/pkg/diff)\n* [github.com/pmezard/go-difflib](https://godoc.org/github.com/pmezard/go-difflib)\n* [github.com/psanford/memfs](https://godoc.org/github.com/psanford/memfs)\n* [github.com/rogpeppe/go-internal](https://godoc.org/github.com/rogpeppe/go-internal)\n* [github.com/russross/blackfriday/v2](https://godoc.org/github.com/russross/blackfriday/v2)\n* [github.com/sergi/go-diff](https://godoc.org/github.com/sergi/go-diff)\n* [github.com/sirupsen/logrus](https://godoc.org/github.com/sirupsen/logrus)\n* [github.com/sourcegraph/annotate](https://godoc.org/github.com/sourcegraph/annotate)\n* [github.com/sourcegraph/syntaxhighlight](https://godoc.org/github.com/sourcegraph/syntaxhighlight)\n* [github.com/spf13/cobra](https://godoc.org/github.com/spf13/cobra)\n* [github.com/spf13/pflag](https://godoc.org/github.com/spf13/pflag)\n* [github.com/stretchr/objx](https://godoc.org/github.com/stretchr/objx)\n* [github.com/stretchr/testify](https://godoc.org/github.com/stretchr/testify)\n* [github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark)\n* [golang.org/x/crypto](https://godoc.org/golang.org/x/crypto)\n* [golang.org/x/mod](https://godoc.org/golang.org/x/mod)\n* [golang.org/x/net](https://godoc.org/golang.org/x/net)\n* [golang.org/x/sync](https://godoc.org/golang.org/x/sync)\n* [golang.org/x/sys](https://godoc.org/golang.org/x/sys)\n* [golang.org/x/term](https://godoc.org/golang.org/x/term)\n* [golang.org/x/text](https://godoc.org/golang.org/x/text)\n* [golang.org/x/tools](https://godoc.org/golang.org/x/tools)\n* [golang.org/x/xerrors](https://godoc.org/golang.org/x/xerrors)\n* [gopkg.in/check.v1](https://godoc.org/gopkg.in/check.v1)\n* [gopkg.in/yaml.v2](https://godoc.org/gopkg.in/yaml.v2)\n* [gopkg.in/yaml.v3](https://godoc.org/gopkg.in/yaml.v3)\n"
  },
  {
    "path": "app.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gorilla/mux\"\n)\n\n// App is where it all happens! It holds on to options,\n// the underlying router, the middleware, and more.\n// Without an App you can't do much!\ntype App struct {\n\tOptions\n\t// Middleware, ErrorHandlers, router, and filepaths are moved to Home.\n\tHome\n\tmoot   *sync.RWMutex\n\troutes RouteList\n\t// TODO: to be deprecated #road-to-v1\n\troot     *App\n\tchildren []*App\n\n\t// Routenamer for the app. This field provides the ability to override the\n\t// base route namer for something more specific to the app.\n\tRouteNamer RouteNamer\n}\n\n// Muxer returns the underlying mux router to allow\n// for advance configurations\nfunc (a *App) Muxer() *mux.Router {\n\treturn a.router\n}\n\n// New returns a new instance of App and adds some sane, and useful, defaults.\nfunc New(opts Options) *App {\n\tLoadPlugins()\n\tenv.Load()\n\n\topts = optionsWithDefaults(opts)\n\n\ta := &App{\n\t\tOptions: opts,\n\t\tHome: Home{\n\t\t\tname:   opts.Name,\n\t\t\thost:   opts.Host,\n\t\t\tprefix: opts.Prefix,\n\t\t\tErrorHandlers: ErrorHandlers{\n\t\t\t\thttp.StatusNotFound:            defaultErrorHandler,\n\t\t\t\thttp.StatusInternalServerError: defaultErrorHandler,\n\t\t\t},\n\t\t\trouter: mux.NewRouter(),\n\t\t},\n\t\tmoot:     &sync.RWMutex{},\n\t\troutes:   RouteList{},\n\t\tchildren: []*App{},\n\n\t\tRouteNamer: baseRouteNamer{},\n\t}\n\ta.Home.app = a     // replace root.\n\ta.Home.appSelf = a // temporary, reverse reference to the group app.\n\n\tnotFoundHandler := func(errorf string, code int) http.HandlerFunc {\n\t\treturn func(res http.ResponseWriter, req *http.Request) {\n\t\t\tc := a.newContext(RouteInfo{}, res, req)\n\t\t\terr := fmt.Errorf(errorf, req.Method, req.URL.Path)\n\t\t\t_ = a.ErrorHandlers.Get(code)(code, err, c)\n\t\t}\n\t}\n\n\ta.router.NotFoundHandler = notFoundHandler(\"path not found: %s %s\", http.StatusNotFound)\n\ta.router.MethodNotAllowedHandler = notFoundHandler(\"method not found: %s %s\", http.StatusMethodNotAllowed)\n\n\tif a.MethodOverride == nil {\n\t\ta.MethodOverride = MethodOverride\n\t}\n\n\ta.Middleware = newMiddlewareStack(RequestLogger)\n\ta.Use(a.defaultErrorMiddleware)\n\ta.Use(a.PanicHandler)\n\n\treturn a\n}\n"
  },
  {
    "path": "app_test.go",
    "content": "package buffalo\n\nfunc voidHandler(c Context) error {\n\treturn nil\n}\n"
  },
  {
    "path": "binding/bindable.go",
    "content": "package binding\n\nimport \"net/http\"\n\n// Bindable when implemented, on a type\n// will override any Binders that have been\n// configured when using buffalo#Context.Bind\ntype Bindable interface {\n\tBind(*http.Request) error\n}\n"
  },
  {
    "path": "binding/bindable_test.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype orbison struct {\n\tbound bool\n}\n\nfunc (o *orbison) Bind(req *http.Request) error {\n\to.bound = true\n\treturn nil\n}\n\nfunc Test_Bindable(t *testing.T) {\n\tr := require.New(t)\n\n\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\to := &orbison{}\n\tr.False(o.bound)\n\tr.NoError(Exec(req, o))\n\tr.True(o.bound)\n}\n"
  },
  {
    "path": "binding/binding.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/binding/decoders\"\n\t\"github.com/gobuffalo/buffalo/internal/nulls\"\n\t\"github.com/monoculum/formam\"\n)\n\nvar (\n\t// MaxFileMemory can be used to set the maximum size, in bytes, for files to be\n\t// stored in memory during uploaded for multipart requests.\n\t// See https://golang.org/pkg/net/http/#Request.ParseMultipartForm for more\n\t// information on how this impacts file uploads.\n\tMaxFileMemory int64 = 5 * 1024 * 1024\n\n\t// formDecoder (formam) that will be used across ContentTypeBinders\n\tformDecoder = buildFormDecoder()\n\n\t// BaseRequestBinder is an instance of the requestBinder, it comes with preconfigured\n\t// content type binders for HTML, JSON, XML and Files, as well as custom types decoders\n\t// for time.Time and nulls.Time\n\tBaseRequestBinder = NewRequestBinder(\n\t\tHTMLContentTypeBinder{\n\t\t\tdecoder: formDecoder,\n\t\t},\n\t\tJSONContentTypeBinder{},\n\t\tXMLRequestTypeBinder{},\n\t\tFileRequestTypeBinder{\n\t\t\tdecoder: formDecoder,\n\t\t},\n\t)\n)\n\n// buildFormDecoder that will be used in the package. This method adds some custom decoders for time.Time and nulls.Time.\nfunc buildFormDecoder() *formam.Decoder {\n\tdecoder := formam.NewDecoder(&formam.DecoderOptions{\n\t\tTagName:           \"form\",\n\t\tIgnoreUnknownKeys: true,\n\t})\n\n\tdecoder.RegisterCustomType(decoders.TimeDecoderFn(), []any{time.Time{}}, nil)\n\tdecoder.RegisterCustomType(decoders.NullTimeDecoderFn(), []any{nulls.Time{}}, nil)\n\n\treturn decoder\n}\n\n// RegisterTimeFormats allows to add custom time layouts that\n// the binder will be able to use for decoding.\nfunc RegisterTimeFormats(layouts ...string) {\n\tdecoders.RegisterTimeFormats(layouts...)\n}\n\n// RegisterCustomDecoder allows to define custom decoders for certain types\n// In the request.\nfunc RegisterCustomDecoder(fn CustomTypeDecoder, types []any, fields []any) {\n\trawFunc := (func([]string) (any, error))(fn)\n\tformDecoder.RegisterCustomType(rawFunc, types, fields)\n}\n\n// Register maps a request Content-Type (application/json)\n// to a Binder.\nfunc Register(contentType string, fn Binder) {\n\tBaseRequestBinder.Register(contentType, fn)\n}\n\n// Exec will bind the interface to the request.Body. The type of binding\n// is dependent on the \"Content-Type\" for the request. If the type\n// is \"application/json\" it will use \"json.NewDecoder\". If the type\n// is \"application/xml\" it will use \"xml.NewDecoder\". The default\n// binder is \"https://github.com/monoculum/formam\".\nfunc Exec(req *http.Request, value any) error {\n\treturn BaseRequestBinder.Exec(req, value)\n}\n"
  },
  {
    "path": "binding/binding_test.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype blogPost struct {\n\tTags     []string\n\tDislikes int\n\tLikes    int32\n}\n\nfunc Test_Register(t *testing.T) {\n\tr := require.New(t)\n\n\tRegister(\"foo/bar\", func(*http.Request, any) error {\n\t\treturn nil\n\t})\n\n\tr.NotNil(BaseRequestBinder.binders[\"foo/bar\"])\n\n\treq, err := http.NewRequest(\"POST\", \"/\", nil)\n\tr.NoError(err)\n\n\treq.Header.Set(\"Content-Type\", \"foo/bar\")\n\treq.Form = url.Values{\n\t\t\"Tags\":     []string{\"AAA\"},\n\t\t\"Likes\":    []string{\"12\"},\n\t\t\"Dislikes\": []string{\"1000\"},\n\t}\n\n\treq.ParseForm()\n\n\tvar post blogPost\n\tr.NoError(Exec(req, &post))\n\n\tr.Equal([]string(nil), post.Tags)\n\tr.Equal(int32(0), post.Likes)\n\tr.Equal(0, post.Dislikes)\n\n}\n\nfunc Test_RegisterCustomDecoder(t *testing.T) {\n\tr := require.New(t)\n\n\tRegisterCustomDecoder(func(vals []string) (any, error) {\n\t\treturn []string{\"X\"}, nil\n\t}, []any{[]string{}}, nil)\n\n\tRegisterCustomDecoder(func(vals []string) (any, error) {\n\t\treturn 0, nil\n\t}, []any{int(0)}, nil)\n\n\tpost := blogPost{}\n\treq, err := http.NewRequest(\"POST\", \"/\", nil)\n\tr.NoError(err)\n\n\treq.Header.Set(\"Content-Type\", \"application/html\")\n\treq.Form = url.Values{\n\t\t\"Tags\":     []string{\"AAA\"},\n\t\t\"Likes\":    []string{\"12\"},\n\t\t\"Dislikes\": []string{\"1000\"},\n\t}\n\treq.ParseForm()\n\n\tr.NoError(Exec(req, &post))\n\tr.Equal([]string{\"X\"}, post.Tags)\n\tr.Equal(int32(12), post.Likes)\n\tr.Equal(0, post.Dislikes)\n}\n"
  },
  {
    "path": "binding/decoders/decoders.go",
    "content": "package decoders\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\tlock = &sync.RWMutex{}\n\n\t// timeFormats are the base time formats supported by the time.Time and\n\t// nulls.Time Decoders you can prepend custom formats to this list\n\t// by using RegisterTimeFormats.\n\ttimeFormats = []string{\n\t\ttime.RFC3339,\n\t\t\"01/02/2006\",\n\t\t\"2006-01-02\",\n\t\t\"2006-01-02T15:04\",\n\t\ttime.ANSIC,\n\t\ttime.UnixDate,\n\t\ttime.RubyDate,\n\t\ttime.RFC822,\n\t\ttime.RFC822Z,\n\t\ttime.RFC850,\n\t\ttime.RFC1123,\n\t\ttime.RFC1123Z,\n\t\ttime.RFC3339Nano,\n\t\ttime.Kitchen,\n\t\ttime.Stamp,\n\t\ttime.StampMilli,\n\t\ttime.StampMicro,\n\t\ttime.StampNano,\n\t}\n)\n\n// RegisterTimeFormats allows to add custom time layouts that\n// the binder will be able to use for decoding.\nfunc RegisterTimeFormats(layouts ...string) {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\n\ttimeFormats = append(layouts, timeFormats...)\n}\n"
  },
  {
    "path": "binding/decoders/null_time.go",
    "content": "package decoders\n\nimport \"github.com/gobuffalo/buffalo/internal/nulls\"\n\n// NullTimeDecoderFn is a custom type decoder func for null.Time fields\nfunc NullTimeDecoderFn() func([]string) (any, error) {\n\treturn func(vals []string) (any, error) {\n\t\tvar ti nulls.Time\n\n\t\t// If vals is empty, return a nulls.Time with Valid = false (i.e. NULL).\n\t\t// The parseTime() function called below does this check as well, but\n\t\t// because it doesn't return an error in the case where vals is empty,\n\t\t// we have no way to determine from its response that the nulls.Time\n\t\t// should actually be NULL.\n\t\tif len(vals) == 0 || vals[0] == \"\" {\n\t\t\treturn ti, nil\n\t\t}\n\n\t\tt, err := parseTime(vals)\n\t\tif err != nil {\n\t\t\treturn ti, err\n\t\t}\n\n\t\tti.Time = t\n\t\tti.Valid = true\n\n\t\treturn ti, nil\n\t}\n}\n"
  },
  {
    "path": "binding/decoders/null_time_test.go",
    "content": "package decoders\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/internal/nulls\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_NullTimeCustomDecoder_Decode(t *testing.T) {\n\tr := require.New(t)\n\n\ttestCases := []struct {\n\t\tinput     string\n\t\texpected  time.Time\n\t\texpValid  bool\n\t\texpectErr bool\n\t}{\n\t\t{\n\t\t\tinput:    \"2017-01-01\",\n\t\t\texpected: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),\n\t\t\texpValid: true,\n\t\t},\n\t\t{\n\t\t\tinput:    \"2018-07-13T15:34\",\n\t\t\texpected: time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),\n\t\t\texpValid: true,\n\t\t},\n\t\t{\n\t\t\tinput:    \"2018-20-10T30:15\",\n\t\t\texpected: time.Time{},\n\t\t\texpValid: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"\",\n\t\t\texpected: time.Time{},\n\t\t\texpValid: false,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\n\t\ttt, err := NullTimeDecoderFn()([]string{testCase.input})\n\t\tr.IsType(tt, nulls.Time{})\n\t\tnt := tt.(nulls.Time)\n\n\t\tif testCase.expectErr {\n\t\t\tr.Error(err)\n\t\t\tr.Equal(nt.Valid, false)\n\t\t\tcontinue\n\t\t}\n\n\t\tr.Equal(testCase.expected, nt.Time)\n\t\tr.Equal(testCase.expValid, nt.Valid)\n\t}\n}\n"
  },
  {
    "path": "binding/decoders/parse_time.go",
    "content": "package decoders\n\nimport (\n\t\"time\"\n)\n\nfunc parseTime(vals []string) (time.Time, error) {\n\tvar t time.Time\n\tvar err error\n\n\t// don't try to parse empty time values, it will raise an error\n\tif len(vals) == 0 || vals[0] == \"\" {\n\t\treturn t, nil\n\t}\n\n\tfor _, layout := range timeFormats {\n\t\tt, err = time.Parse(layout, vals[0])\n\t\tif err == nil {\n\t\t\treturn t, nil\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn t, err\n\t}\n\n\treturn t, nil\n}\n"
  },
  {
    "path": "binding/decoders/time.go",
    "content": "package decoders\n\n// TimeDecoderFn is a custom type decoder func for Time fields\nfunc TimeDecoderFn() func([]string) (any, error) {\n\treturn func(vals []string) (any, error) {\n\t\treturn parseTime(vals)\n\t}\n}\n"
  },
  {
    "path": "binding/decoders/time_test.go",
    "content": "package decoders\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseTimeErrorParsing(t *testing.T) {\n\tr := require.New(t)\n\n\t_, err := parseTime([]string{\"this is sparta\"})\n\tr.Error(err)\n}\n\nfunc TestParseTime(t *testing.T) {\n\tr := require.New(t)\n\n\ttestCases := []struct {\n\t\tinput     string\n\t\texpected  time.Time\n\t\texpectErr bool\n\t}{\n\t\t{\n\t\t\tinput:     \"2017-01-01\",\n\t\t\texpected:  time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),\n\t\t\texpectErr: false,\n\t\t},\n\t\t{\n\t\t\tinput:     \"2018-07-13T15:34\",\n\t\t\texpected:  time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),\n\t\t\texpectErr: false,\n\t\t},\n\t\t{\n\t\t\tinput:     \"2018-20-10T30:15\",\n\t\t\texpected:  time.Time{},\n\t\t\texpectErr: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttt, err := parseTime([]string{tc.input})\n\t\tif !tc.expectErr {\n\t\t\tr.NoError(err)\n\t\t}\n\n\t\tr.Equal(tc.expected, tt)\n\t}\n}\n\nfunc TestParseTimeConflicting(t *testing.T) {\n\tr := require.New(t)\n\n\tRegisterTimeFormats(\"2006-02-01\")\n\ttt, err := parseTime([]string{\"2017-01-10\"})\n\n\tr.NoError(err)\n\texpected := time.Date(2017, time.October, 1, 0, 0, 0, 0, time.UTC)\n\tr.Equal(expected, tt)\n}\n"
  },
  {
    "path": "binding/file.go",
    "content": "package binding\n\nimport (\n\t\"mime/multipart\"\n)\n\n// File holds information regarding an uploaded file\ntype File struct {\n\tmultipart.File\n\t*multipart.FileHeader\n}\n\n// Valid if there is an actual uploaded file\nfunc (f File) Valid() bool {\n\treturn f.File != nil\n}\n\nfunc (f File) String() string {\n\tif f.File == nil {\n\t\treturn \"\"\n\t}\n\treturn f.Filename\n}\n"
  },
  {
    "path": "binding/file_request_type_binder.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n\t\"reflect\"\n\n\t\"github.com/monoculum/formam\"\n)\n\n// FileRequestTypeBinder is in charge of binding File request types.\ntype FileRequestTypeBinder struct {\n\tdecoder *formam.Decoder\n}\n\n// ContentTypes returns the list of content types for FileRequestTypeBinder\nfunc (ht FileRequestTypeBinder) ContentTypes() []string {\n\treturn []string{\n\t\t\"multipart/form-data\",\n\t}\n}\n\n// BinderFunc that will take care of the HTML File binding\nfunc (ht FileRequestTypeBinder) BinderFunc() Binder {\n\treturn func(req *http.Request, i any) error {\n\t\terr := req.ParseMultipartForm(MaxFileMemory)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := ht.decoder.Decode(req.Form, i); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tform := req.MultipartForm.File\n\t\tif len(form) == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\tri := reflect.Indirect(reflect.ValueOf(i))\n\t\trt := ri.Type()\n\t\tfor n := range form {\n\t\t\tf := ri.FieldByName(n)\n\t\t\tif !f.IsValid() {\n\t\t\t\tfor i := 0; i < rt.NumField(); i++ {\n\t\t\t\t\tsf := rt.Field(i)\n\t\t\t\t\tif sf.Tag.Get(\"form\") == n {\n\t\t\t\t\t\tf = ri.Field(i)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !f.IsValid() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif f.Kind() == reflect.Slice {\n\t\t\t\tfor _, fh := range req.MultipartForm.File[n] {\n\t\t\t\t\tmf, err := fh.Open()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tf.Set(reflect.Append(f, reflect.ValueOf(File{\n\t\t\t\t\t\tFile:       mf,\n\t\t\t\t\t\tFileHeader: fh,\n\t\t\t\t\t})))\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, ok := f.Interface().(File); ok {\n\t\t\t\tmf, mh, err := req.FormFile(n)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tf.Set(reflect.ValueOf(File{\n\t\t\t\t\tFile:       mf,\n\t\t\t\t\tFileHeader: mh,\n\t\t\t\t}))\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "binding/file_test.go",
    "content": "package binding_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo\"\n\t\"github.com/gobuffalo/buffalo/binding\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype WithFile struct {\n\tMyFile binding.File\n}\n\ntype NamedFileSlice struct {\n\tMyFiles []binding.File `form:\"thefiles\"`\n}\n\ntype NamedFile struct {\n\tMyFile binding.File `form:\"afile\"`\n}\n\nfunc App() *buffalo.App {\n\ta := buffalo.New(buffalo.Options{})\n\ta.POST(\"/on-struct\", func(c buffalo.Context) error {\n\t\twf := &WithFile{}\n\t\tif err := c.Bind(wf); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, render.String(wf.MyFile.Filename))\n\t})\n\ta.POST(\"/named-file\", func(c buffalo.Context) error {\n\t\twf := &NamedFile{}\n\t\tif err := c.Bind(wf); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, render.String(wf.MyFile.Filename))\n\t})\n\ta.POST(\"/named-file-slice\", func(c buffalo.Context) error {\n\t\twmf := &NamedFileSlice{}\n\t\tif err := c.Bind(wmf); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresult := make([]string, len(wmf.MyFiles))\n\t\tfor i, f := range wmf.MyFiles {\n\t\t\tresult[i] += fmt.Sprintf(\"%s\", f.Filename)\n\n\t\t}\n\t\treturn c.Render(http.StatusCreated, render.String(strings.Join(result, \",\")))\n\t})\n\ta.POST(\"/on-context\", func(c buffalo.Context) error {\n\t\tf, err := c.File(\"MyFile\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, render.String(f.Filename))\n\t})\n\n\treturn a\n}\n\nfunc Test_File_Upload_On_Struct(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := newFileUploadRequest(\"/on-struct\", \"MyFile\", \"file_test.go\")\n\tr.NoError(err)\n\tres := httptest.NewRecorder()\n\n\tApp().ServeHTTP(res, req)\n\n\tr.Equal(http.StatusCreated, res.Code)\n\tr.Equal(\"file_test.go\", res.Body.String())\n}\n\nfunc Test_File_Upload_On_Struct_WithTag_WithMultipleFiles(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := newFileUploadRequest(\"/named-file-slice\", \"thefiles\", \"file_test.go\", \"file.go\", \"types.go\")\n\tr.NoError(err)\n\tres := httptest.NewRecorder()\n\n\tApp().ServeHTTP(res, req)\n\n\tr.Equal(http.StatusCreated, res.Code)\n\tr.Equal(\"file_test.go,file.go,types.go\", res.Body.String())\n}\n\nfunc Test_File_Upload_On_Struct_WithTag(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := newFileUploadRequest(\"/named-file\", \"afile\", \"file_test.go\")\n\tr.NoError(err)\n\tres := httptest.NewRecorder()\n\n\tApp().ServeHTTP(res, req)\n\n\tr.Equal(http.StatusCreated, res.Code)\n\tr.Equal(\"file_test.go\", res.Body.String())\n}\n\nfunc Test_File_Upload_On_Context(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := newFileUploadRequest(\"/on-context\", \"MyFile\", \"file_test.go\")\n\tr.NoError(err)\n\tres := httptest.NewRecorder()\n\n\tApp().ServeHTTP(res, req)\n\n\tr.Equal(http.StatusCreated, res.Code)\n\tr.Equal(\"file_test.go\", res.Body.String())\n}\n\n// this helper method was inspired by this blog post by Matt Aimonetti:\n// https://matt.aimonetti.net/posts/2013/07/01/golang-multipart-file-upload-example/\nfunc newFileUploadRequest(uri string, paramName string, paths ...string) (*http.Request, error) {\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\n\tfor _, path := range paths {\n\t\tfile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer file.Close()\n\t\tpart, err := writer.CreateFormFile(paramName, filepath.Base(path))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, err = io.Copy(part, file); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := writer.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\treq, err := http.NewRequest(\"POST\", uri, body)\n\treq.Header.Set(\"Content-Type\", writer.FormDataContentType())\n\treturn req, err\n}\n"
  },
  {
    "path": "binding/html_content_type_binder.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/monoculum/formam\"\n)\n\n// HTMLContentTypeBinder is in charge of binding HTML request types.\ntype HTMLContentTypeBinder struct {\n\tdecoder *formam.Decoder\n}\n\n// ContentTypes that will be used to identify HTML requests\nfunc (ht HTMLContentTypeBinder) ContentTypes() []string {\n\treturn []string{\n\t\t\"application/html\",\n\t\t\"text/html\",\n\t\t\"application/x-www-form-urlencoded\",\n\t\t\"html\",\n\t}\n}\n\n// BinderFunc that will take care of the HTML binding\nfunc (ht HTMLContentTypeBinder) BinderFunc() Binder {\n\treturn func(req *http.Request, i any) error {\n\t\terr := req.ParseForm()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := ht.decoder.Decode(req.Form, i); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "binding/json_content_type_binder.go",
    "content": "package binding\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n)\n\n// JSONContentTypeBinder is in charge of binding JSON request types.\ntype JSONContentTypeBinder struct{}\n\n// BinderFunc returns the Binder for this JSONRequestTypeBinder\nfunc (js JSONContentTypeBinder) BinderFunc() Binder {\n\treturn func(req *http.Request, value any) error {\n\t\treturn json.NewDecoder(req.Body).Decode(value)\n\t}\n}\n\n// ContentTypes that will be wired to this the JSON Binder\nfunc (js JSONContentTypeBinder) ContentTypes() []string {\n\treturn []string{\n\t\t\"application/json\",\n\t\t\"text/json\",\n\t\t\"json\",\n\t}\n}\n"
  },
  {
    "path": "binding/request_binder.go",
    "content": "package binding\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo/internal/httpx\"\n)\n\nvar (\n\terrBlankContentType = errors.New(\"blank content type\")\n)\n\n// RequestBinder is in charge of binding multiple requests types to\n// struct.\ntype RequestBinder struct {\n\tlock    *sync.RWMutex\n\tbinders map[string]Binder\n}\n\n// Register maps a request Content-Type (application/json)\n// to a Binder.\nfunc (rb *RequestBinder) Register(contentType string, fn Binder) {\n\trb.lock.Lock()\n\tdefer rb.lock.Unlock()\n\n\trb.binders[strings.ToLower(contentType)] = fn\n}\n\n// Exec binds a request with a passed value, depending on the content type\n// It will look for the correct RequestTypeBinder and use it.\nfunc (rb *RequestBinder) Exec(req *http.Request, value any) error {\n\trb.lock.Lock()\n\tdefer rb.lock.Unlock()\n\n\tif ba, ok := value.(Bindable); ok {\n\t\treturn ba.Bind(req)\n\t}\n\n\tct := httpx.ContentType(req)\n\tif ct == \"\" {\n\t\treturn errBlankContentType\n\t}\n\n\tbinder := rb.binders[ct]\n\tif binder == nil {\n\t\treturn fmt.Errorf(\"could not find a binder for %s\", ct)\n\t}\n\n\treturn binder(req, value)\n}\n\n// NewRequestBinder creates our request binder with support for\n// XML, JSON, HTTP and File request types.\nfunc NewRequestBinder(requestBinders ...ContenTypeBinder) *RequestBinder {\n\tresult := &RequestBinder{\n\t\tlock:    &sync.RWMutex{},\n\t\tbinders: map[string]Binder{},\n\t}\n\n\tfor _, requestBinder := range requestBinders {\n\t\tfor _, contentType := range requestBinder.ContentTypes() {\n\t\t\tresult.Register(contentType, requestBinder.BinderFunc())\n\t\t}\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "binding/request_binder_test.go",
    "content": "package binding\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_RequestBinder_Exec(t *testing.T) {\n\tr := require.New(t)\n\n\tvar used bool\n\tBaseRequestBinder.Register(\"paganotoni/test\", func(*http.Request, any) error {\n\t\tused = true\n\t\treturn nil\n\t})\n\n\treq, err := http.NewRequest(\"GET\", \"/home\", strings.NewReader(\"\"))\n\treq.Header.Add(\"content-type\", \"paganotoni/test\")\n\tr.NoError(err)\n\n\tdata := &struct{}{}\n\tr.NoError(BaseRequestBinder.Exec(req, data))\n\tr.True(used)\n}\n\nfunc Test_RequestBinder_Exec_BlankContentType(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := http.NewRequest(\"GET\", \"/home\", strings.NewReader(\"\"))\n\tr.NoError(err)\n\n\tdata := &struct{}{}\n\tr.Equal(BaseRequestBinder.Exec(req, data), errBlankContentType)\n}\n\nfunc Test_RequestBinder_Exec_Bindable(t *testing.T) {\n\tr := require.New(t)\n\n\tBaseRequestBinder.Register(\"paganotoni/orbison\", func(req *http.Request, val any) error {\n\t\tswitch v := val.(type) {\n\t\tcase orbison:\n\t\t\tv.bound = false\n\t\t}\n\n\t\treturn errors.New(\"this should not be called\")\n\t})\n\n\treq, err := http.NewRequest(\"GET\", \"/home\", strings.NewReader(\"\"))\n\treq.Header.Add(\"content-type\", \"paganotoni/orbison\")\n\tr.NoError(err)\n\n\tdata := &orbison{}\n\tr.NoError(BaseRequestBinder.Exec(req, data))\n\tr.True(data.bound)\n}\n\nfunc Test_RequestBinder_Exec_NoBinder(t *testing.T) {\n\tr := require.New(t)\n\n\treq, err := http.NewRequest(\"GET\", \"/home\", strings.NewReader(\"\"))\n\treq.Header.Add(\"content-type\", \"paganotoni/other\")\n\tr.NoError(err)\n\n\terr = BaseRequestBinder.Exec(req, &struct{}{})\n\tr.Error(err)\n\tr.Equal(err.Error(), \"could not find a binder for paganotoni/other\")\n}\n"
  },
  {
    "path": "binding/types.go",
    "content": "package binding\n\nimport (\n\t\"net/http\"\n)\n\n// ContenTypeBinder are those capable of handling a request type like JSON or XML\ntype ContenTypeBinder interface {\n\tBinderFunc() Binder\n\tContentTypes() []string\n}\n\n// Binder takes a request and binds it to an interface.\n// If there is a problem it should return an error.\ntype Binder func(*http.Request, any) error\n\n// CustomTypeDecoder converts a custom type from the request into its exact type.\ntype CustomTypeDecoder func([]string) (any, error)\n"
  },
  {
    "path": "binding/xml_request_type_binder.go",
    "content": "package binding\n\nimport (\n\t\"encoding/xml\"\n\t\"net/http\"\n)\n\n// XMLRequestTypeBinder is in charge of binding XML request types.\ntype XMLRequestTypeBinder struct{}\n\n// BinderFunc returns the Binder for this RequestTypeBinder\nfunc (xm XMLRequestTypeBinder) BinderFunc() Binder {\n\treturn func(req *http.Request, value any) error {\n\t\treturn xml.NewDecoder(req.Body).Decode(value)\n\t}\n}\n\n// ContentTypes that will be wired to this the XML Binder\nfunc (xm XMLRequestTypeBinder) ContentTypes() []string {\n\treturn []string{\n\t\t\"application/xml\",\n\t\t\"text/xml\",\n\t\t\"xml\",\n\t}\n}\n"
  },
  {
    "path": "buffalo.go",
    "content": "/*\nPackage buffalo is a Go web development eco-system, designed to make your life easier.\n\nBuffalo helps you to generate a web project that already has everything from front-end (JavaScript, SCSS, etc.) to back-end (database, routing, etc.) already hooked up and ready to run. From there it provides easy APIs to build your web application quickly in Go.\n\nBuffalo **isn't just a framework**, it's a holistic web development environment and project structure that **lets developers get straight to the business** of, well, building their business.\n*/\npackage buffalo\n"
  },
  {
    "path": "context.go",
    "content": "package buffalo\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/gobuffalo/buffalo/binding\"\n\t\"github.com/gobuffalo/buffalo/internal/httpx\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gorilla/mux\"\n)\n\n// Context holds on to information as you\n// pass it down through middleware, Handlers,\n// templates, etc... It strives to make your\n// life a happier one.\ntype Context interface {\n\tcontext.Context\n\tResponse() http.ResponseWriter\n\tRequest() *http.Request\n\tSession() *Session\n\tCookies() *Cookies\n\tParams() ParamValues\n\tParam(string) string\n\tSet(string, any)\n\tLogField(string, any)\n\tLogFields(map[string]any)\n\tLogger() Logger\n\tBind(any) error\n\tRender(int, render.Renderer) error\n\tError(int, error) error\n\tRedirect(int, string, ...any) error\n\tData() map[string]any\n\tFlash() *Flash\n\tFile(string) (binding.File, error)\n}\n\n// ParamValues will most commonly be url.Values,\n// but isn't it great that you set your own? :)\ntype ParamValues interface {\n\tGet(string) string\n}\n\nfunc (a *App) newContext(info RouteInfo, res http.ResponseWriter, req *http.Request) Context {\n\tif ws, ok := res.(*Response); ok {\n\t\tres = ws\n\t}\n\n\t// Parse URL Params\n\tparams := url.Values{}\n\tvars := mux.Vars(req)\n\tfor k, v := range vars {\n\t\tparams.Add(k, v)\n\t}\n\n\t// Parse URL Query String Params\n\t// For POST, PUT, and PATCH requests, it also parse the request body as a form.\n\t// Request body parameters take precedence over URL query string values in params\n\tif err := req.ParseForm(); err == nil {\n\t\tfor k, v := range req.Form {\n\t\t\tfor _, vv := range v {\n\t\t\t\tparams.Add(k, vv)\n\t\t\t}\n\t\t}\n\t}\n\n\tsession := a.getSession(req, res)\n\n\tct := httpx.ContentType(req)\n\n\tdata := newRequestData()\n\tdata.d = map[string]any{\n\t\t\"app\":           a,\n\t\t\"env\":           a.Env,\n\t\t\"routes\":        a.Routes(),\n\t\t\"current_route\": info,\n\t\t\"current_path\":  req.URL.Path,\n\t\t\"contentType\":   ct,\n\t\t\"method\":        req.Method,\n\t}\n\n\tfor _, route := range a.Routes() {\n\t\tcRoute := route\n\t\tdata.d[cRoute.PathName] = cRoute.BuildPathHelper()\n\t}\n\n\treturn &DefaultContext{\n\t\tContext:     req.Context(),\n\t\tcontentType: ct,\n\t\tresponse:    res,\n\t\trequest:     req,\n\t\tparams:      params,\n\t\tlogger:      a.Logger,\n\t\tsession:     session,\n\t\tflash:       newFlash(session),\n\t\tdata:        data,\n\t}\n}\n"
  },
  {
    "path": "cookies.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"time\"\n)\n\n// Cookies allows you to easily get cookies from the request, and set cookies on the response.\ntype Cookies struct {\n\treq *http.Request\n\tres http.ResponseWriter\n}\n\n// Get returns the value of the cookie with the given name. Returns http.ErrNoCookie if there's no cookie with that name in the request.\nfunc (c *Cookies) Get(name string) (string, error) {\n\tck, err := c.req.Cookie(name)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn ck.Value, nil\n}\n\n// Set a cookie on the response, which will expire after the given duration.\nfunc (c *Cookies) Set(name, value string, maxAge time.Duration) {\n\tck := http.Cookie{\n\t\tName:   name,\n\t\tValue:  value,\n\t\tMaxAge: int(maxAge.Seconds()),\n\t}\n\n\thttp.SetCookie(c.res, &ck)\n}\n\n// SetWithExpirationTime sets a cookie that will expire at a specific time.\n// Note that the time is determined by the client's browser, so it might not expire at the expected time,\n// for example if the client has changed the time on their computer.\nfunc (c *Cookies) SetWithExpirationTime(name, value string, expires time.Time) {\n\tck := http.Cookie{\n\t\tName:    name,\n\t\tValue:   value,\n\t\tExpires: expires,\n\t}\n\n\thttp.SetCookie(c.res, &ck)\n}\n\n// SetWithPath sets a cookie path on the server in which the cookie will be available on.\n// If set to '/', the cookie will be available within the entire domain.\n// If set to '/foo/', the cookie will only be available within the /foo/ directory and\n// all sub-directories such as /foo/bar/ of domain.\nfunc (c *Cookies) SetWithPath(name, value, path string) {\n\tck := http.Cookie{\n\t\tName:  name,\n\t\tValue: value,\n\t\tPath:  path,\n\t}\n\n\thttp.SetCookie(c.res, &ck)\n}\n\n// Delete sets a header that tells the browser to remove the cookie with the given name.\nfunc (c *Cookies) Delete(name string) {\n\tck := http.Cookie{\n\t\tName:  name,\n\t\tValue: \"v\",\n\t\t// Setting a time in the distant past, like the unix epoch, removes the cookie,\n\t\t// since it has long expired.\n\t\tExpires: time.Unix(0, 0),\n\t}\n\n\thttp.SetCookie(c.res, &ck)\n}\n"
  },
  {
    "path": "cookies_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCookies_Get(t *testing.T) {\n\tr := require.New(t)\n\treq := httptest.NewRequest(\"POST\", \"/\", nil)\n\treq.Header.Set(\"Cookie\", \"name=Arthur Dent; answer=42\")\n\n\tc := Cookies{req, nil}\n\n\tv, err := c.Get(\"name\")\n\tr.NoError(err)\n\tr.Equal(\"Arthur Dent\", v)\n\n\tv, err = c.Get(\"answer\")\n\tr.NoError(err)\n\tr.Equal(\"42\", v)\n\n\t_, err = c.Get(\"unknown\")\n\tr.EqualError(err, http.ErrNoCookie.Error())\n}\n\nfunc TestCookies_Set(t *testing.T) {\n\tr := require.New(t)\n\tres := httptest.NewRecorder()\n\n\tc := Cookies{&http.Request{}, res}\n\n\tc.Set(\"name\", \"Rob Pike\", time.Hour*24)\n\n\th := res.Header().Get(\"Set-Cookie\")\n\tr.Equal(\"name=\\\"Rob Pike\\\"; Max-Age=86400\", h)\n}\n\nfunc TestCookies_SetWithPath(t *testing.T) {\n\tr := require.New(t)\n\tres := httptest.NewRecorder()\n\n\tc := Cookies{&http.Request{}, res}\n\n\tc.SetWithPath(\"name\", \"Rob Pike\", \"/foo\")\n\n\th := res.Header().Get(\"Set-Cookie\")\n\tr.Equal(\"name=\\\"Rob Pike\\\"; Path=/foo\", h)\n}\n\nfunc TestCookies_SetWithExpirationTime(t *testing.T) {\n\tr := require.New(t)\n\tres := httptest.NewRecorder()\n\n\tc := Cookies{&http.Request{}, res}\n\n\te := time.Date(2017, 7, 29, 19, 28, 45, 0, time.UTC)\n\tc.SetWithExpirationTime(\"name\", \"Rob Pike\", e)\n\n\th := res.Header().Get(\"Set-Cookie\")\n\tr.Equal(\"name=\\\"Rob Pike\\\"; Expires=Sat, 29 Jul 2017 19:28:45 GMT\", h)\n}\n\nfunc TestCookies_Delete(t *testing.T) {\n\tr := require.New(t)\n\tres := httptest.NewRecorder()\n\n\tc := Cookies{&http.Request{}, res}\n\n\tc.Delete(\"remove-me\")\n\n\th := res.Header().Get(\"Set-Cookie\")\n\tr.Equal(\"remove-me=v; Expires=Thu, 01 Jan 1970 00:00:00 GMT\", h)\n}\n"
  },
  {
    "path": "default_context.go",
    "content": "package buffalo\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/binding\"\n\t\"github.com/gobuffalo/buffalo/render\"\n)\n\n// assert that DefaultContext is implementing Context\nvar _ Context = &DefaultContext{}\nvar _ context.Context = &DefaultContext{}\n\n// TODO(sio4): #road-to-v1 - make DefaultContext private\n// and only allow it to be generated by App.newContext() or any similar.\n\n// DefaultContext is, as its name implies, a default\n// implementation of the Context interface.\ntype DefaultContext struct {\n\tcontext.Context\n\tresponse    http.ResponseWriter\n\trequest     *http.Request\n\tparams      url.Values\n\tlogger      Logger\n\tsession     *Session\n\tcontentType string\n\tdata        *requestData\n\tflash       *Flash\n}\n\n// Response returns the original Response for the request.\nfunc (d *DefaultContext) Response() http.ResponseWriter {\n\treturn d.response\n}\n\n// Request returns the original Request.\nfunc (d *DefaultContext) Request() *http.Request {\n\treturn d.request\n}\n\n// Params returns all of the parameters for the request,\n// including both named params and query string parameters.\nfunc (d *DefaultContext) Params() ParamValues {\n\treturn d.params\n}\n\n// Logger returns the Logger for this context.\nfunc (d *DefaultContext) Logger() Logger {\n\treturn d.logger\n}\n\n// Param returns a param, either named or query string,\n// based on the key.\nfunc (d *DefaultContext) Param(key string) string {\n\treturn d.Params().Get(key)\n}\n\n// Set a value onto the Context. Any value set onto the Context\n// will be automatically available in templates.\nfunc (d *DefaultContext) Set(key string, value any) {\n\tif d.data == nil {\n\t\td.data = newRequestData()\n\t}\n\td.data.moot.Lock()\n\tdefer d.data.moot.Unlock()\n\td.data.d[key] = value\n}\n\n// Value that has previously stored on the context.\nfunc (d *DefaultContext) Value(key any) any {\n\tif k, ok := key.(string); ok && d.data != nil {\n\t\td.data.moot.RLock()\n\t\tdefer d.data.moot.RUnlock()\n\t\tif v, ok := d.data.d[k]; ok {\n\n\t\t\treturn v\n\t\t}\n\t}\n\tif d.Context == nil {\n\t\treturn nil\n\t}\n\treturn d.Context.Value(key)\n}\n\n// Session for the associated Request.\nfunc (d *DefaultContext) Session() *Session {\n\treturn d.session\n}\n\n// Cookies for the associated request and response.\nfunc (d *DefaultContext) Cookies() *Cookies {\n\treturn &Cookies{d.request, d.response}\n}\n\n// Flash messages for the associated Request.\nfunc (d *DefaultContext) Flash() *Flash {\n\treturn d.flash\n}\n\ntype paginable interface {\n\tPaginate() string\n}\n\n// Render a status code and render.Renderer to the associated Response.\n// The request parameters will be made available to the render.Renderer\n// \"{{.params}}\". Any values set onto the Context will also automatically\n// be made available to the render.Renderer. To render \"no content\" pass\n// in a nil render.Renderer.\nfunc (d *DefaultContext) Render(status int, rr render.Renderer) error {\n\tstart := time.Now()\n\tdefer func() {\n\t\td.LogField(\"render\", time.Since(start))\n\t}()\n\n\tif rr == nil {\n\t\td.Response().WriteHeader(status)\n\t\treturn nil\n\t}\n\n\tdata := d.Data()\n\tpp := map[string]string{}\n\tfor k, v := range d.params {\n\t\tpp[k] = v[0]\n\t}\n\tdata[\"params\"] = pp\n\tdata[\"flash\"] = d.Flash().data\n\tdata[\"session\"] = d.Session()\n\tdata[\"request\"] = d.Request()\n\tdata[\"status\"] = status\n\tbb := &bytes.Buffer{}\n\n\terr := rr.Render(bb, data)\n\tif err != nil {\n\t\tvar er render.ErrRedirect\n\t\tif errors.As(err, &er) {\n\t\t\treturn d.Redirect(er.Status, er.URL)\n\t\t}\n\t\treturn HTTPError{Status: http.StatusInternalServerError, Cause: err}\n\t}\n\n\tif d.Session() != nil {\n\t\td.Flash().Clear()\n\t\td.Flash().persist(d.Session())\n\t\tif err := d.Session().Save(); err != nil {\n\t\t\treturn HTTPError{Status: http.StatusInternalServerError, Cause: err}\n\t\t}\n\t}\n\n\td.Response().Header().Set(\"Content-Type\", rr.ContentType())\n\tif p, ok := data[\"pagination\"].(paginable); ok {\n\t\td.Response().Header().Set(\"X-Pagination\", p.Paginate())\n\t}\n\td.Response().WriteHeader(status)\n\t_, err = io.Copy(d.Response(), bb)\n\tif err != nil {\n\t\treturn HTTPError{Status: http.StatusInternalServerError, Cause: err}\n\t}\n\n\treturn nil\n}\n\n// Bind the interface to the request.Body. The type of binding\n// is dependent on the \"Content-Type\" for the request. If the type\n// is \"application/json\" it will use \"json.NewDecoder\". If the type\n// is \"application/xml\" it will use \"xml.NewDecoder\". See the\n// github.com/gobuffalo/buffalo/binding package for more details.\nfunc (d *DefaultContext) Bind(value any) error {\n\treturn binding.Exec(d.Request(), value)\n}\n\n// LogField adds the key/value pair onto the Logger to be printed out\n// as part of the request logging. This allows you to easily add things\n// like metrics (think DB times) to your request.\nfunc (d *DefaultContext) LogField(key string, value any) {\n\tif d.logger == nil {\n\t\treturn\n\t}\n\td.logger = d.logger.WithField(key, value)\n}\n\n// LogFields adds the key/value pairs onto the Logger to be printed out\n// as part of the request logging. This allows you to easily add things\n// like metrics (think DB times) to your request.\nfunc (d *DefaultContext) LogFields(values map[string]any) {\n\tif d.logger == nil {\n\t\treturn\n\t}\n\td.logger = d.logger.WithFields(values)\n}\n\nfunc (d *DefaultContext) Error(status int, err error) error {\n\treturn HTTPError{Status: status, Cause: err}\n}\n\nvar mapType = reflect.ValueOf(map[string]any{}).Type()\n\n// Redirect a request with the given status to the given URL.\nfunc (d *DefaultContext) Redirect(status int, url string, args ...any) error {\n\tif d.Session() != nil {\n\t\td.Flash().persist(d.Session())\n\t\tif err := d.Session().Save(); err != nil {\n\t\t\treturn HTTPError{Status: http.StatusInternalServerError, Cause: err}\n\t\t}\n\t}\n\n\tif strings.HasSuffix(url, \"Path()\") {\n\t\tif len(args) > 1 {\n\t\t\treturn fmt.Errorf(\"you must pass only a map[string]any to a route path: %T\", args)\n\t\t}\n\t\tvar m map[string]any\n\t\tif len(args) == 1 {\n\t\t\trv := reflect.Indirect(reflect.ValueOf(args[0]))\n\t\t\tif !rv.Type().ConvertibleTo(mapType) {\n\t\t\t\treturn fmt.Errorf(\"you must pass only a map[string]any to a route path: %T\", args)\n\t\t\t}\n\t\t\tm = rv.Convert(mapType).Interface().(map[string]any)\n\t\t}\n\t\th, ok := d.Value(strings.TrimSuffix(url, \"()\")).(RouteHelperFunc)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"could not find a route helper named %s\", url)\n\t\t}\n\t\turl, err := h(m)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thttp.Redirect(d.Response(), d.Request(), string(url), status)\n\t\treturn nil\n\t}\n\n\tif len(args) > 0 {\n\t\turl = fmt.Sprintf(url, args...)\n\t}\n\thttp.Redirect(d.Response(), d.Request(), url, status)\n\treturn nil\n}\n\n// Data contains all the values set through Get/Set.\nfunc (d *DefaultContext) Data() map[string]any {\n\tm := map[string]any{}\n\n\tif d.data == nil {\n\t\treturn m\n\t}\n\td.data.moot.RLock()\n\tdefer d.data.moot.RUnlock()\n\tm = maps.Clone(d.data.d)\n\treturn m\n}\n\nfunc (d *DefaultContext) String() string {\n\tdata := d.Data()\n\tbb := make([]string, 0, len(data))\n\n\tfor k, v := range data {\n\t\tif _, ok := v.(RouteHelperFunc); !ok {\n\t\t\tbb = append(bb, fmt.Sprintf(\"%s: %s\", k, v))\n\t\t}\n\t}\n\tslices.Sort(bb)\n\treturn strings.Join(bb, \"\\n\\n\")\n}\n\n// File returns an uploaded file by name, or an error\nfunc (d *DefaultContext) File(name string) (binding.File, error) {\n\treq := d.Request()\n\tif err := req.ParseMultipartForm(5 * 1024 * 1024); err != nil {\n\t\treturn binding.File{}, err\n\t}\n\tf, h, err := req.FormFile(name)\n\tbf := binding.File{\n\t\tFile:       f,\n\t\tFileHeader: h,\n\t}\n\treturn bf, err\n}\n\n// MarshalJSON implements json marshaling for the context\nfunc (d *DefaultContext) MarshalJSON() ([]byte, error) {\n\tm := map[string]any{}\n\tdata := d.Data()\n\tfor k, v := range data {\n\t\t// don't try and marshal ourself\n\t\tif _, ok := v.(*DefaultContext); ok {\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := json.Marshal(v); err == nil {\n\t\t\t// it can be marshaled, so add it:\n\t\t\tm[k] = v\n\t\t}\n\t}\n\treturn json.Marshal(m)\n}\n"
  },
  {
    "path": "default_context_test.go",
    "content": "package buffalo\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/gobuffalo/logger\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc basicContext() DefaultContext {\n\treturn DefaultContext{\n\t\tContext: context.Background(),\n\t\tlogger:  logger.New(logger.DebugLevel),\n\t\tdata:    newRequestData(),\n\t\tflash:   &Flash{data: make(map[string][]string)},\n\t}\n}\n\nfunc Test_DefaultContext_Redirect(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\tu := \"/foo?bar=http%3A%2F%2Flocalhost%3A3000%2Flogin%2Fcallback%2Ffacebook\"\n\ta.GET(\"/\", func(c Context) error {\n\t\treturn c.Redirect(http.StatusFound, u)\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\tr.Equal(u, res.Location())\n}\n\nfunc Test_DefaultContext_Redirect_Helper(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []struct {\n\t\tE string\n\t\tI map[string]any\n\t\tS int\n\t}{\n\t\t{\n\t\t\tE: \"/foo/baz/\",\n\t\t\tI: map[string]any{\"bar\": \"baz\"},\n\t\t\tS: http.StatusPermanentRedirect,\n\t\t},\n\t\t{\n\t\t\tS: http.StatusInternalServerError,\n\t\t},\n\t}\n\n\tfor _, tt := range table {\n\t\ta := New(Options{})\n\t\ta.GET(\"/foo/{bar}\", func(c Context) error {\n\t\t\treturn c.Render(http.StatusOK, render.String(c.Param(\"bar\")))\n\t\t})\n\t\ta.GET(\"/\", func(c Context) error {\n\t\t\treturn c.Redirect(http.StatusPermanentRedirect, \"fooBarPath()\", tt.I)\n\t\t})\n\t\ta.GET(\"/nomap\", func(c Context) error {\n\t\t\treturn c.Redirect(http.StatusPermanentRedirect, \"rootPath()\")\n\t\t})\n\n\t\tw := httptest.New(a)\n\t\tres := w.HTML(\"/\").Get()\n\t\tr.Equal(tt.S, res.Code)\n\t\tr.Equal(tt.E, res.Location())\n\n\t\tres = w.HTML(\"/nomap\").Get()\n\t\tr.Equal(http.StatusPermanentRedirect, res.Code)\n\t\tr.Equal(\"/\", res.Location())\n\t}\n}\n\nfunc Test_DefaultContext_Param(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{\n\t\tparams: url.Values{\n\t\t\t\"name\": []string{\"Mark\"},\n\t\t},\n\t}\n\n\tr.Equal(\"Mark\", c.Param(\"name\"))\n}\n\nfunc Test_DefaultContext_Param_form(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := New(Options{})\n\tvar name string\n\tapp.POST(\"/\", func(c Context) error {\n\t\tname = c.Param(\"name\")\n\t\treturn nil\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/\").Post(map[string]string{\n\t\t\"name\": \"Mark\",\n\t})\n\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Equal(\"Mark\", name)\n}\n\nfunc Test_DefaultContext_Param_Multiple(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := New(Options{})\n\tvar params ParamValues\n\tvar param string\n\tapp.POST(\"/{id}\", func(c Context) error {\n\t\tparams = c.Params()\n\t\tparam = c.Param(\"id\")\n\t\treturn nil\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/a?id=c&y=z&id=d\").Post(map[string]string{\n\t\t\"id\": \"b\",\n\t})\n\tparamsExpected := url.Values{\n\t\t\"id\": []string{\"a\", \"b\", \"c\", \"d\"},\n\t\t\"y\":  []string{\"z\"},\n\t}\n\n\tr.Equal(200, res.Code)\n\tr.Equal(paramsExpected, params.(url.Values))\n\tr.Equal(\"a\", param)\n}\n\nfunc Test_DefaultContext_GetSet(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\tr.Nil(c.Value(\"name\"))\n\n\tc.Set(\"name\", \"Mark\")\n\tr.NotNil(c.Value(\"name\"))\n\tr.Equal(\"Mark\", c.Value(\"name\").(string))\n}\n\nfunc Test_DefaultContext_Set_not_configured(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{}\n\n\tc.Set(\"name\", \"Yonghwan\")\n\tr.NotNil(c.Value(\"name\"))\n\tr.Equal(\"Yonghwan\", c.Value(\"name\").(string))\n}\n\nfunc Test_DefaultContext_Value(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\tr.Nil(c.Value(\"name\"))\n\n\tc.Set(\"name\", \"Mark\")\n\tr.NotNil(c.Value(\"name\"))\n\tr.Equal(\"Mark\", c.Value(\"name\").(string))\n}\n\nfunc Test_DefaultContext_Value_not_configured(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{}\n\tr.Nil(c.Value(\"name\"))\n}\n\nfunc Test_DefaultContext_Render(t *testing.T) {\n\tr := require.New(t)\n\n\tc := basicContext()\n\tres := httptest.NewRecorder()\n\tc.response = res\n\tc.params = url.Values{\"name\": []string{\"Mark\"}}\n\tc.Set(\"greet\", \"Hello\")\n\n\terr := c.Render(http.StatusTeapot, render.String(`<%= greet %> <%= params[\"name\"] %>!`))\n\tr.NoError(err)\n\n\tr.Equal(http.StatusTeapot, res.Code)\n\tr.Equal(\"Hello Mark!\", res.Body.String())\n}\n\nfunc Test_DefaultContext_Bind_Default(t *testing.T) {\n\tr := require.New(t)\n\n\tuser := struct {\n\t\tFirstName string `form:\"first_name\"`\n\t}{}\n\n\ta := New(Options{})\n\ta.POST(\"/\", func(c Context) error {\n\t\terr := c.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tw := httptest.New(a)\n\tuv := url.Values{\"first_name\": []string{\"Mark\"}}\n\tres := w.HTML(\"/\").Post(uv)\n\tr.Equal(http.StatusCreated, res.Code)\n\n\tr.Equal(\"Mark\", user.FirstName)\n}\n\nfunc Test_DefaultContext_Bind_No_ContentType(t *testing.T) {\n\tr := require.New(t)\n\n\tuser := struct {\n\t\tFirstName string `form:\"first_name\"`\n\t}{\n\t\tFirstName: \"Mark\",\n\t}\n\n\ta := New(Options{})\n\ta.POST(\"/\", func(c Context) error {\n\t\terr := c.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn c.Error(http.StatusUnprocessableEntity, err)\n\t\t}\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tbb := &bytes.Buffer{}\n\treq, err := http.NewRequest(\"POST\", \"/\", bb)\n\tr.NoError(err)\n\treq.Header.Del(\"Content-Type\")\n\tres := httptest.NewRecorder()\n\ta.ServeHTTP(res, req)\n\tr.Equal(http.StatusUnprocessableEntity, res.Code)\n\tr.Contains(res.Body.String(), \"blank content type\")\n}\n\nfunc Test_DefaultContext_Bind_Empty_ContentType(t *testing.T) {\n\tr := require.New(t)\n\n\tuser := struct {\n\t\tFirstName string `form:\"first_name\"`\n\t}{\n\t\tFirstName: \"Mark\",\n\t}\n\n\ta := New(Options{})\n\ta.POST(\"/\", func(c Context) error {\n\t\terr := c.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn c.Error(http.StatusUnprocessableEntity, err)\n\t\t}\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tbb := &bytes.Buffer{}\n\treq, err := http.NewRequest(\"POST\", \"/\", bb)\n\tr.NoError(err)\n\t// Want to make sure that an empty string value does not cause an error on `split`\n\treq.Header.Set(\"Content-Type\", \"\")\n\tres := httptest.NewRecorder()\n\ta.ServeHTTP(res, req)\n\tr.Equal(http.StatusUnprocessableEntity, res.Code)\n\tr.Contains(res.Body.String(), \"blank content type\")\n}\n\nfunc Test_DefaultContext_Bind_Default_BlankFields(t *testing.T) {\n\tr := require.New(t)\n\n\tuser := struct {\n\t\tFirstName string `form:\"first_name\"`\n\t}{\n\t\tFirstName: \"Mark\",\n\t}\n\n\ta := New(Options{})\n\ta.POST(\"/\", func(c Context) error {\n\t\terr := c.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tw := httptest.New(a)\n\tuv := url.Values{\"first_name\": []string{\"\"}}\n\tres := w.HTML(\"/\").Post(uv)\n\tr.Equal(http.StatusCreated, res.Code)\n\n\tr.Equal(\"\", user.FirstName)\n}\n\nfunc Test_DefaultContext_Bind_JSON(t *testing.T) {\n\tr := require.New(t)\n\n\tuser := struct {\n\t\tFirstName string `json:\"first_name\"`\n\t}{}\n\n\ta := New(Options{})\n\ta.POST(\"/\", func(c Context) error {\n\t\terr := c.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tw := httptest.New(a)\n\tres := w.JSON(\"/\").Post(map[string]string{\n\t\t\"first_name\": \"Mark\",\n\t})\n\tr.Equal(http.StatusCreated, res.Code)\n\n\tr.Equal(\"Mark\", user.FirstName)\n}\n\nfunc Test_DefaultContext_Data(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\n\tr.EqualValues(map[string]any{}, c.Data())\n}\n\nfunc Test_DefaultContext_Data_not_configured(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{}\n\n\tr.EqualValues(map[string]any{}, c.Data())\n}\n\nfunc Test_DefaultContext_String(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\tc.Set(\"name\", \"Buffalo\")\n\tc.Set(\"language\", \"go\")\n\n\tr.EqualValues(\"language: go\\n\\nname: Buffalo\", c.String())\n}\n\nfunc Test_DefaultContext_String_EmptyData(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\tr.EqualValues(\"\", c.String())\n}\n\nfunc Test_DefaultContext_String_EmptyData_not_configured(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{}\n\n\tr.EqualValues(\"\", c.String())\n}\n\nfunc Test_DefaultContext_MarshalJSON(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\tc.Set(\"name\", \"Buffalo\")\n\tc.Set(\"language\", \"go\")\n\n\tjb, err := c.MarshalJSON()\n\tr.NoError(err)\n\tr.EqualValues(`{\"language\":\"go\",\"name\":\"Buffalo\"}`, string(jb))\n}\n\nfunc Test_DefaultContext_MarshalJSON_EmptyData(t *testing.T) {\n\tr := require.New(t)\n\tc := basicContext()\n\n\tjb, err := c.MarshalJSON()\n\tr.NoError(err)\n\tr.EqualValues(`{}`, string(jb))\n}\n\nfunc Test_DefaultContext_MarshalJSON_EmptyData_not_configured(t *testing.T) {\n\tr := require.New(t)\n\tc := DefaultContext{}\n\n\tjb, err := c.MarshalJSON()\n\tr.NoError(err)\n\tr.EqualValues(`{}`, string(jb))\n}\n"
  },
  {
    "path": "errors.go",
    "content": "package buffalo\n\nimport (\n\t\"database/sql\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"runtime/debug\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/defaults\"\n\t\"github.com/gobuffalo/buffalo/internal/httpx\"\n\t\"github.com/gobuffalo/events\"\n\t\"github.com/gobuffalo/plush/v5\"\n)\n\nvar (\n\t//go:embed internal/templates/error.dev.html\n\tdevErrorTmpl string\n\n\t//go:embed internal/templates/error.prod.html\n\tprodErrorTmpl string\n\n\t//go:embed internal/templates/notfound.prod.html\n\tprodNotFoundTmpl string\n)\n\n// HTTPError a typed error returned by http Handlers and used for choosing error handlers\ntype HTTPError struct {\n\tStatus int   `json:\"status\"`\n\tCause  error `json:\"error\"`\n}\n\n// Unwrap allows the error to be unwrapped.\nfunc (h HTTPError) Unwrap() error {\n\treturn h.Cause\n}\n\n// Error returns the cause of the error as string.\nfunc (h HTTPError) Error() string {\n\tif h.Cause != nil {\n\t\treturn h.Cause.Error()\n\t}\n\treturn \"unknown cause\"\n}\n\n// ErrorHandler interface for handling an error for a\n// specific status code.\ntype ErrorHandler func(int, error, Context) error\n\n// ErrorHandlers is used to hold a list of ErrorHandler\n// types that can be used to handle specific status codes.\n/*\n\ta.ErrorHandlers[http.StatusInternalServerError] = func(status int, err error, c buffalo.Context) error {\n\t\tres := c.Response()\n\t\tres.WriteHeader(status)\n\t\tres.Write([]byte(err.Error()))\n\t\treturn nil\n\t}\n*/\ntype ErrorHandlers map[int]ErrorHandler\n\n// Get a registered ErrorHandler for this status code. If\n// no ErrorHandler has been registered, a default one will\n// be returned.\nfunc (e ErrorHandlers) Get(status int) ErrorHandler {\n\tif eh, ok := e[status]; ok {\n\t\treturn eh\n\t}\n\tif eh, ok := e[0]; ok {\n\t\treturn eh\n\t}\n\treturn defaultErrorHandler\n}\n\n// Default sets an error handler should a status\n// code not already be mapped. This will replace\n// the original default error handler.\n// This is a *catch-all* handler.\nfunc (e ErrorHandlers) Default(eh ErrorHandler) {\n\tif eh != nil {\n\t\te[0] = eh\n\t}\n}\n\n// PanicHandler recovers from panics gracefully and calls\n// the error handling code for a 500 error.\nfunc (a *App) PanicHandler(next Handler) Handler {\n\treturn func(c Context) error {\n\t\tdefer func() { //catch or finally\n\t\t\tr := recover()\n\t\t\tvar err error\n\t\t\tif r != nil { //catch\n\t\t\t\tswitch t := r.(type) {\n\t\t\t\tcase error:\n\t\t\t\t\terr = t\n\t\t\t\tcase string:\n\t\t\t\t\terr = fmt.Errorf(\"%s\", t)\n\t\t\t\tdefault:\n\t\t\t\t\terr = fmt.Errorf(\"%s\", fmt.Sprint(t))\n\t\t\t\t}\n\n\t\t\t\tpayload := events.Payload{\n\t\t\t\t\t\"context\":    c,\n\t\t\t\t\t\"app\":        a,\n\t\t\t\t\t\"stacktrace\": string(debug.Stack()),\n\t\t\t\t\t\"error\":      err,\n\t\t\t\t}\n\t\t\t\tevents.EmitError(events.ErrPanic, err, payload)\n\n\t\t\t\teh := a.ErrorHandlers.Get(http.StatusInternalServerError)\n\t\t\t\teh(http.StatusInternalServerError, err, c)\n\t\t\t}\n\t\t}()\n\t\treturn next(c)\n\t}\n}\n\nfunc (a *App) defaultErrorMiddleware(next Handler) Handler {\n\treturn func(c Context) error {\n\t\terr := next(c)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\t// 500 Internal Server Error by default\n\t\tstatus := http.StatusInternalServerError\n\n\t\t// unpack root err and check for HTTPError\n\t\tif errors.Is(err, sql.ErrNoRows) {\n\t\t\tstatus = http.StatusNotFound\n\t\t}\n\t\tvar h HTTPError\n\t\tif errors.As(err, &h) {\n\t\t\tstatus = h.Status\n\t\t}\n\n\t\tpayload := events.Payload{\n\t\t\t\"context\": c,\n\t\t\t\"app\":     a,\n\t\t\t\"status\":  status,\n\t\t\t\"error\":   err,\n\t\t}\n\t\tif status >= http.StatusInternalServerError {\n\t\t\t// we need the details (or stack trace) only for 5xx errors.\n\t\t\t// pkg/errors supports '%+v' for stack trace.\n\t\t\t// the other type of errors that support '%+v' is also supported.\n\t\t\tpayload[\"stacktrace\"] = fmt.Sprintf(\"%+v\", err)\n\t\t}\n\t\tevents.EmitError(events.ErrGeneral, err, payload)\n\n\t\teh := a.ErrorHandlers.Get(status)\n\t\terr = eh(status, err, c)\n\t\tif err != nil {\n\t\t\tevents.Emit(events.Event{\n\t\t\t\tKind:    EvtFailureErr,\n\t\t\t\tMessage: \"unable to handle error and giving up\",\n\t\t\t\tError:   err,\n\t\t\t\tPayload: payload,\n\t\t\t})\n\t\t\t// things have really hit the fan if we're here!!\n\t\t\ta.Logger.Error(err)\n\t\t\tc.Response().WriteHeader(http.StatusInternalServerError)\n\t\t\tc.Response().Write([]byte(err.Error()))\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc productionErrorResponseFor(status int) []byte {\n\tif status == http.StatusNotFound {\n\t\treturn []byte(prodNotFoundTmpl)\n\t}\n\n\treturn []byte(prodErrorTmpl)\n}\n\n// ErrorResponse is a used to display errors as JSON or XML\ntype ErrorResponse struct {\n\tXMLName xml.Name `json:\"-\" xml:\"response\"`\n\tError   string   `json:\"error\" xml:\"error\"`\n\tTrace   string   `json:\"trace,omitempty\" xml:\"trace,omitempty\"`\n\tCode    int      `json:\"code\" xml:\"code,attr\"`\n}\n\nconst defaultErrorCT = \"text/html; charset=utf-8\"\n\nfunc defaultErrorHandler(status int, origErr error, c Context) error {\n\tenv := c.Value(\"env\")\n\trequestCT := defaults.String(httpx.ContentType(c.Request()), defaultErrorCT)\n\n\tvar defaultErrorResponse *ErrorResponse\n\n\tc.LogField(\"status\", status)\n\tc.Logger().Error(origErr)\n\tc.Response().WriteHeader(status)\n\n\tif env != nil && env.(string) != \"development\" {\n\t\tswitch strings.ToLower(requestCT) {\n\t\tcase \"application/json\", \"text/json\", \"json\", \"application/xml\", \"text/xml\", \"xml\":\n\t\t\tdefaultErrorResponse = &ErrorResponse{\n\t\t\t\tCode:  status,\n\t\t\t\tError: http.StatusText(status),\n\t\t\t}\n\t\tdefault:\n\t\t\tc.Response().Header().Set(\"content-type\", defaultErrorCT)\n\t\t\tresponseBody := productionErrorResponseFor(status)\n\t\t\tc.Response().Write(responseBody)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\ttrace := fmt.Sprintf(\"%+v\", origErr)\n\tif cause := errors.Unwrap(origErr); cause != nil {\n\t\torigErr = cause\n\t}\n\n\terrResponse := errorResponseDefault(defaultErrorResponse, &ErrorResponse{\n\t\tError: origErr.Error(),\n\t\tTrace: trace,\n\t\tCode:  status,\n\t})\n\n\tswitch strings.ToLower(requestCT) {\n\tcase \"application/json\", \"text/json\", \"json\":\n\t\tc.Response().Header().Set(\"content-type\", \"application/json\")\n\t\terr := json.NewEncoder(c.Response()).Encode(errResponse)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase \"application/xml\", \"text/xml\", \"xml\":\n\t\tc.Response().Header().Set(\"content-type\", \"text/xml\")\n\t\terr := xml.NewEncoder(c.Response()).Encode(errResponse)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\tc.Response().Header().Set(\"content-type\", defaultErrorCT)\n\t\tif err := c.Request().ParseForm(); err != nil {\n\t\t\ttrace = fmt.Sprintf(\"%s\\n%s\", err.Error(), trace)\n\t\t}\n\n\t\troutes := c.Value(\"routes\")\n\t\tcd := c.Data()\n\n\t\tdelete(cd, \"app\")\n\t\tdelete(cd, \"routes\")\n\n\t\tdata := map[string]any{\n\t\t\t\"routes\":      routes,\n\t\t\t\"error\":       trace,\n\t\t\t\"status\":      status,\n\t\t\t\"data\":        cd,\n\t\t\t\"params\":      c.Params(),\n\t\t\t\"posted_form\": c.Request().Form,\n\t\t\t\"context\":     c,\n\t\t\t\"headers\":     inspectHeaders(c.Request().Header),\n\t\t\t\"inspect\": func(v any) string {\n\t\t\t\treturn fmt.Sprintf(\"%+v\", v)\n\t\t\t},\n\t\t}\n\n\t\tctx := plush.NewContextWith(data)\n\t\tt, err := plush.Render(devErrorTmpl, ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = c.Response().Write([]byte(t))\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc errorResponseDefault(defaultResponse, alternativeResponse *ErrorResponse) *ErrorResponse {\n\tif defaultResponse != nil {\n\t\treturn defaultResponse\n\t}\n\treturn alternativeResponse\n}\n\ntype inspectHeaders http.Header\n\nfunc (i inspectHeaders) String() string {\n\n\tbb := make([]string, 0, len(i))\n\n\tfor k, v := range i {\n\t\tbb = append(bb, fmt.Sprintf(\"%s: %s\", k, v))\n\t}\n\tslices.Sort(bb)\n\treturn strings.Join(bb, \"\\n\")\n}\n"
  },
  {
    "path": "errors_test.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/gobuffalo/logger\"\n\t\"github.com/sirupsen/logrus\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// testLoggerHook is useful to test whats being logged.\ntype testLoggerHook struct {\n\terrors []*logrus.Entry\n}\n\nfunc (lh *testLoggerHook) Fire(entry *logrus.Entry) error {\n\tlh.errors = append(lh.errors, entry)\n\treturn nil\n}\n\nfunc (lh *testLoggerHook) Levels() []logrus.Level {\n\treturn []logrus.Level{\n\t\tlogrus.ErrorLevel,\n\t}\n}\n\nfunc Test_defaultErrorHandler_SetsContentType(t *testing.T) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn c.Error(http.StatusUnauthorized, fmt.Errorf(\"boom\"))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/\").Get()\n\tr.Equal(http.StatusUnauthorized, res.Code)\n\tct := res.Header().Get(\"content-type\")\n\tr.Equal(\"text/html; charset=utf-8\", ct)\n}\n\nfunc Test_defaultErrorHandler_Logger(t *testing.T) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn c.Error(http.StatusUnauthorized, fmt.Errorf(\"boom\"))\n\t})\n\n\ttestHook := &testLoggerHook{}\n\tl := logrus.New()\n\tl.SetOutput(os.Stdout)\n\tl.AddHook(testHook)\n\tlog := logger.Logrus{\n\t\tFieldLogger: l,\n\t}\n\tapp.Logger = log\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/\").Get()\n\tr.Equal(http.StatusUnauthorized, res.Code)\n\tr.Equal(http.StatusUnauthorized, testHook.errors[0].Data[\"status\"])\n}\n\nfunc Test_defaultErrorHandler_JSON_development(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"application/json\", \"development\")\n}\n\nfunc Test_defaultErrorHandler_XML_development(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"text/xml\", \"development\")\n}\n\nfunc Test_defaultErrorHandler_JSON_staging(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"application/json\", \"staging\")\n}\n\nfunc Test_defaultErrorHandler_XML_staging(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"text/xml\", \"staging\")\n}\n\nfunc Test_defaultErrorHandler_JSON_production(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"application/json\", \"production\")\n}\n\nfunc Test_defaultErrorHandler_XML_production(t *testing.T) {\n\ttestDefaultErrorHandler(t, \"text/xml\", \"production\")\n}\n\nfunc testDefaultErrorHandler(t *testing.T, contentType, env string) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tapp.Env = env\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn c.Error(http.StatusUnauthorized, fmt.Errorf(\"boom\"))\n\t})\n\n\tw := httptest.New(app)\n\tvar res *httptest.Response\n\tif contentType == \"application/json\" {\n\t\tres = w.JSON(\"/\").Get().Response\n\t} else {\n\t\tres = w.XML(\"/\").Get().Response\n\t}\n\tr.Equal(http.StatusUnauthorized, res.Code)\n\tct := res.Header().Get(\"content-type\")\n\tr.Equal(contentType, ct)\n\tb := res.Body.String()\n\n\tif env == \"development\" {\n\t\tif contentType == \"text/xml\" {\n\t\t\tr.Contains(b, `<response code=\"401\">`)\n\t\t\tr.Contains(b, `<error>boom</error>`)\n\t\t\tr.Contains(b, `<trace>`)\n\t\t\tr.Contains(b, `</trace>`)\n\t\t\tr.Contains(b, `</response>`)\n\t\t} else {\n\t\t\tr.Contains(b, `\"code\":401`)\n\t\t\tr.Contains(b, `\"error\":\"boom\"`)\n\t\t\tr.Contains(b, `\"trace\":\"`)\n\t\t}\n\t} else {\n\t\tif contentType == \"text/xml\" {\n\t\t\tr.Contains(b, `<response code=\"401\">`)\n\t\t\tr.Contains(b, fmt.Sprintf(`<error>%s</error>`, http.StatusText(http.StatusUnauthorized)))\n\t\t\tr.NotContains(b, `<trace>`)\n\t\t\tr.NotContains(b, `</trace>`)\n\t\t\tr.Contains(b, `</response>`)\n\t\t} else {\n\t\t\tr.Contains(b, `\"code\":401`)\n\t\t\tr.Contains(b, fmt.Sprintf(`\"error\":\"%s\"`, http.StatusText(http.StatusUnauthorized)))\n\t\t\tr.NotContains(b, `\"trace\":\"`)\n\t\t}\n\t}\n}\n\nfunc Test_defaultErrorHandler_nil_error(t *testing.T) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn c.Error(http.StatusInternalServerError, nil)\n\t})\n\n\tw := httptest.New(app)\n\tres := w.JSON(\"/\").Get()\n\tr.Equal(http.StatusInternalServerError, res.Code)\n}\n\nfunc Test_PanicHandler(t *testing.T) {\n\tapp := New(Options{})\n\tapp.GET(\"/string\", func(c Context) error {\n\t\tpanic(\"string boom\")\n\t})\n\tapp.GET(\"/error\", func(c Context) error {\n\t\tpanic(fmt.Errorf(\"error boom\"))\n\t})\n\n\ttable := []struct {\n\t\tpath     string\n\t\texpected string\n\t}{\n\t\t{\"/string\", \"string boom\"},\n\t\t{\"/error\", \"error boom\"},\n\t}\n\n\tconst stack = `github.com/gobuffalo/buffalo.Test_PanicHandler`\n\n\tw := httptest.New(app)\n\tfor _, tt := range table {\n\t\tt.Run(tt.path, func(st *testing.T) {\n\t\t\tr := require.New(st)\n\n\t\t\tres := w.HTML(\"%s\", tt.path).Get()\n\t\t\tr.Equal(http.StatusInternalServerError, res.Code)\n\n\t\t\tbody := res.Body.String()\n\t\t\tr.Contains(body, tt.expected)\n\t\t\tr.Contains(body, stack)\n\t\t})\n\t}\n}\n\nfunc Test_defaultErrorMiddleware(t *testing.T) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tvar x string\n\tvar ok bool\n\tapp.ErrorHandlers[http.StatusUnprocessableEntity] = func(code int, err error, c Context) error {\n\t\tx, ok = c.Value(\"T\").(string)\n\t\tc.Response().WriteHeader(code)\n\t\tc.Response().Write([]byte(err.Error()))\n\t\treturn nil\n\t}\n\tapp.Use(func(next Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tc.Set(\"T\", \"t\")\n\t\t\treturn c.Error(http.StatusUnprocessableEntity, fmt.Errorf(\"boom\"))\n\t\t}\n\t})\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn nil\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/\").Get()\n\tr.Equal(http.StatusUnprocessableEntity, res.Code)\n\tr.True(ok)\n\tr.Equal(\"t\", x)\n}\n\nfunc Test_SetErrorMiddleware(t *testing.T) {\n\tr := require.New(t)\n\tapp := New(Options{})\n\tapp.ErrorHandlers.Default(func(code int, err error, c Context) error {\n\t\tres := c.Response()\n\t\tres.WriteHeader(http.StatusTeapot)\n\t\tres.Write([]byte(\"i'm a teapot\"))\n\t\treturn nil\n\t})\n\tapp.GET(\"/\", func(c Context) error {\n\t\treturn c.Error(http.StatusUnprocessableEntity, fmt.Errorf(\"boom\"))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/\").Get()\n\tr.Equal(http.StatusTeapot, res.Code)\n\tr.Equal(\"i'm a teapot\", res.Body.String())\n}\n"
  },
  {
    "path": "events.go",
    "content": "package buffalo\n\n// TODO: TODO-v1 check if they are really need to be exported.\n/* The event id should be unique across packages as the format of\n   \"<package-name>:<additional-names>:<optional-error>\" as documented. They\n   should not be used by another packages to keep it informational. To make\n   it sure, they need to be internal.\n   Especially for plugable conponents like servers or workers, they can have\n   their own event definition if they need but the buffalo runtime can emit\n   generalize events when e.g. the runtime calls configured worker.\n*/\nconst (\n\t// EvtAppStart is emitted when buffalo.App#Serve is called\n\tEvtAppStart = \"buffalo:app:start\"\n\t// EvtAppStartErr is emitted when an error occurs calling buffalo.App#Serve\n\tEvtAppStartErr = \"buffalo:app:start:err\"\n\n\t// EvtAppStop is emitted when buffalo.App#Stop is called\n\tEvtAppStop = \"buffalo:app:stop\"\n\t// EvtAppStopErr is emitted when an error occurs calling buffalo.App#Stop\n\tEvtAppStopErr = \"buffalo:app:stop:err\"\n\n\t// EvtRouteStarted is emitted when a requested route is being processed\n\tEvtRouteStarted = \"buffalo:route:started\"\n\t// EvtRouteFinished is emitted when a requested route is completed\n\tEvtRouteFinished = \"buffalo:route:finished\"\n\t// EvtRouteErr is emitted when there is a problem handling processing a route\n\tEvtRouteErr = \"buffalo:route:err\"\n\n\t// EvtServerStart is emitted when buffalo is about to start servers\n\tEvtServerStart = \"buffalo:server:start\"\n\t// EvtServerStartErr is emitted when an error occurs when starting servers\n\tEvtServerStartErr = \"buffalo:server:start:err\"\n\t// EvtServerStop is emitted when buffalo is about to stop servers\n\tEvtServerStop = \"buffalo:server:stop\"\n\t// EvtServerStopErr is emitted when an error occurs when stopping servers\n\tEvtServerStopErr = \"buffalo:server:stop:err\"\n\n\t// EvtWorkerStart is emitted when buffalo is about to start workers\n\tEvtWorkerStart = \"buffalo:worker:start\"\n\t// EvtWorkerStartErr is emitted when an error occurs when starting workers\n\tEvtWorkerStartErr = \"buffalo:worker:start:err\"\n\t// EvtWorkerStop is emitted when buffalo is about to stop workers\n\tEvtWorkerStop = \"buffalo:worker:stop\"\n\t// EvtWorkerStopErr is emitted when an error occurs when stopping workers\n\tEvtWorkerStopErr = \"buffalo:worker:stop:err\"\n\n\t// EvtFailureErr is emitted when something can't be processed at all. it is a bad thing\n\tEvtFailureErr = \"buffalo:failure:err\"\n)\n"
  },
  {
    "path": "flash.go",
    "content": "package buffalo\n\nimport \"encoding/json\"\n\n// flashKey is the prefix inside the Session.\nconst flashKey = \"_flash_\"\n\n// Flash is a struct that helps with the operations over flash messages.\ntype Flash struct {\n\tdata map[string][]string\n}\n\n// Delete removes a particular key from the Flash.\nfunc (f Flash) Delete(key string) {\n\tdelete(f.data, key)\n}\n\n// Clear removes all keys from the Flash.\nfunc (f *Flash) Clear() {\n\tf.data = map[string][]string{}\n}\n\n// Set allows to set a list of values into a particular key.\nfunc (f Flash) Set(key string, values []string) {\n\tf.data[key] = values\n}\n\n// Add adds a flash value for a flash key, if the key already has values the list for that value grows.\nfunc (f Flash) Add(key, value string) {\n\tif len(f.data[key]) == 0 {\n\t\tf.data[key] = []string{value}\n\t\treturn\n\t}\n\n\tf.data[key] = append(f.data[key], value)\n}\n\n// Persist the flash inside the session.\nfunc (f Flash) persist(session *Session) {\n\tb, _ := json.Marshal(f.data)\n\tsession.Set(flashKey, b)\n}\n\n// newFlash creates a new Flash and loads the session data inside its data.\nfunc newFlash(session *Session) *Flash {\n\tresult := &Flash{\n\t\tdata: map[string][]string{},\n\t}\n\n\tif session.Session != nil {\n\t\tif f := session.Get(flashKey); f != nil {\n\t\t\tjson.Unmarshal(f.([]byte), &result.data)\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "flash_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"text/template\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_FlashAdd(t *testing.T) {\n\tr := require.New(t)\n\tf := newFlash(&Session{})\n\n\tr.Equal(f.data, map[string][]string{})\n\n\tf.Add(\"error\", \"something\")\n\tr.Equal(f.data, map[string][]string{\n\t\t\"error\": {\"something\"},\n\t})\n\n\tf.Add(\"error\", \"other\")\n\tr.Equal(f.data, map[string][]string{\n\t\t\"error\": {\"something\", \"other\"},\n\t})\n}\n\nfunc Test_FlashRender(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\tc.Flash().Add(\"errors\", \"Error AJ set\")\n\t\tc.Flash().Add(\"errors\", \"Error DAL set\")\n\n\t\treturn c.Render(http.StatusCreated, rr.String(errorsTPL))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\n\tr.Contains(res.Body.String(), \"Error AJ set\")\n\tr.Contains(res.Body.String(), \"Error DAL set\")\n}\n\nfunc Test_FlashRenderEmpty(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\treturn c.Render(http.StatusCreated, rr.String(errorsTPL))\n\t})\n\n\tw := httptest.New(a)\n\n\tres := w.HTML(\"/\").Get()\n\tr.NotContains(res.Body.String(), \"Flash:\")\n}\n\nconst errorsTPL = `\n<%= for (k, v) in flash[\"errors\"] { %>\n\tFlash:\n\t\t<%= k %>:<%= v %>\n<% } %>\n`\n\nfunc Test_FlashRenderEntireFlash(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\tc.Flash().Add(\"something\", \"something to say!\")\n\t\treturn c.Render(http.StatusCreated, rr.String(keyTPL))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\tr.Contains(res.Body.String(), \"something to say!\")\n}\n\nconst keyTPL = `<%= for (k, v) in flash { %>\n\tFlash:\n\t\t<%= k %>:<%= v %>\n<% } %>\n`\n\nfunc Test_FlashRenderCustomKey(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\tc.Flash().Add(\"something\", \"something to say!\")\n\t\treturn c.Render(http.StatusCreated, rr.String(keyTPL))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\tr.Contains(res.Body.String(), \"something to say!\")\n}\n\nfunc Test_FlashRenderCustomKeyNotDefined(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\treturn c.Render(http.StatusCreated, rr.String(customKeyTPL))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\tr.NotContains(res.Body.String(), \"something to say!\")\n}\n\nconst customKeyTPL = `\n\t{{#each flash.other as |k value|}}\n\t\t{{value}}\n\t{{/each}}\n`\n\nfunc Test_FlashNotClearedOnRedirect(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/flash\", func(c Context) error {\n\t\tc.Flash().Add(\"success\", \"Antonio, you're welcome!\")\n\t\treturn c.Redirect(http.StatusSeeOther, \"/\")\n\t})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\ttemplate := `Message: <%= flash[\"success\"] %>`\n\t\treturn c.Render(http.StatusCreated, rr.String(template))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/flash\").Get()\n\tr.Equal(res.Code, http.StatusSeeOther)\n\tr.Equal(res.Location(), \"/\")\n\n\tres = w.HTML(\"/\").Get()\n\tr.Contains(res.Body.String(), template.HTMLEscapeString(\"Antonio, you're welcome!\"))\n\n}\n"
  },
  {
    "path": "fs.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n)\n\n// FS wraps a directory and an embed FS that are expected to have the same contents.\n// it prioritizes the directory FS and falls back to the embedded FS if the file cannot\n// be found on disk. This is useful during development or when deploying with\n// assets not embedded in the binary.\n//\n// Additionally FS hiddes any file named embed.go from the FS.\ntype FS struct {\n\tembed fs.FS\n\tdir   fs.FS\n}\n\n// NewFS returns a new FS that wraps the given directory and embedded FS.\n// the embed.FS is expected to embed the same files as the directory FS.\nfunc NewFS(embed fs.ReadDirFS, dir string) FS {\n\treturn FS{\n\t\tembed: embed,\n\t\tdir:   os.DirFS(dir),\n\t}\n}\n\n// Open opens the named file.\n//\n// When Open returns an error, it should be of type *PathError with the Op\n// field set to \"open\", the Path field set to name, and the Err field\n// describing the problem.\n//\n// Open should reject attempts to open names that do not satisfy\n// ValidPath(name), returning a *PathError with Err set to ErrInvalid or\n// ErrNotExist.\nfunc (f FS) Open(name string) (fs.File, error) {\n\tif name == \"embed.go\" {\n\t\treturn nil, &fs.PathError{\n\t\t\tOp:   \"open\",\n\t\t\tPath: name,\n\t\t\tErr:  fs.ErrNotExist,\n\t\t}\n\t}\n\tfile, err := f.getFile(name)\n\tif name == \".\" {\n\t\t// NOTE: It always returns the root from the \"disk\" instead\n\t\t// \"embed\". However, it could be fine since the the purpose\n\t\t// of buffalo.FS isn't supporting full featured filesystem.\n\t\treturn rootFile{file}, err\n\t}\n\treturn file, err\n}\n\nfunc (f FS) getFile(name string) (fs.File, error) {\n\tfile, err := f.dir.Open(name)\n\tif err == nil {\n\t\treturn file, nil\n\t}\n\n\treturn f.embed.Open(name)\n}\n\n// rootFile wraps the \".\" directory for hidding the embed.go file.\ntype rootFile struct {\n\tfs.File\n}\n\n// ReadDir implements the fs.ReadDirFile interface.\nfunc (f rootFile) ReadDir(n int) (entries []fs.DirEntry, err error) {\n\tdir, ok := f.File.(fs.ReadDirFile)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%T is not a directory\", f.File)\n\t}\n\n\tentries, err = dir.ReadDir(n)\n\tentries = hideEmbedFile(entries)\n\treturn entries, err\n}\n\nfunc hideEmbedFile(entries []fs.DirEntry) []fs.DirEntry {\n\tresult := make([]fs.DirEntry, 0, len(entries))\n\n\tfor _, entry := range entries {\n\t\tif entry.Name() != \"embed.go\" {\n\t\t\tresult = append(result, entry)\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "fs_test.go",
    "content": "package buffalo\n\nimport (\n\t\"io\"\n\t\"io/fs\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/internal/testdata/embedded\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_FS_Disallows_Parent_Folders(t *testing.T) {\n\tr := require.New(t)\n\n\tfsys := NewFS(embedded.FS(), \"internal/testdata/disk\")\n\tr.NotNil(fsys)\n\n\tf, err := fsys.Open(\"../panic.txt\")\n\tr.ErrorIs(err, fs.ErrNotExist)\n\tr.Nil(f)\n\n\tf, err = fsys.Open(\"try/../to/../trick/../panic.txt\")\n\tr.ErrorIs(err, fs.ErrNotExist)\n\tr.Nil(f)\n}\n\nfunc Test_FS_Hides_embed_go(t *testing.T) {\n\tr := require.New(t)\n\n\tfsys := NewFS(embedded.FS(), \"internal/testdata/disk\")\n\tr.NotNil(fsys)\n\n\tf, err := fsys.Open(\"embed.go\")\n\tr.ErrorIs(err, fs.ErrNotExist)\n\tr.Nil(f)\n}\n\nfunc Test_FS_Prioritizes_Disk(t *testing.T) {\n\tr := require.New(t)\n\n\tfsys := NewFS(embedded.FS(), \"internal/testdata/disk\")\n\tr.NotNil(fsys)\n\n\tf, err := fsys.Open(\"file.txt\")\n\tr.NoError(err)\n\n\tb, err := io.ReadAll(f)\n\tr.NoError(err)\n\n\tr.Equal(\"This file is on disk.\", string(b))\n\n\t// should handle slash-separated path for all systems including Windows\n\tf, err = fsys.Open(\"under/sub/subfile\")\n\tr.NoError(err)\n\n\tb, err = io.ReadAll(f)\n\tr.NoError(err)\n\n\tr.Equal(\"This file is on disk/sub.\", string(b))\n}\n\nfunc Test_FS_Uses_Embed_If_No_Disk(t *testing.T) {\n\tr := require.New(t)\n\n\tfsys := NewFS(embedded.FS(), \"internal/testdata/empty\")\n\tr.NotNil(fsys)\n\n\tf, err := fsys.Open(\"file.txt\")\n\tr.NoError(err)\n\n\tb, err := io.ReadAll(f)\n\tr.NoError(err)\n\n\tr.Equal(\"This file is embedded.\", string(b))\n\n\t// should handle slash-separated path for all systems including Windows\n\tf, err = fsys.Open(\"under/sub/subfile\")\n\tr.NoError(err)\n\n\tb, err = io.ReadAll(f)\n\tr.NoError(err)\n\n\tr.Equal(\"This file is on embedded/sub.\", string(b))\n}\n\nfunc Test_FS_ReadDirFile(t *testing.T) {\n\tr := require.New(t)\n\n\tfsys := NewFS(embedded.FS(), \"internal/testdata/disk\")\n\tr.NotNil(fsys)\n\n\tf, err := fsys.Open(\".\")\n\tr.NoError(err)\n\n\tdir, ok := f.(fs.ReadDirFile)\n\tr.True(ok, \"folder does not implement fs.ReadDirFile interface\")\n\n\t// First read should return at most 1 file\n\tentries, err := dir.ReadDir(1)\n\tr.NoError(err)\n\n\t// The actual len will be 0 because the first file read is the embed.go file\n\t// this is counter-intuitive, but it's how the fs.ReadDirFile interface is specified;\n\t// if err == nil, just continue to call ReadDir until io.EOF is returned.\n\tr.LessOrEqual(len(entries), 1, \"a call to ReadDir must at most return n entries\")\n\n\t// Second read should return at most 2 files\n\tentries, err = dir.ReadDir(3)\n\tr.NoError(err)\n\n\t// The actual len will be 2 (file.txt & file2.txt + under/)\n\tr.LessOrEqual(len(entries), 3, \"a call to ReadDir must at most return n entries\")\n\n\t// trying to read next 2 files (none left)\n\tentries, err = dir.ReadDir(2)\n\tr.ErrorIs(err, io.EOF)\n\tr.Empty(entries)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/gobuffalo/buffalo\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/BurntSushi/toml v1.2.1\n\tgithub.com/gobuffalo/events v1.4.3\n\tgithub.com/gobuffalo/flect v1.0.3\n\tgithub.com/gobuffalo/github_flavored_markdown v1.1.4\n\tgithub.com/gobuffalo/helpers v0.6.10\n\tgithub.com/gobuffalo/httptest v1.5.2\n\tgithub.com/gobuffalo/logger v1.0.7\n\tgithub.com/gobuffalo/plush/v5 v5.0.11\n\tgithub.com/gobuffalo/refresh v1.13.3\n\tgithub.com/gobuffalo/tags/v3 v3.1.4\n\tgithub.com/gorilla/handlers v1.5.1\n\tgithub.com/gorilla/mux v1.8.0\n\tgithub.com/gorilla/sessions v1.2.1\n\tgithub.com/joho/godotenv v1.4.0\n\tgithub.com/monoculum/formam v3.5.5+incompatible\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/spf13/cobra v1.6.1\n\tgithub.com/stretchr/testify v1.9.0\n\tgolang.org/x/text v0.29.0\n)\n\nrequire (\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/fatih/color v1.13.0 // indirect\n\tgithub.com/fatih/structs v1.1.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.1 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/gobuffalo/validate/v3 v3.3.3 // indirect\n\tgithub.com/gofrs/uuid v4.2.0+incompatible // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/gorilla/securecookie v1.1.1 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.0.1 // indirect\n\tgithub.com/mattn/go-colorable v0.1.9 // indirect\n\tgithub.com/mattn/go-isatty v0.0.14 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/sergi/go-diff v1.3.1 // indirect\n\tgithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect\n\tgithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgolang.org/x/net v0.45.0 // indirect\n\tgolang.org/x/sys v0.36.0 // indirect\n\tgolang.org/x/term v0.35.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=\ngithub.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=\ngithub.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\ngithub.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=\ngithub.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/gobuffalo/events v1.4.3 h1:JYDq7NbozP10zaN9Ijfem6Ozox2KacU2fU38RyquXM8=\ngithub.com/gobuffalo/events v1.4.3/go.mod h1:2BwfpV5X63t8xkUcVqIv4IbyAobJazRSVu1F1pgf3rc=\ngithub.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE=\ngithub.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=\ngithub.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=\ngithub.com/gobuffalo/github_flavored_markdown v1.1.4 h1:WacrEGPXUDX+BpU1GM/Y0ADgMzESKNWls9hOTG1MHVs=\ngithub.com/gobuffalo/github_flavored_markdown v1.1.4/go.mod h1:Vl9686qrVVQou4GrHRK/KOG3jCZOKLUqV8MMOAYtlso=\ngithub.com/gobuffalo/helpers v0.6.10 h1:puKDCOrJ0EIq5ScnTRgKyvEZ05xQa+gwRGCpgoh6Ek8=\ngithub.com/gobuffalo/helpers v0.6.10/go.mod h1:r52L6VSnByLJFOmURp1irvzgSakk7RodChi1YbGwk8I=\ngithub.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM=\ngithub.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g=\ngithub.com/gobuffalo/logger v1.0.7 h1:LTLwWelETXDYyqF/ASf0nxaIcdEOIJNxRokPcfI/xbU=\ngithub.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM=\ngithub.com/gobuffalo/plush/v5 v5.0.11 h1:FlThobIUreYx8fM4pH2Sug8TLXfNtmhqj6JO1Qs5jT8=\ngithub.com/gobuffalo/plush/v5 v5.0.11/go.mod h1:C08u/VEqzzPBXFF/yqs40P/5Cvc/zlZsMzhCxXyWJmU=\ngithub.com/gobuffalo/refresh v1.13.3 h1:HYQlI6RiqWUf2yzCXvUHAYqm9M9/teVnox+mjzo/9rQ=\ngithub.com/gobuffalo/refresh v1.13.3/go.mod h1:NkzgLKZGk5suOvgvOD0/VALog0fH29Ib7fwym9JmRxA=\ngithub.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM=\ngithub.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0=\ngithub.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4=\ngithub.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g=\ngithub.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=\ngithub.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=\ngithub.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=\ngithub.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=\ngithub.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\ngithub.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=\ngithub.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=\ngithub.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=\ngithub.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/monoculum/formam v3.5.5+incompatible h1:iPl5csfEN96G2N2mGu8V/ZB62XLf9ySTpC8KRH6qXec=\ngithub.com/monoculum/formam v3.5.5+incompatible/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=\ngithub.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=\ngithub.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=\ngithub.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngolang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=\ngolang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=\ngolang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=\ngolang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=\ngolang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=\ngolang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=\ngolang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=\ngolang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "handler.go",
    "content": "package buffalo\n\n// Handler is the basis for all of Buffalo. A Handler\n// will be given a Context interface that represents the\n// give request/response. It is the responsibility of the\n// Handler to handle the request/response correctly. This\n// could mean rendering a template, JSON, etc... or it could\n// mean returning an error.\n/*\n\tfunc (c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"Hello World!\"))\n\t}\n\n\tfunc (c Context) error {\n\t\treturn c.Redirect(http.StatusMovedPermanently, \"http://github.com/gobuffalo/buffalo\")\n\t}\n\n\tfunc (c Context) error {\n\t\treturn c.Error(http.StatusUnprocessableEntity, fmt.Errorf(\"oops!!\"))\n\t}\n*/\ntype Handler func(Context) error\n"
  },
  {
    "path": "home.go",
    "content": "package buffalo\n\nimport (\n\t\"github.com/gorilla/mux\"\n)\n\n/* TODO: consider to split out Home (or Router, whatever) from App #road-to-v1\n   Group and Domain based multi-homing are actually not an App if the concept\n   of the App represents the application. The App should be only one for whole\n   application.\n\n   For an extreme example, App.Group().Stop() or even App.Group().Serve() are\n   still valid function calls while they should not be allowed and the result\n   could be strage.\n*/\n\n// Home is a container for Domains and Groups that independently serves a\n// group of pages with its own Middleware and ErrorHandlers. It is usually\n// a multi-homed server domain or group of paths under a certain prefix.\n//\n// While the App is for managing whole application life cycle along with its\n// default Home, including initializing and stopping its all components such\n// as listeners and long-running jobs, Home is only for a specific group of\n// services to serve its service logic efficiently.\ntype Home struct {\n\tapp     *App // will replace App.root\n\tappSelf *App // temporary while the App is in action.\n\t// replace Options' Name, Host, and Prefix\n\tname   string\n\thost   string\n\tprefix string\n\n\t// moved from App\n\t// Middleware returns the current MiddlewareStack for the App/Group.\n\tMiddleware    *MiddlewareStack `json:\"-\"`\n\tErrorHandlers ErrorHandlers    `json:\"-\"`\n\trouter        *mux.Router\n\tfilepaths     []string\n}\n"
  },
  {
    "path": "internal/defaults/defaults.go",
    "content": "package defaults\n\nfunc String(s1, s2 string) string {\n\tif s1 == \"\" {\n\t\treturn s2\n\t}\n\treturn s1\n}\n\nfunc Int(i1, i2 int) int {\n\tif i1 == 0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n\nfunc Int64(i1, i2 int64) int64 {\n\tif i1 == 0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n\nfunc Float32(i1, i2 float32) float32 {\n\tif i1 == 0.0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n\nfunc Float64(i1, i2 float64) float64 {\n\tif i1 == 0.0 {\n\t\treturn i2\n\t}\n\treturn i1\n}\n"
  },
  {
    "path": "internal/defaults/defaults_test.go",
    "content": "package defaults\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_String(t *testing.T) {\n\ta := assert.New(t)\n\n\ta.Equal(String(\"\", \"foo\"), \"foo\")\n\ta.Equal(String(\"bar\", \"foo\"), \"bar\")\n\tvar s string\n\ta.Equal(String(s, \"foo\"), \"foo\")\n}\n\nfunc Test_Int(t *testing.T) {\n\ta := assert.New(t)\n\n\ta.Equal(Int(0, 1), 1)\n\ta.Equal(Int(2, 1), 2)\n\tvar s int\n\ta.Equal(Int(s, 1), 1)\n}\n\nfunc Test_Int64(t *testing.T) {\n\ta := assert.New(t)\n\n\ta.Equal(Int64(0, 1), int64(1))\n\ta.Equal(Int64(2, 1), int64(2))\n\tvar s int64\n\ta.Equal(Int64(s, 1), int64(1))\n}\n\nfunc Test_Float32(t *testing.T) {\n\ta := assert.New(t)\n\n\ta.Equal(Float32(0, 1), float32(1))\n\ta.Equal(Float32(2, 1), float32(2))\n\tvar s float32\n\ta.Equal(Float32(s, 1), float32(1))\n}\n\nfunc Test_Float64(t *testing.T) {\n\ta := assert.New(t)\n\n\ta.Equal(Float64(0, 1), float64(1))\n\ta.Equal(Float64(2, 1), float64(2))\n\tvar s float64\n\ta.Equal(Float64(s, 1), float64(1))\n}\n"
  },
  {
    "path": "internal/env/env.go",
    "content": "// Package env provides environment variable utilities for Buffalo.\n// This package replaces the github.com/gobuffalo/envy dependency.\npackage env\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/joho/godotenv\"\n)\n\n// Load loads .env file(s) into environment.\n// If no filenames are provided, it loads the .env file in the current directory.\nfunc Load(filenames ...string) error {\n\treturn godotenv.Load(filenames...)\n}\n\n// Get returns the value of the environment variable named by key.\n// If the variable is not set or is empty, it returns the fallback value.\nfunc Get(key, fallback string) string {\n\tif v := os.Getenv(key); v != \"\" {\n\t\treturn v\n\t}\n\treturn fallback\n}\n\n// MustGet returns the value of the environment variable named by key.\n// It returns an error if the variable is not set or is empty.\nfunc MustGet(key string) (string, error) {\n\tv := os.Getenv(key)\n\tif v == \"\" {\n\t\treturn \"\", fmt.Errorf(\"environment variable %s is not set\", key)\n\t}\n\treturn v, nil\n}\n\n// Environ returns a copy of the environment variables.\nfunc Environ() []string {\n\treturn os.Environ()\n}\n\n// GoPath returns the GOPATH environment variable.\n// If GOPATH is not set, it returns the default GOPATH: $HOME/go\nfunc GoPath() string {\n\tif gp := os.Getenv(\"GOPATH\"); gp != \"\" {\n\t\treturn gp\n\t}\n\thome, _ := os.UserHomeDir()\n\treturn filepath.Join(home, \"go\")\n}\n\n// Set sets the environment variable named by key to value.\nfunc Set(key, value string) {\n\tos.Setenv(key, value)\n}\n\n// Temp executes f with temporarily modified environment variables.\n// After f returns, the environment is restored to its original state.\nfunc Temp(f func()) {\n\toldEnv := os.Environ()\n\tdefer func() {\n\t\tos.Clearenv()\n\t\tfor _, e := range oldEnv {\n\t\t\tif i := strings.IndexByte(e, '='); i > 0 {\n\t\t\t\tos.Setenv(e[:i], e[i+1:])\n\t\t\t}\n\t\t}\n\t}()\n\tos.Clearenv()\n\tf()\n}\n"
  },
  {
    "path": "internal/fakesmtp/connection.go",
    "content": "package fakesmtp\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"net\"\n)\n\n// Connection of a client with our server\ntype Connection struct {\n\tconn    net.Conn\n\taddress string\n\ttime    int64\n\tbufin   *bufio.Reader\n\tbufout  *bufio.Writer\n}\n\n// write something to the client on the connection\nfunc (c *Connection) write(s string) {\n\tc.bufout.WriteString(s + \"\\r\\n\")\n\tc.bufout.Flush()\n}\n\n// read a string from the connected client\nfunc (c *Connection) read() string {\n\treply, err := c.bufin.ReadString('\\n')\n\n\tif err != nil {\n\t\tfmt.Println(\"e \", err)\n\t}\n\treturn reply\n}\n"
  },
  {
    "path": "internal/fakesmtp/server.go",
    "content": "package fakesmtp\n\n// This server is inspired by https://github.com/andrewarrow/jungle_smtp\n// and most of its functionality have been taken from the original repo and updated to\n// work better for buffalo.\n\nimport (\n\t\"bufio\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Server is our fake server that will be listening for SMTP connections.\ntype Server struct {\n\tListener net.Listener\n\tmessages []string\n\tmutex    sync.Mutex\n}\n\n// Start listens for connections on the given port\nfunc (s *Server) Start(port string) error {\n\tfor {\n\t\tconn, err := s.Listener.Accept()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ts.Handle(&Connection{\n\t\t\tconn:    conn,\n\t\t\taddress: conn.RemoteAddr().String(),\n\t\t\ttime:    time.Now().Unix(),\n\t\t\tbufin:   bufio.NewReader(conn),\n\t\t\tbufout:  bufio.NewWriter(conn),\n\t\t})\n\t}\n}\n\n// Handle a connection from a client\nfunc (s *Server) Handle(c *Connection) {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\ts.messages = append(s.messages, \"\")\n\n\ts.readHello(c)\n\ts.readSender(c)\n\ts.readRecipients(c)\n\ts.readData(c)\n\n\tc.conn.Close()\n}\n\n// Requests and notifies readed the Hello\nfunc (s *Server) readHello(c *Connection) {\n\tc.write(\"220 Welcome\")\n\ttext := c.read()\n\ts.addMessageLine(text)\n\n\tc.write(\"250 Received\")\n}\n\n// readSender reads the Sender from the connection\nfunc (s *Server) readSender(c *Connection) {\n\ttext := c.read()\n\ts.addMessageLine(text)\n\tc.write(\"250 Sender\")\n}\n\n// readRecipients reads recipients from the connection\nfunc (s *Server) readRecipients(c *Connection) {\n\ttext := c.read()\n\ts.addMessageLine(text)\n\n\tc.write(\"250 Recipient\")\n\ttext = c.read()\n\tfor strings.Contains(text, \"RCPT\") {\n\t\ts.addMessageLine(text)\n\t\tc.write(\"250 Recipient\")\n\t\ttext = c.read()\n\t}\n}\n\n// readData reads the message data.\nfunc (s *Server) readData(c *Connection) {\n\tc.write(\"354 Ok Send data ending with <CRLF>.<CRLF>\")\n\n\tfor {\n\t\ttext := c.read()\n\t\tbytes := []byte(text)\n\t\ts.addMessageLine(text)\n\t\t// 46 13 10\n\t\tif bytes[0] == 46 && bytes[1] == 13 && bytes[2] == 10 {\n\t\t\tbreak\n\t\t}\n\t}\n\tc.write(\"250 server has transmitted the message\")\n}\n\n// addMessageLine ads a line to the last message\nfunc (s *Server) addMessageLine(text string) {\n\ts.messages[len(s.Messages())-1] = s.LastMessage() + text\n}\n\n// LastMessage returns the last message on the server\nfunc (s *Server) LastMessage() string {\n\tif len(s.Messages()) == 0 {\n\t\treturn \"\"\n\t}\n\n\treturn s.Messages()[len(s.Messages())-1]\n}\n\n// Messages returns the list of messages on the server\nfunc (s *Server) Messages() []string {\n\treturn s.messages\n}\n\n// Clear the server messages\nfunc (s *Server) Clear() {\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\ts.messages = []string{}\n}\n\n// New returns a pointer to a new Server instance listening on the given port.\nfunc New(port string) (*Server, error) {\n\ts := &Server{messages: []string{}}\n\n\tlistener, err := net.Listen(\"tcp\", \"0.0.0.0:\"+port)\n\tif err != nil {\n\t\treturn s, err\n\t}\n\ts.Listener = listener\n\treturn s, nil\n}\n"
  },
  {
    "path": "internal/httpx/content_type.go",
    "content": "package httpx\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/defaults\"\n)\n\nfunc ContentType(req *http.Request) string {\n\tct := defaults.String(req.Header.Get(\"Content-Type\"), req.Header.Get(\"Accept\"))\n\tct = strings.TrimSpace(ct)\n\tvar cts []string\n\tif strings.Contains(ct, \",\") {\n\t\tcts = strings.Split(ct, \",\")\n\t} else {\n\t\tcts = strings.Split(ct, \";\")\n\t}\n\tfor _, c := range cts {\n\t\tc = strings.TrimSpace(c)\n\t\tif strings.HasPrefix(c, \"*/*\") {\n\t\t\tcontinue\n\t\t}\n\t\treturn strings.ToLower(c)\n\t}\n\tif ct == \"*/*\" {\n\t\treturn \"\"\n\t}\n\treturn ct\n}\n"
  },
  {
    "path": "internal/httpx/content_type_test.go",
    "content": "package httpx\n\nimport (\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_ContentType(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []struct {\n\t\tHeader   string\n\t\tValue    string\n\t\tExpected string\n\t}{\n\t\t{\"content-type\", \"a\", \"a\"},\n\t\t{\"Content-Type\", \"c,d\", \"c\"},\n\t\t{\"Content-Type\", \"e;f\", \"e\"},\n\t\t{\"Content-Type\", \"\", \"\"},\n\t\t{\"Accept\", \"\", \"\"},\n\t\t{\"Accept\", \"*/*\", \"\"},\n\t\t{\"Accept\", \"*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\", \"text/javascript\"},\n\t\t{\"accept\", \"text/javascript,application/javascript,application/ecmascript,application/x-ecmascript\", \"text/javascript\"},\n\t}\n\n\tfor _, tt := range table {\n\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\t\treq.Header.Set(tt.Header, tt.Value)\n\t\tr.Equal(tt.Expected, ContentType(req))\n\t}\n}\n"
  },
  {
    "path": "internal/meta/meta.go",
    "content": "// Package meta provides application metadata for Buffalo's plugin system.\npackage meta\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/BurntSushi/toml\"\n)\n\n// BuildTags is a type alias for build tags used by plugins.\ntype BuildTags []string\n\n// App holds metadata about the Buffalo application.\ntype App struct {\n\tRoot    string `toml:\"-\"`\n\tWithPop bool   `toml:\"with_pop\"`\n}\n\n// New creates App metadata for the given root path.\n// If root is \".\" or empty, uses current working directory.\n// First tries to load WithPop from config/buffalo-app.toml,\n// then falls back to detecting database.yml.\nfunc New(root string) App {\n\tif root == \".\" || root == \"\" {\n\t\tif pwd, err := os.Getwd(); err == nil {\n\t\t\troot = pwd\n\t\t}\n\t}\n\n\tapp := App{Root: root}\n\n\ttomlPath := filepath.Join(root, \"config\", \"buffalo-app.toml\")\n\tif _, err := os.Stat(tomlPath); err == nil {\n\t\t// TOML config exists, use it and skip auto-detection\n\t\ttoml.DecodeFile(tomlPath, &app)\n\t\treturn app\n\t}\n\n\t// No TOML config, auto-detect from filesystem\n\tif _, err := os.Stat(filepath.Join(root, \"database.yml\")); err == nil {\n\t\tapp.WithPop = true\n\t}\n\n\treturn app\n}\n"
  },
  {
    "path": "internal/meta/meta_test.go",
    "content": "package meta\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_New_Defaults(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := New(\"\")\n\tr.NotEmpty(app.Root)\n\tr.False(app.WithPop)\n\n\tapp = New(\".\")\n\tr.NotEmpty(app.Root)\n}\n\nfunc Test_New_With_DatabaseYML(t *testing.T) {\n\tr := require.New(t)\n\n\ttmp := t.TempDir()\n\tdbYML := filepath.Join(tmp, \"database.yml\")\n\tr.NoError(os.WriteFile(dbYML, []byte(\"test\"), 0644))\n\n\tapp := New(tmp)\n\tr.Equal(tmp, app.Root)\n\tr.True(app.WithPop)\n}\n\nfunc Test_New_With_TOML(t *testing.T) {\n\tr := require.New(t)\n\n\ttmp := t.TempDir()\n\tconfigDir := filepath.Join(tmp, \"config\")\n\tr.NoError(os.MkdirAll(configDir, 0755))\n\n\ttomlContent := `with_pop = true`\n\ttomlPath := filepath.Join(configDir, \"buffalo-app.toml\")\n\tr.NoError(os.WriteFile(tomlPath, []byte(tomlContent), 0644))\n\n\tapp := New(tmp)\n\tr.Equal(tmp, app.Root)\n\tr.True(app.WithPop)\n}\n\nfunc Test_New_TOML_Priority_Over_DatabaseYML(t *testing.T) {\n\tr := require.New(t)\n\n\ttmp := t.TempDir()\n\n\t// Create both files\n\tconfigDir := filepath.Join(tmp, \"config\")\n\tr.NoError(os.MkdirAll(configDir, 0755))\n\n\ttomlContent := `with_pop = false`\n\ttomlPath := filepath.Join(configDir, \"buffalo-app.toml\")\n\tr.NoError(os.WriteFile(tomlPath, []byte(tomlContent), 0644))\n\n\tdbYML := filepath.Join(tmp, \"database.yml\")\n\tr.NoError(os.WriteFile(dbYML, []byte(\"test\"), 0644))\n\n\t// TOML should take priority\n\tapp := New(tmp)\n\tr.False(app.WithPop)\n}\n\nfunc Test_New_No_Files(t *testing.T) {\n\tr := require.New(t)\n\n\ttmp := t.TempDir()\n\n\tapp := New(tmp)\n\tr.Equal(tmp, app.Root)\n\tr.False(app.WithPop)\n}\n"
  },
  {
    "path": "internal/nulls/nulls.go",
    "content": "package nulls\n\nimport (\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"time\"\n)\n\n// Time represents a time.Time that may be null.\n// It implements sql.Scanner and driver.Valuer interfaces.\ntype Time struct {\n\tTime  time.Time\n\tValid bool // Valid is true if Time is not NULL\n}\n\n// Scan implements the sql.Scanner interface.\nfunc (t *Time) Scan(value any) error {\n\tif value == nil {\n\t\tt.Time, t.Valid = time.Time{}, false\n\t\treturn nil\n\t}\n\tt.Valid = true\n\tswitch v := value.(type) {\n\tcase time.Time:\n\t\tt.Time = v\n\tcase []byte:\n\t\treturn t.Parse(string(v))\n\tcase string:\n\t\treturn t.Parse(v)\n\t}\n\treturn nil\n}\n\n// Parse tries to parse the string as a time using multiple formats.\nfunc (t *Time) Parse(s string) error {\n\tformats := []string{\n\t\ttime.RFC3339,\n\t\t\"2006-01-02 15:04:05\",\n\t\t\"2006-01-02\",\n\t\t\"01/02/2006\",\n\t\t\"01/02/2006 15:04:05\",\n\t\t\"2006-01-02T15:04:05\",\n\t\ttime.RFC3339Nano,\n\t}\n\n\tfor _, format := range formats {\n\t\tif tt, err := time.Parse(format, s); err == nil {\n\t\t\tt.Time = tt\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value implements the driver.Valuer interface.\nfunc (t Time) Value() (driver.Value, error) {\n\tif !t.Valid {\n\t\treturn nil, nil\n\t}\n\treturn t.Time, nil\n}\n\n// UnmarshalJSON implements json.Unmarshaler.\nfunc (t *Time) UnmarshalJSON(data []byte) error {\n\tif string(data) == \"null\" {\n\t\tt.Time, t.Valid = time.Time{}, false\n\t\treturn nil\n\t}\n\tif len(data) >= 2 && data[0] == '\"' && data[len(data)-1] == '\"' {\n\t\tdata = data[1 : len(data)-1]\n\t}\n\treturn t.Parse(string(data))\n}\n\n// MarshalJSON implements json.Marshaler.\nfunc (t Time) MarshalJSON() ([]byte, error) {\n\tif !t.Valid {\n\t\treturn []byte(\"null\"), nil\n\t}\n\treturn json.Marshal(t.Time)\n}\n\n// String implements fmt.Stringer.\nfunc (t Time) String() string {\n\tif !t.Valid {\n\t\treturn \"\"\n\t}\n\treturn t.Time.String()\n}\n\n// NewTime returns a new, properly initialized\n// Time object.\nfunc NewTime(t time.Time) Time {\n\treturn Time{\n\t\tTime:  t,\n\t\tValid: true,\n\t}\n}\n"
  },
  {
    "path": "internal/templates/error.dev.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>\n        <%= status %> - ERROR!\n    </title>\n    <style>\n        html {\n            font-family: sans-serif;\n            -webkit-text-size-adjust: 100%;\n            -ms-text-size-adjust: 100%\n        }\n\n        body {\n            margin: 0\n        }\n\n        header {\n            display: block\n        }\n\n        a {\n            background-color: transparent\n        }\n\n        a:active,\n        a:hover {\n            outline: 0\n        }\n\n        h1 {\n            margin: .67em 0;\n            font-size: 2em\n        }\n\n        img {\n            border: 0\n        }\n\n        pre {\n            overflow: auto\n        }\n\n        code,\n        pre {\n            font-family: monospace, monospace;\n            font-size: 1em\n        }\n\n        table {\n            border-spacing: 0;\n            border-collapse: collapse\n        }\n\n        td,\n        th {\n            padding: 0\n        }\n\n        @media print {\n            * {\n                color: #000 !important;\n                text-shadow: none !important;\n                background: 0 0 !important;\n                -webkit-box-shadow: none !important;\n                box-shadow: none !important\n            }\n\n            a,\n            a:visited {\n                text-decoration: underline\n            }\n\n            a[href]:after {\n                content: \" (\"attr(href) \")\"\n            }\n\n            pre {\n                border: 1px solid #999;\n                page-break-inside: avoid\n            }\n\n            thead {\n                display: table-header-group\n            }\n\n            img,\n            tr {\n                page-break-inside: avoid\n            }\n\n            img {\n                max-width: 100% !important\n            }\n\n            h3 {\n                orphans: 3;\n                widows: 3\n            }\n\n            h3 {\n                page-break-after: avoid\n            }\n\n            .table {\n                border-collapse: collapse !important\n            }\n\n            .table td,\n            .table th {\n                background-color: #fff !important\n            }\n        }\n\n        @font-face {\n            font-family: 'Glyphicons Halflings';\n            src: url(../fonts/glyphicons-halflings-regular.eot);\n            src: url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'), url(../fonts/glyphicons-halflings-regular.woff) format('woff'), url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')\n        }\n\n        * {\n            -webkit-box-sizing: border-box;\n            -moz-box-sizing: border-box;\n            box-sizing: border-box\n        }\n\n        html {\n            font-size: 10px;\n            -webkit-tap-highlight-color: rgba(0, 0, 0, 0)\n        }\n\n        body {\n            font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n            font-size: 14px;\n            line-height: 1.42857143;\n            color: #333;\n            background-color: #fff\n        }\n\n        a {\n            color: #337ab7;\n            text-decoration: none\n        }\n\n        a:focus,\n        a:hover {\n            color: #23527c;\n            text-decoration: underline\n        }\n\n        a:focus {\n            outline: 5px auto -webkit-focus-ring-color;\n            outline-offset: -2px\n        }\n\n        img {\n            vertical-align: middle\n        }\n\n        h1,\n        h3 {\n            font-family: inherit;\n            font-weight: 500;\n            line-height: 1.1;\n            color: inherit\n        }\n\n        h1,\n        h3 {\n            margin-top: 20px;\n            margin-bottom: 10px\n        }\n\n        h1 {\n            font-size: 36px\n        }\n\n        h3 {\n            font-size: 24px\n        }\n\n        code,\n        pre {\n            font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace\n        }\n\n        code {\n            padding: 2px 4px;\n            font-size: 90%;\n            color: #c7254e;\n            background-color: #f9f2f4;\n            border-radius: 4px\n        }\n\n        pre {\n            display: block;\n            padding: 9.5px;\n            margin: 0 0 10px;\n            font-size: 13px;\n            line-height: 1.42857143;\n            color: #333;\n            word-break: break-all;\n            word-wrap: break-word;\n            background-color: #f5f5f5;\n            border: 1px solid #ccc;\n            border-radius: 4px\n        }\n\n        .container {\n            padding-right: 15px;\n            padding-left: 15px;\n            margin-right: auto;\n            margin-left: auto\n        }\n\n        @media (min-width:768px) {\n            .container {\n                width: 750px\n            }\n        }\n\n        @media (min-width:992px) {\n            .container {\n                width: 970px\n            }\n        }\n\n        @media (min-width:1200px) {\n            .container {\n                width: 1170px\n            }\n        }\n\n        .row {\n            margin-right: -15px;\n            margin-left: -15px\n        }\n\n        .col-md-1,\n        .col-md-10,\n        .col-md-12,\n        .col-sm-2,\n        .col-sm-6,\n        .col-xs-3,\n        .col-xs-7 {\n            position: relative;\n            min-height: 1px;\n            padding-right: 15px;\n            padding-left: 15px\n        }\n\n        .col-xs-3,\n        .col-xs-7 {\n            float: left\n        }\n\n        .col-xs-7 {\n            width: 58.33333333%\n        }\n\n        .col-xs-3 {\n            width: 25%\n        }\n\n        @media (min-width:768px) {\n\n            .col-sm-2,\n            .col-sm-6 {\n                float: left\n            }\n\n            .col-sm-6 {\n                width: 50%\n            }\n\n            .col-sm-2 {\n                width: 16.66666667%\n            }\n        }\n\n        @media (min-width:992px) {\n\n            .col-md-1,\n            .col-md-10,\n            .col-md-12 {\n                float: left\n            }\n\n            .col-md-12 {\n                width: 100%\n            }\n\n            .col-md-10 {\n                width: 83.33333333%\n            }\n\n            .col-md-1 {\n                width: 8.33333333%\n            }\n        }\n\n        table {\n            background-color: transparent\n        }\n\n        th {\n            text-align: left\n        }\n\n        .table {\n            width: 100%;\n            max-width: 100%;\n            margin-bottom: 20px\n        }\n\n        .table>tbody>tr>td,\n        .table>thead>tr>th {\n            padding: 8px;\n            line-height: 1.42857143;\n            vertical-align: top;\n            border-top: 1px solid #ddd\n        }\n\n        .table>thead>tr>th {\n            vertical-align: bottom;\n            border-bottom: 2px solid #ddd\n        }\n\n        .table>thead:first-child>tr:first-child>th {\n            border-top: 0\n        }\n\n        .table-striped>tbody>tr:nth-of-type(odd) {\n            background-color: #f9f9f9\n        }\n\n        .container:after,\n        .container:before,\n        .row:after,\n        .row:before {\n            display: table;\n            content: \" \"\n        }\n\n        .container:after,\n        .row:after {\n            clear: both\n        }\n\n        @-ms-viewport {\n            width: device-width\n        }\n\n        h1 {\n            margin-top: 20px\n        }\n\n        * {\n            -webkit-box-sizing: border-box;\n            -moz-box-sizing: border-box;\n            box-sizing: border-box\n        }\n\n        body {\n            font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n            font-size: 14px;\n            line-height: 1.42857143;\n            color: #333;\n            background-color: #fff;\n            margin: 0\n        }\n\n        h1 {\n            margin-bottom: 10px;\n            font-family: inherit;\n            font-weight: 500;\n            line-height: 1.1;\n            color: inherit\n        }\n\n        .table {\n            margin-bottom: 20px\n        }\n\n        h1 {\n            font-size: 36px\n        }\n\n        a {\n            color: #337ab7;\n            text-decoration: none\n        }\n\n        a:hover {\n            color: #23527c\n        }\n\n        .container {\n            padding-right: 15px;\n            padding-left: 15px;\n            margin-right: auto;\n            margin-left: auto\n        }\n\n        @media (min-width:768px) {\n            .container {\n                width: 750px\n            }\n        }\n\n        @media (min-width:992px) {\n            .container {\n                width: 970px\n            }\n        }\n\n        @media (min-width:1200px) {\n            .container {\n                width: 1170px\n            }\n        }\n\n        .table {\n            width: 100%;\n            max-width: 100%;\n            background-color: transparent;\n            border-spacing: 0;\n            border-collapse: collapse\n        }\n\n        .table-striped>tbody {\n            background-color: #f9f9f9\n        }\n\n        .table>tbody>tr>td,\n        .table>thead>tr>th {\n            padding: 8px;\n            line-height: 1.42857143;\n            vertical-align: top;\n            border-top: 1px solid #ddd\n        }\n\n        .table>thead>tr>th {\n            border-top: 0;\n            vertical-align: bottom;\n            border-bottom: 2px solid #ddd;\n            text-align: left\n        }\n\n        code {\n            padding: 2px 4px;\n            font-size: 90%;\n            color: #c7254e;\n            background-color: #f9f2f4;\n            border-radius: 4px;\n            font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace\n        }\n\n        .row {\n            margin-right: -15px;\n            margin-left: -15px\n        }\n\n        .col-md-10 {\n            float: left;\n            position: relative;\n            min-height: 1px;\n            padding-right: 15px;\n            padding-left: 15px\n        }\n\n        .col-md-10 {\n            width: 83.33333333%\n        }\n\n        img {\n            vertical-align: middle;\n            border: 0\n        }\n\n        .container {\n            min-width: 320px\n        }\n\n        body {\n            font-family: helvetica\n        }\n\n        table {\n            font-size: 14px\n        }\n\n        table.table tbody tr td {\n            border-top: 0;\n            border-bottom: 1px dotted #ddd;\n            padding: 2px;\n        }\n\n        pre {\n            white-space: pre-wrap;\n            margin-bottom: 10px;\n            max-height: 275px;\n            overflow: scroll\n        }\n\n        header {\n            background-color: #ed605e;\n            padding: 10px 20px;\n            box-sizing: border-box\n        }\n\n        .logo img {\n            width: 80px\n        }\n\n        .titles h1 {\n            font-size: 30px;\n            font-weight: 300;\n            color: #fff;\n            margin: 24px 0\n        }\n\n        .content h3 {\n            color: gray;\n            margin: 25px 0\n        }\n\n        .foot {\n            padding: 5px 0 20px;\n            text-align: right;\n            color: #c5c5c5;\n            font-weight: 300\n        }\n\n        .foot a {\n            color: #8b8b8b;\n            text-decoration: underline\n        }\n\n        .centered {\n            text-align: center\n        }\n\n        @media all and (max-width:500px) {\n            .titles h1 {\n                font-size: 25px;\n                margin: 26px 0\n            }\n        }\n\n        @media all and (max-width:530px) {\n            .titles h1 {\n                font-size: 20px;\n                margin: 24px 0\n            }\n\n            .logo {\n                padding: 0\n            }\n\n            .logo img {\n                width: 100%;\n                max-width: 80px\n            }\n        }\n    </style>\n</head>\n\n<body>\n    <header>\n        <div class=\"container\">\n            <div class=\"row\">\n                <div class=\"col-md-1 col-sm-2 col-xs-3 logo\">\n                    <a href=\"/\"><img src=\"https://gobuffalo.io/assets/images/logo_med.png\" alt=\"\"></a>\n                </div>\n                <div class=\"col-md-10 col-sm-6 col-xs-7 titles\">\n                    <h1>\n                        <%= status %> - ERROR!\n                    </h1>\n                </div>\n            </div>\n        </div>\n    </header>\n\n    <div class=\"container content\">\n        <div class=\"row\">\n            <div class=\"col-md-12\">\n                <h3>Error Trace</h3>\n                <pre><%= error %></pre>\n\n                <h3>Context</h3>\n                <pre><%= inspect(context) %></pre>\n\n                <h3>Parameters</h3>\n                <pre><%= inspect(params) %></pre>\n\n                <h3>Headers</h3>\n                <pre><%= inspect(headers) %></pre>\n\n                <h3>Form</h3>\n                <pre><%= inspect(posted_form) %></pre>\n\n                <h3>Routes</h3>\n                <table class=\"table table-striped\">\n                    <thead>\n                        <tr text-align=\"left\">\n                            <th class=\"centered\">METHOD</th>\n                            <th>PATH</th>\n                            <th>NAME</th>\n                            <th>HANDLER</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n\n                        <%= for (r) in routes { %>\n                            <tr>\n                                <td class=\"centered\">\n                                    <%= r.Method %>\n                                </td>\n                                <td>\n                                    <%= if (r.Method !=\"GET\" || r.Path ~=\"{\" ) { %>\n                                        <%= r.Path %>\n                                            <% } else { %>\n                                                <a href=\"<%= r.Path %>\">\n                                                    <%= r.Path %>\n                                                </a>\n                                                <% } %>\n                                </td>\n                                <td>\n                                    <%= r.PathName %>\n                                </td>\n                                <td><code><%= r.HandlerName %></code></td>\n                            </tr>\n                            <% } %>\n\n                    </tbody>\n                </table>\n            </div>\n        </div>\n        <div class=\"foot\"> <span> Powered by <a href=\"http://gobuffalo.io/\">gobuffalo.io</a></span></div>\n    </div>\n</body>\n\n</html>"
  },
  {
    "path": "internal/templates/error.prod.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <style>\n        h1,\n        p.powered {\n            text-align: center\n        }\n\n        body {\n            background: #ECECEC;\n            padding-top: 25px;\n            font-family: helvetica neue, helvetica, sans-serif;\n            color: #333\n        }\n\n        .card {\n            box-sizing: border-box;\n            width: 440px;\n            min-width: 270px;\n            margin: 0 auto;\n            padding: 10px 25px 35px 10px;\n            background: #FFF;\n            box-shadow: 0 2px 4px 0 rgba(185, 185, 185, .28);\n            border-radius: 5px\n        }\n\n        .card p {\n            max-width: 320px;\n            margin: 15px auto\n        }\n\n        h1 {\n            font-size: 22px\n        }\n\n        hr {\n            border: .5px solid #D72727;\n            width: 180px\n        }\n\n        p.powered {\n            font-family: HelveticaNeue-Light;\n            font-size: 12px;\n            color: #333\n        }\n\n        @media (max-width:600px) {\n            .card {\n                width: 100%;\n                display: block\n            }\n        }\n    </style>\n</head>\n\n<body>\n    <div class=\"container\">\n        <div class=\"card\">\n            <h1>We're Sorry!</h1>\n            <hr>\n            <p>It looks like something went wrong! Don't worry, we are aware of the problem and are looking into it.</p>\n            <p>Sorry if this has caused you any problems. Please check back again later.</p>\n        </div>\n\n        <p class=\"powered\">powered by <a href=\"https://gobuffalo.io\">gobuffalo.io</a></p>\n    </div>\n</body>\n\n</html>"
  },
  {
    "path": "internal/templates/notfound.prod.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <style>\n        h1,\n        p.powered {\n            text-align: center\n        }\n\n        body {\n            background: #ECECEC;\n            padding-top: 25px;\n            font-family: helvetica neue, helvetica, sans-serif;\n            color: #333\n        }\n\n        .card {\n            box-sizing: border-box;\n            width: 440px;\n            min-width: 270px;\n            margin: 0 auto;\n            padding: 10px 25px 35px 10px;\n            background: #FFF;\n            box-shadow: 0 2px 4px 0 rgba(185, 185, 185, .28);\n            border-radius: 5px\n        }\n\n        .card p {\n            max-width: 320px;\n            margin: 15px auto\n        }\n\n        h1 {\n            font-size: 22px\n        }\n\n        hr {\n            border: .5px solid #1272E2;\n            width: 180px\n        }\n\n        p.powered {\n            font-family: HelveticaNeue-Light;\n            font-size: 12px;\n            color: #333\n        }\n\n        @media (max-width:600px) {\n            .card {\n                width: 100%;\n                display: block\n            }\n        }\n    </style>\n</head>\n\n<body>\n    <div class=\"container\">\n        <div class=\"card\">\n            <h1>Not Found</h1>\n            <hr>\n            <p>The page you're looking for does not exist, you may have mistyped the address or the page may have been\n                moved.</p>\n        </div>\n\n        <p class=\"powered\">powered by <a href=\"https://gobuffalo.io\">gobuffalo.io</a></p>\n    </div>\n</body>\n\n</html>"
  },
  {
    "path": "internal/testdata/disk/file.txt",
    "content": "This file is on disk."
  },
  {
    "path": "internal/testdata/disk/file2.txt",
    "content": ""
  },
  {
    "path": "internal/testdata/disk/under/sub/subfile",
    "content": "This file is on disk/sub."
  },
  {
    "path": "internal/testdata/embedded/embed.go",
    "content": "package embedded\n\nimport (\n\t\"embed\"\n)\n\n//go:embed *\nvar files embed.FS\n\nfunc FS() embed.FS {\n\treturn files\n}\n"
  },
  {
    "path": "internal/testdata/embedded/file.txt",
    "content": "This file is embedded."
  },
  {
    "path": "internal/testdata/embedded/under/sub/subfile",
    "content": "This file is on embedded/sub."
  },
  {
    "path": "internal/testdata/panic.txt",
    "content": "This file must not be accessible from buffalo.FS."
  },
  {
    "path": "logger.go",
    "content": "package buffalo\n\nimport (\n\t\"github.com/gobuffalo/logger\"\n)\n\n// Logger interface is used throughout Buffalo\n// apps to log a whole manner of things.\ntype Logger = logger.FieldLogger\n"
  },
  {
    "path": "mail/README.md",
    "content": "# github.com/gobuffalo/buffalo/mail\n\nThis package is intended to allow easy Email sending with Buffalo, it allows you to define your custom `mail.Sender` for the provider you would like to use.\n\n## Generator\n\n```bash\nbuffalo generate mailer welcome_email\n```\n\n## Example Usage\n\n```go\n//actions/mail.go\npackage x\n\nimport (\n    \"log\"\n    \"net/http\"\n\n    \"github.com/gobuffalo/buffalo/render\"\n    \"github.com/gobuffalo/buffalo/internal/env\"\n    \"github.com/gobuffalo/plush\"\n    \"github.com/gobuffalo/buffalo/mail\"\n    \"errors\"\n    \"gitlab.com/wawandco/app/models\"\n)\n\nvar smtp mail.Sender\nvar r *render.Engine\n\nfunc init() {\n\n    //Pulling config from the env.\n    port := env.Get(\"SMTP_PORT\", \"1025\")\n    host := env.Get(\"SMTP_HOST\", \"localhost\")\n    user := env.Get(\"SMTP_USER\", \"\")\n    password := env.Get(\"SMTP_PASSWORD\", \"\")\n\n    var err error\n    smtp, err = mail.NewSMTPSender(host, port, user, password)\n\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    //The rendering engine, this is usually generated inside actions/render.go in your buffalo app.\n    r = render.New(render.Options{\n        TemplatesFS:   mailTemplates,\n    })\n}\n\n//SendContactMessage Sends contact message to contact@myapp.com\nfunc SendContactMessage(c *models.Contact) error {\n\n    //Creates a new message\n    m := mail.NewMessage()\n    m.From = \"sender@myapp.com\"\n    m.Subject = \"New Contact\"\n    m.To = []string{\"contact@myapp.com\"}\n\n    // Data that will be used inside the templates when rendering.\n    data := map[string]interface{}{\n        \"contact\": c,\n    }\n\n    // You can add multiple bodies to the message you're creating to have content-types alternatives.\n    err := m.AddBodies(data, r.HTML(\"mail/contact.html\"), r.Plain(\"mail/contact.txt\"))\n\n    if err != nil {\n        return err\n    }\n\n    err = smtp.Send(m)\n    if err != nil {\n        return err\n    }\n\n    return nil\n}\n\n```\n\nThis `SendContactMessage` could be called by one of your actions, p.e. the action that handles your contact form submission.\n\n```go\n//actions/contact.go\n...\n\nfunc ContactFormHandler(c buffalo.Context) error {\n    contact := &models.Contact{}\n    c.Bind(contact)\n\n    //Calling to send the message\n    SendContactMessage(contact)\n    return c.Redirect(http.StatusFound, \"contact/thanks\")\n}\n...\n```\n\nIf you're using Gmail or need to configure your SMTP connection you can use the Dialer property on the SMTPSender, p.e: (for Gmail)\n\n```go\n...\nvar smtp mail.Sender\n\nfunc init() {\n    port := env.Get(\"SMTP_PORT\", \"465\")\n    // or 587 with TLS\n\n    host := env.Get(\"SMTP_HOST\", \"smtp.gmail.com\")\n    user := env.Get(\"SMTP_USER\", \"your@email.com\")\n    password := env.Get(\"SMTP_PASSWORD\", \"yourp4ssw0rd\")\n\n    var err error\n    sender, err := mail.NewSMTPSender(host, port, user, password)\n    sender.Dialer.SSL = true\n\n    //or if TLS\n    sender.Dialer.TLSConfig = &tls.Config{...}\n\n    smtp = sender\n}\n...\n```\n"
  },
  {
    "path": "mail/attachment.go",
    "content": "package mail\n\nimport \"io\"\n\n// Attachment are files added into a email message\ntype Attachment struct {\n\tName        string\n\tReader      io.Reader\n\tContentType string\n\tEmbedded    bool\n}\n"
  },
  {
    "path": "mail/body.go",
    "content": "package mail\n\n// Body represents one of the bodies in the Message could be main or alternative\ntype Body struct {\n\tContent     string\n\tContentType string\n}\n"
  },
  {
    "path": "mail/dialer.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/smtp\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Dialer connects to an SMTP server and sends emails.\ntype Dialer struct {\n\tHost     string\n\tPort     int\n\tUsername string\n\tPassword string\n\tAuth     smtp.Auth\n\t// SSL defines whether an SSL connection is used. It should be false in\n\t// most cases since the authentication mechanism should use the STARTTLS\n\t// extension instead.\n\tSSL bool\n\t// TLSConfig represents the TLS configuration used for the TLS (when the\n\t// STARTTLS extension is used) or SSL connection.\n\tTLSConfig *tls.Config\n\t// StartTLSPolicy represents the TLS security level required to communicate\n\t// with the SMTP server. Defaults to opportunistic STARTTLS.\n\tStartTLSPolicy StartTLSPolicy\n\t// LocalName is the hostname sent to the SMTP server with the HELO command.\n\t// By default, \"localhost\" is sent.\n\tLocalName string\n\t// Timeout to use for read/write operations. Defaults to 10 seconds, can\n\t// be set to 0 to disable timeouts.\n\tTimeout time.Duration\n\t// Whether we should retry mailing if the connection returned an error.\n\tRetryFailure bool\n}\n\nfunc newDialer(host string, port int, username, password string) *Dialer {\n\treturn &Dialer{\n\t\tHost:         host,\n\t\tPort:         port,\n\t\tUsername:     username,\n\t\tPassword:     password,\n\t\tSSL:          port == 465,\n\t\tTimeout:      10 * time.Second,\n\t\tRetryFailure: true,\n\t}\n}\n\n// NetDialTimeout specifies the DialTimeout function to establish a connection\n// to the SMTP server. This can be used to override dialing in the case that a\n// proxy or other special behavior is needed.\nvar NetDialTimeout = net.DialTimeout\n\nfunc (d *Dialer) dial() (sendCloser, error) {\n\tconn, err := NetDialTimeout(\"tcp\", addr(d.Host, d.Port), d.Timeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif d.SSL {\n\t\tconn = tlsClient(conn, d.tlsConfig())\n\t}\n\n\tc, err := smtpNewClient(conn, d.Host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif d.Timeout > 0 {\n\t\tconn.SetDeadline(time.Now().Add(d.Timeout))\n\t}\n\n\tif d.LocalName != \"\" {\n\t\tif err := c.Hello(d.LocalName); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif !d.SSL && d.StartTLSPolicy != noStartTLS {\n\t\tok, _ := c.Extension(\"STARTTLS\")\n\t\tif !ok && d.StartTLSPolicy == mandatoryStartTLS {\n\t\t\terr := startTLSUnsupportedError{Policy: d.StartTLSPolicy}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif ok {\n\t\t\tif err := c.StartTLS(d.tlsConfig()); err != nil {\n\t\t\t\tc.Close()\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tif d.Auth == nil && d.Username != \"\" {\n\t\tif ok, auths := c.Extension(\"AUTH\"); ok {\n\t\t\tif strings.Contains(auths, \"CRAM-MD5\") {\n\t\t\t\td.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)\n\t\t\t} else if strings.Contains(auths, \"LOGIN\") &&\n\t\t\t\t!strings.Contains(auths, \"PLAIN\") {\n\t\t\t\td.Auth = &loginAuth{\n\t\t\t\t\tusername: d.Username,\n\t\t\t\t\tpassword: d.Password,\n\t\t\t\t\thost:     d.Host,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\td.Auth = smtp.PlainAuth(\"\", d.Username, d.Password, d.Host)\n\t\t\t}\n\t\t}\n\t}\n\n\tif d.Auth != nil {\n\t\tif err = c.Auth(d.Auth); err != nil {\n\t\t\tc.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn &smtpSender{c, conn, d}, nil\n}\n\nfunc (d *Dialer) tlsConfig() *tls.Config {\n\tif d.TLSConfig == nil {\n\t\treturn &tls.Config{ServerName: d.Host}\n\t}\n\treturn d.TLSConfig\n}\n\n// StartTLSPolicy represents the TLS security level required to communicate\n// with an SMTP server.\ntype StartTLSPolicy int\n\nconst (\n\topportunisticStartTLS StartTLSPolicy = iota\n\tmandatoryStartTLS\n\tnoStartTLS = -1\n)\n\nfunc (policy *StartTLSPolicy) String() string {\n\tswitch *policy {\n\tcase opportunisticStartTLS:\n\t\treturn \"OpportunisticStartTLS\"\n\tcase mandatoryStartTLS:\n\t\treturn \"MandatoryStartTLS\"\n\tcase noStartTLS:\n\t\treturn \"NoStartTLS\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"StartTLSPolicy:%v\", *policy)\n\t}\n}\n\ntype startTLSUnsupportedError struct {\n\tPolicy StartTLSPolicy\n}\n\nfunc (e startTLSUnsupportedError) Error() string {\n\treturn \"gomail: \" + e.Policy.String() + \" required, but SMTP server does not support STARTTLS\"\n}\n\nfunc addr(host string, port int) string {\n\treturn fmt.Sprintf(\"%s:%d\", host, port)\n}\n\nfunc (d *Dialer) dialAndSend(m ...*smtpMessage) error {\n\ts, err := d.dial()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer s.Close()\n\n\tfor _, err := range sendSMTP(s, m...) {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype smtpSender struct {\n\tsmtpClient\n\tconn net.Conn\n\td    *Dialer\n}\n\nfunc (c *smtpSender) retryError(err error) bool {\n\tif !c.d.RetryFailure {\n\t\treturn false\n\t}\n\n\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\treturn true\n\t}\n\n\treturn err == io.EOF\n}\n\nfunc (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {\n\tif c.d.Timeout > 0 {\n\t\tc.conn.SetDeadline(time.Now().Add(c.d.Timeout))\n\t}\n\n\tif err := c.Mail(from); err != nil {\n\t\tif c.retryError(err) {\n\t\t\tsc, derr := c.d.dial()\n\t\t\tif derr == nil {\n\t\t\t\tif s, ok := sc.(*smtpSender); ok {\n\t\t\t\t\t*c = *s\n\t\t\t\t\treturn c.Send(from, to, msg)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n\n\tfor _, addr := range to {\n\t\tif err := c.Rcpt(addr); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tw, err := c.Data()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err = msg.WriteTo(w); err != nil {\n\t\tw.Close()\n\t\treturn err\n\t}\n\n\treturn w.Close()\n}\n\nfunc (c *smtpSender) Close() error {\n\treturn c.Quit()\n}\n\n// Stubbed out for tests.\nvar (\n\ttlsClient     = tls.Client\n\tsmtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {\n\t\treturn smtp.NewClient(conn, host)\n\t}\n)\n\ntype smtpClient interface {\n\tHello(string) error\n\tExtension(string) (bool, string)\n\tStartTLS(*tls.Config) error\n\tAuth(smtp.Auth) error\n\tMail(string) error\n\tRcpt(string) error\n\tData() (io.WriteCloser, error)\n\tQuit() error\n\tClose() error\n}\n\ntype sendCloser interface {\n\tsender\n\tClose() error\n}\n\ntype sender interface {\n\tSend(from string, to []string, msg io.WriterTo) error\n}\n"
  },
  {
    "path": "mail/mail.go",
    "content": "// Package mail provides email sending functionality for Buffalo applications.\n// It supports SMTP delivery with customizable configuration including TLS/SSL,\n// authentication, and batch sending capabilities.\n//\n// Portions of the SMTP implementation are derived from the go-mail/mail project\n// (https://github.com/go-mail/mail) under the MIT License.\n//\n// TODO: Properly encode filenames for non-ASCII characters.\n// TODO: Properly encode email addresses for non-ASCII characters.\n// TODO: Test embedded files and attachments for their existence before sending.\n// TODO: Allow supplying an io.Reader when embedding and attaching files.\npackage mail\n\nimport (\n\t\"context\"\n\t\"maps\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo\"\n\t\"github.com/gobuffalo/buffalo/render\"\n)\n\n// NewMessage builds a new message.\nfunc NewMessage() Message {\n\treturn Message{\n\t\tContext: context.Background(),\n\t\tHeaders: map[string]string{},\n\t\tData:    render.Data{},\n\t\tmoot:    &sync.RWMutex{},\n\t}\n}\n\n// NewFromData builds a new message with raw template data given\nfunc NewFromData(data render.Data) Message {\n\tm := NewMessage()\n\tm.Data = maps.Clone(data)\n\treturn m\n}\n\n// New builds a new message with the current buffalo.Context\nfunc New(c buffalo.Context) Message {\n\tm := NewFromData(c.Data())\n\tm.Context = c\n\treturn m\n}\n"
  },
  {
    "path": "mail/mail_test.go",
    "content": "package mail\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_NewFromData(t *testing.T) {\n\tr := require.New(t)\n\tm := NewFromData(map[string]any{\n\t\t\"foo\": \"bar\",\n\t})\n\tr.Equal(\"bar\", m.Data[\"foo\"])\n}\n\nfunc Test_New(t *testing.T) {\n\tr := require.New(t)\n\n\tvar m Message\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/\", func(c buffalo.Context) error {\n\t\tc.Set(\"foo\", \"bar\")\n\t\tm = New(c)\n\t\treturn c.Render(http.StatusOK, render.String(\"\"))\n\t})\n\tw := httptest.New(app)\n\tw.HTML(\"/\").Get()\n\n\tr.NotNil(m)\n\tr.Equal(\"bar\", m.Data[\"foo\"])\n\trp, ok := m.Data[\"rootPath\"].(buffalo.RouteHelperFunc)\n\tr.True(ok)\n\tx, err := rp(map[string]any{})\n\tr.NoError(err)\n\tr.Equal(template.HTML(\"/\"), x)\n}\n"
  },
  {
    "path": "mail/message.go",
    "content": "package mail\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"maps\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n)\n\n// Message represents an Email message\ntype Message struct {\n\tContext     context.Context\n\tFrom        string\n\tTo          []string\n\tCC          []string\n\tBcc         []string\n\tSubject     string\n\tHeaders     map[string]string\n\tData        render.Data\n\tBodies      []Body\n\tAttachments []Attachment\n\tmoot        *sync.RWMutex\n}\n\nfunc (m *Message) merge(data render.Data) render.Data {\n\tm.moot.Lock()\n\td := maps.Clone(m.Data)\n\tm.moot.Unlock()\n\tmaps.Copy(d, data)\n\treturn d\n}\n\n// AddBody the message by receiving a renderer and rendering data, first message will be\n// used as the main message Body rest of them will be passed as alternative bodies on the\n// email message\nfunc (m *Message) AddBody(r render.Renderer, data render.Data) error {\n\tbuf := bytes.NewBuffer([]byte{})\n\terr := r.Render(buf, m.merge(data))\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm.Bodies = append(m.Bodies, Body{\n\t\tContent:     buf.String(),\n\t\tContentType: r.ContentType(),\n\t})\n\n\treturn nil\n}\n\n// AddBodies Allows to add multiple bodies to the message, it returns errors that\n// could happen in the rendering.\nfunc (m *Message) AddBodies(data render.Data, renderers ...render.Renderer) error {\n\tfor _, r := range renderers {\n\t\terr := m.AddBody(r, data)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// AddAttachment adds the attachment to the list of attachments the Message has.\nfunc (m *Message) AddAttachment(name, contentType string, r io.Reader) error {\n\tm.Attachments = append(m.Attachments, Attachment{\n\t\tName:        name,\n\t\tContentType: contentType,\n\t\tReader:      r,\n\t\tEmbedded:    false,\n\t})\n\n\treturn nil\n}\n\n// AddEmbedded adds the attachment to the list of attachments\n// the Message has and uses inline instead of attachement property.\nfunc (m *Message) AddEmbedded(name string, r io.Reader) error {\n\tm.Attachments = append(m.Attachments, Attachment{\n\t\tName:     name,\n\t\tReader:   r,\n\t\tEmbedded: true,\n\t})\n\n\treturn nil\n}\n\n// SetHeader sets the heder field and value for the message\nfunc (m *Message) SetHeader(field, value string) {\n\tm.Headers[field] = value\n}\n"
  },
  {
    "path": "mail/sender.go",
    "content": "package mail\n\n// Sender defines the interface for sending individual email messages.\ntype Sender interface {\n\t// Send delivers a single email message.\n\tSend(Message) error\n}\n\n// BatchSender defines the interface for sending multiple email messages.\ntype BatchSender interface {\n\tSender\n\t// SendBatch delivers multiple messages. It returns per-message errors\n\t// and any general error that prevented sending entirely.\n\tSendBatch(messages ...Message) (errorsByMessages []error, generalError error)\n}\n"
  },
  {
    "path": "mail/smtp_auth.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/smtp\"\n\t\"slices\"\n)\n\n// loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.\ntype loginAuth struct {\n\tusername string\n\tpassword string\n\thost     string\n}\n\nfunc (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {\n\tif !server.TLS {\n\t\tif !slices.Contains(server.Auth, \"LOGIN\") {\n\t\t\treturn \"\", nil, fmt.Errorf(\"gomail: unencrypted connection\")\n\t\t}\n\t}\n\tif server.Name != a.host {\n\t\treturn \"\", nil, fmt.Errorf(\"gomail: wrong host name\")\n\t}\n\treturn \"LOGIN\", nil, nil\n}\n\nfunc (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {\n\tif !more {\n\t\treturn nil, nil\n\t}\n\n\tswitch {\n\tcase bytes.Equal(fromServer, []byte(\"Username:\")):\n\t\treturn []byte(a.username), nil\n\tcase bytes.Equal(fromServer, []byte(\"Password:\")):\n\t\treturn []byte(a.password), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"gomail: unexpected server challenge: %s\", fromServer)\n\t}\n}\n"
  },
  {
    "path": "mail/smtp_errors.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport \"fmt\"\n\n// sendError represents the failure to transmit a Message, detailing the cause\n// of the failure and index of the Message within a batch.\ntype sendError struct {\n\t// Index specifies the index of the Message within a batch.\n\tIndex uint\n\tCause error\n}\n\nfunc (err *sendError) Error() string {\n\treturn fmt.Sprintf(\"gomail: could not send email %d: %v\",\n\t\terr.Index+1, err.Cause)\n}\n"
  },
  {
    "path": "mail/smtp_message.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\n// smtpMessage represents an email for SMTP transmission.\ntype smtpMessage struct {\n\theader      smtpHeader\n\tparts       []*part\n\tattachments []*file\n\tembedded    []*file\n\tcharset     string\n\tencoding    encoding\n\thEncoder    mimeEncoder\n\tbuf         bytes.Buffer\n\tboundary    string\n}\n\ntype smtpHeader map[string][]string\n\ntype part struct {\n\tcontentType string\n\tcopier      func(io.Writer) error\n\tencoding    encoding\n}\n\nfunc newSMTPMessage(settings ...messageSetting) *smtpMessage {\n\tm := &smtpMessage{\n\t\theader:   make(smtpHeader),\n\t\tcharset:  \"UTF-8\",\n\t\tencoding: encodingQuotedPrintable,\n\t}\n\n\tm.applySettings(settings)\n\n\tif m.encoding == encodingBase64 {\n\t\tm.hEncoder = bEncoding\n\t} else {\n\t\tm.hEncoder = qEncoding\n\t}\n\n\treturn m\n}\n\nfunc (m *smtpMessage) applySettings(settings []messageSetting) {\n\tfor _, s := range settings {\n\t\ts(m)\n\t}\n}\n\ntype messageSetting func(m *smtpMessage)\n\ntype encoding string\n\nconst (\n\tencodingQuotedPrintable encoding = \"quoted-printable\"\n\tencodingBase64          encoding = \"base64\"\n\tencodingUnencoded       encoding = \"8bit\"\n)\n\nfunc (m *smtpMessage) setHeader(field string, value ...string) {\n\tm.encodeHeader(value)\n\tm.header[field] = value\n}\n\nfunc (m *smtpMessage) encodeHeader(values []string) {\n\tfor i := range values {\n\t\tvalues[i] = m.encodeString(values[i])\n\t}\n}\n\nfunc (m *smtpMessage) encodeString(value string) string {\n\treturn m.hEncoder.Encode(m.charset, value)\n}\n\nfunc (m *smtpMessage) setBody(contentType, body string, settings ...partSetting) {\n\tm.setBodyWriter(contentType, newCopier(body), settings...)\n}\n\nfunc (m *smtpMessage) setBodyWriter(contentType string, f func(io.Writer) error, settings ...partSetting) {\n\tm.parts = []*part{m.newPart(contentType, f, settings)}\n}\n\nfunc (m *smtpMessage) addAlternative(contentType, body string, settings ...partSetting) {\n\tm.addAlternativeWriter(contentType, newCopier(body), settings...)\n}\n\nfunc newCopier(s string) func(io.Writer) error {\n\treturn func(w io.Writer) error {\n\t\t_, err := io.WriteString(w, s)\n\t\treturn err\n\t}\n}\n\nfunc (m *smtpMessage) addAlternativeWriter(contentType string, f func(io.Writer) error, settings ...partSetting) {\n\tm.parts = append(m.parts, m.newPart(contentType, f, settings))\n}\n\nfunc (m *smtpMessage) newPart(contentType string, f func(io.Writer) error, settings []partSetting) *part {\n\tp := &part{\n\t\tcontentType: contentType,\n\t\tcopier:      f,\n\t\tencoding:    m.encoding,\n\t}\n\n\tfor _, s := range settings {\n\t\ts(p)\n\t}\n\n\treturn p\n}\n\ntype partSetting func(*part)\n\nfunc setPartEncoding(e encoding) partSetting {\n\treturn partSetting(func(p *part) {\n\t\tp.encoding = e\n\t})\n}\n\ntype file struct {\n\tName     string\n\tHeader   map[string][]string\n\tCopyFunc func(w io.Writer) error\n}\n\nfunc (f *file) setHeader(field, value string) {\n\tf.Header[field] = []string{value}\n}\n\ntype fileSetting func(*file)\n\nfunc setCopyFunc(f func(io.Writer) error) fileSetting {\n\treturn func(fi *file) {\n\t\tfi.CopyFunc = f\n\t}\n}\n\nfunc (m *smtpMessage) attach(filename string, settings ...fileSetting) {\n\tm.attachments = m.appendFile(m.attachments, fileFromFilename(filename), settings)\n}\n\nfunc (m *smtpMessage) embed(filename string, settings ...fileSetting) {\n\tm.embedded = m.appendFile(m.embedded, fileFromFilename(filename), settings)\n}\n\nfunc fileFromFilename(name string) *file {\n\treturn &file{\n\t\tName:   filepath.Base(name),\n\t\tHeader: make(map[string][]string),\n\t\tCopyFunc: func(w io.Writer) error {\n\t\t\th, err := os.Open(name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := io.Copy(w, h); err != nil {\n\t\t\t\th.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn h.Close()\n\t\t},\n\t}\n}\n\nfunc (m *smtpMessage) formatDate(date time.Time) string {\n\treturn date.Format(time.RFC1123Z)\n}\n\nfunc (m *smtpMessage) appendFile(list []*file, f *file, settings []fileSetting) []*file {\n\tfor _, s := range settings {\n\t\ts(f)\n\t}\n\n\tif list == nil {\n\t\treturn []*file{f}\n\t}\n\n\treturn append(list, f)\n}\n"
  },
  {
    "path": "mail/smtp_mime.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"mime\"\n\t\"mime/quotedprintable\"\n\t\"strings\"\n)\n\nvar newQPWriter = quotedprintable.NewWriter\n\ntype mimeEncoder struct {\n\tmime.WordEncoder\n}\n\nvar (\n\tbEncoding     = mimeEncoder{mime.BEncoding}\n\tqEncoding     = mimeEncoder{mime.QEncoding}\n\tlastIndexByte = strings.LastIndexByte\n)\n"
  },
  {
    "path": "mail/smtp_send.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"fmt\"\n\tstdmail \"net/mail\"\n\t\"slices\"\n)\n\n// sendSMTP sends emails using the given Sender.\nfunc sendSMTP(s sender, msg ...*smtpMessage) []error {\n\terrors := make([]error, len(msg))\n\tfor i, m := range msg {\n\t\tif err := sendSingle(s, m); err != nil {\n\t\t\terrors[i] = &sendError{Cause: err, Index: uint(i)}\n\t\t}\n\t}\n\n\treturn errors\n}\n\nfunc sendSingle(s sender, m *smtpMessage) error {\n\tfrom, err := m.getFrom()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tto, err := m.getRecipients()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := s.Send(from, to, m); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (m *smtpMessage) getFrom() (string, error) {\n\tfrom := m.header[\"Sender\"]\n\tif len(from) == 0 {\n\t\tfrom = m.header[\"From\"]\n\t\tif len(from) == 0 {\n\t\t\treturn \"\", fmt.Errorf(`gomail: invalid message, \"From\" field is absent`)\n\t\t}\n\t}\n\n\treturn parseAddress(from[0])\n}\n\nfunc (m *smtpMessage) getRecipients() ([]string, error) {\n\tn := 0\n\tfor _, field := range []string{\"To\", \"Cc\", \"Bcc\"} {\n\t\tif addresses, ok := m.header[field]; ok {\n\t\t\tn += len(addresses)\n\t\t}\n\t}\n\tlist := make([]string, 0, n)\n\n\tfor _, field := range []string{\"To\", \"Cc\", \"Bcc\"} {\n\t\tif addresses, ok := m.header[field]; ok {\n\t\t\tfor _, a := range addresses {\n\t\t\t\taddr, err := parseAddress(a)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tlist = addAddress(list, addr)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list, nil\n}\n\nfunc addAddress(list []string, addr string) []string {\n\tif slices.Contains(list, addr) {\n\t\treturn list\n\t}\n\treturn append(list, addr)\n}\n\nfunc parseAddress(field string) (string, error) {\n\taddr, err := stdmail.ParseAddress(field)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"gomail: invalid address %q: %v\", field, err)\n\t}\n\treturn addr.Address, nil\n}\n"
  },
  {
    "path": "mail/smtp_sender.go",
    "content": "package mail\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n)\n\n// SMTPSender delivers emails via an SMTP server.\ntype SMTPSender struct {\n\t// Dialer configures the connection to the SMTP server.\n\tDialer *Dialer\n}\n\n// Send delivers a single message via SMTP.\nfunc (sm SMTPSender) Send(message Message) error {\n\treturn sm.Dialer.dialAndSend(sm.prepareMessage(message))\n}\n\n// SendBatch delivers multiple messages using a single SMTP connection.\n// Returns per-message errors and any general connection error.\nfunc (sm SMTPSender) SendBatch(messages ...Message) (errorsByMessages []error, generalError error) {\n\tpreparedMessages := make([]*smtpMessage, len(messages))\n\tfor i, message := range messages {\n\t\tpreparedMessages[i] = sm.prepareMessage(message)\n\t}\n\n\ts, err := sm.Dialer.dial()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer s.Close()\n\n\treturn sendSMTP(s, preparedMessages...), nil\n}\nfunc (sm SMTPSender) prepareMessage(message Message) *smtpMessage {\n\tgm := newSMTPMessage()\n\n\tgm.setHeader(\"From\", message.From)\n\tgm.setHeader(\"To\", message.To...)\n\tgm.setHeader(\"Subject\", message.Subject)\n\tgm.setHeader(\"Cc\", message.CC...)\n\tgm.setHeader(\"Bcc\", message.Bcc...)\n\n\tsm.addBodies(message, gm)\n\tsm.addAttachments(message, gm)\n\n\tfor field, value := range message.Headers {\n\t\tgm.setHeader(field, value)\n\t}\n\n\treturn gm\n}\n\nfunc (sm SMTPSender) addBodies(message Message, gm *smtpMessage) {\n\tif len(message.Bodies) == 0 {\n\t\treturn\n\t}\n\n\tmainBody := message.Bodies[0]\n\tgm.setBody(mainBody.ContentType, mainBody.Content, setPartEncoding(encodingUnencoded))\n\n\tfor i := 1; i < len(message.Bodies); i++ {\n\t\talt := message.Bodies[i]\n\t\tgm.addAlternative(alt.ContentType, alt.Content, setPartEncoding(encodingUnencoded))\n\t}\n}\n\nfunc (sm SMTPSender) addAttachments(message Message, gm *smtpMessage) {\n\n\tfor _, at := range message.Attachments {\n\t\tcurrentAttachement := at\n\t\tsettings := setCopyFunc(func(w io.Writer) error {\n\t\t\t_, err := io.Copy(w, currentAttachement.Reader)\n\t\t\treturn err\n\t\t})\n\n\t\tif currentAttachement.Embedded {\n\t\t\tgm.embed(currentAttachement.Name, settings)\n\t\t} else {\n\t\t\tgm.attach(currentAttachement.Name, settings)\n\t\t}\n\n\t}\n}\n\n// NewSMTPSender builds a SMTP mail based in passed config.\nfunc NewSMTPSender(host string, port string, user string, password string) (SMTPSender, error) {\n\tiport, err := strconv.Atoi(port)\n\n\tif err != nil {\n\t\treturn SMTPSender{}, fmt.Errorf(\"invalid port for the SMTP mail\")\n\t}\n\n\tdialer := &Dialer{\n\t\tHost: host,\n\t\tPort: iport,\n\t}\n\n\tif user != \"\" {\n\t\tdialer.Username = user\n\t\tdialer.Password = password\n\t}\n\n\treturn SMTPSender{\n\t\tDialer: dialer,\n\t}, nil\n}\n"
  },
  {
    "path": "mail/smtp_sender_test.go",
    "content": "package mail_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/internal/fakesmtp\"\n\t\"github.com/gobuffalo/buffalo/mail\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar sender mail.Sender\nvar rend *render.Engine\nvar smtpServer *fakesmtp.Server\n\nconst smtpPort = \"2002\"\n\nfunc init() {\n\trend = render.New(render.Options{})\n\tsmtpServer, _ = fakesmtp.New(smtpPort)\n\tsender, _ = mail.NewSMTPSender(\"127.0.0.1\", smtpPort, \"username\", \"password\")\n\n\tgo smtpServer.Start(smtpPort)\n}\n\nfunc TestSendPlain(t *testing.T) {\n\tsmtpServer.Clear()\n\tr := require.New(t)\n\n\tm := mail.NewMessage()\n\tm.From = \"mark@example.com\"\n\tm.To = []string{\"something@something.com\"}\n\tm.Subject = \"Cool Message\"\n\tm.CC = []string{\"other@other.com\", \"my@other.com\"}\n\tm.Bcc = []string{\"secret@other.com\"}\n\n\tm.AddAttachment(\"someFile.txt\", \"text/plain\", bytes.NewBuffer([]byte(\"hello\")))\n\tm.AddAttachment(\"otherFile.txt\", \"text/plain\", bytes.NewBuffer([]byte(\"bye\")))\n\tm.AddEmbedded(\"test.jpg\", bytes.NewBuffer([]byte(\"not a real image\")))\n\tm.AddBody(rend.String(\"Hello <%= Name %>\"), render.Data{\"Name\": \"Antonio\"})\n\tr.Equal(m.Bodies[0].Content, \"Hello Antonio\")\n\n\tm.SetHeader(\"X-SMTPAPI\", `{\"send_at\": 1409348513}`)\n\n\terr := sender.Send(m)\n\tr.Nil(err)\n\n\tlastMessage := smtpServer.LastMessage()\n\n\tr.Contains(lastMessage, \"FROM:<mark@example.com>\")\n\tr.Contains(lastMessage, \"RCPT TO:<other@other.com>\")\n\tr.Contains(lastMessage, \"RCPT TO:<my@other.com>\")\n\tr.Contains(lastMessage, \"RCPT TO:<secret@other.com>\")\n\tr.Contains(lastMessage, \"Subject: Cool Message\")\n\tr.Contains(lastMessage, \"Cc: other@other.com, my@other.com\")\n\tr.Contains(lastMessage, \"Content-Type: text/plain\")\n\tr.Contains(lastMessage, \"Hello Antonio\")\n\tr.Contains(lastMessage, \"Content-Disposition: attachment; filename=\\\"someFile.txt\\\"\")\n\tr.Contains(lastMessage, \"aGVsbG8=\") //base64 of the file content\n\tr.Contains(lastMessage, \"Content-Disposition: attachment; filename=\\\"otherFile.txt\\\"\")\n\tr.Contains(lastMessage, \"Ynll\") //base64 of the file content\n\tr.Contains(lastMessage, \"Content-Disposition: inline; filename=\\\"test.jpg\\\"\")\n\tr.Contains(lastMessage, \"bm90IGEgcmVhbCBpbWFnZQ==\") //base64 of the file content\n\n\tr.Contains(lastMessage, `X-SMTPAPI: {\"send_at\": 1409348513}`)\n}\n"
  },
  {
    "path": "mail/smtp_writeto.go",
    "content": "// Portions of this code are derived from the go-mail/mail project.\n// https://github.com/go-mail/mail (MIT License)\n\npackage mail\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"mime/multipart\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc (m *smtpMessage) WriteTo(w io.Writer) (int64, error) {\n\tmw := &messageWriter{w: w}\n\tmw.writeMessage(m)\n\treturn mw.n, mw.err\n}\n\nfunc (w *messageWriter) writeMessage(m *smtpMessage) {\n\tif _, ok := m.header[\"MIME-Version\"]; !ok {\n\t\tw.writeString(\"MIME-Version: 1.0\\r\\n\")\n\t}\n\tif _, ok := m.header[\"Date\"]; !ok {\n\t\tw.writeHeader(\"Date\", m.formatDate(now()))\n\t}\n\tw.writeHeaders(m.header)\n\n\tif m.hasMixedPart() {\n\t\tw.openMultipart(\"mixed\", m.boundary)\n\t}\n\n\tif m.hasRelatedPart() {\n\t\tw.openMultipart(\"related\", m.boundary)\n\t}\n\n\tif m.hasAlternativePart() {\n\t\tw.openMultipart(\"alternative\", m.boundary)\n\t}\n\tfor _, part := range m.parts {\n\t\tw.writePart(part, m.charset)\n\t}\n\tif m.hasAlternativePart() {\n\t\tw.closeMultipart()\n\t}\n\n\tw.addFiles(m.embedded, false)\n\tif m.hasRelatedPart() {\n\t\tw.closeMultipart()\n\t}\n\n\tw.addFiles(m.attachments, true)\n\tif m.hasMixedPart() {\n\t\tw.closeMultipart()\n\t}\n}\n\nfunc (m *smtpMessage) hasMixedPart() bool {\n\treturn (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1\n}\n\nfunc (m *smtpMessage) hasRelatedPart() bool {\n\treturn (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1\n}\n\nfunc (m *smtpMessage) hasAlternativePart() bool {\n\treturn len(m.parts) > 1\n}\n\ntype messageWriter struct {\n\tw          io.Writer\n\tn          int64\n\twriters    [3]*multipart.Writer\n\tpartWriter io.Writer\n\tdepth      uint8\n\terr        error\n}\n\nfunc (w *messageWriter) openMultipart(mimeType, boundary string) {\n\tmw := multipart.NewWriter(w)\n\tif boundary != \"\" {\n\t\tmw.SetBoundary(boundary)\n\t}\n\tcontentType := \"multipart/\" + mimeType + \";\\r\\n boundary=\" + mw.Boundary()\n\tw.writers[w.depth] = mw\n\n\tif w.depth == 0 {\n\t\tw.writeHeader(\"Content-Type\", contentType)\n\t\tw.writeString(\"\\r\\n\")\n\t} else {\n\t\tw.createPart(map[string][]string{\n\t\t\t\"Content-Type\": {contentType},\n\t\t})\n\t}\n\tw.depth++\n}\n\nfunc (w *messageWriter) createPart(h map[string][]string) {\n\tw.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)\n}\n\nfunc (w *messageWriter) closeMultipart() {\n\tif w.depth > 0 {\n\t\tw.writers[w.depth-1].Close()\n\t\tw.depth--\n\t}\n}\n\nfunc (w *messageWriter) writePart(p *part, charset string) {\n\tw.writeHeaders(map[string][]string{\n\t\t\"Content-Type\":              {p.contentType + \"; charset=\" + charset},\n\t\t\"Content-Transfer-Encoding\": {string(p.encoding)},\n\t})\n\tw.writeBody(p.copier, p.encoding)\n}\n\nfunc (w *messageWriter) addFiles(files []*file, isAttachment bool) {\n\tfor _, f := range files {\n\t\tif _, ok := f.Header[\"Content-Type\"]; !ok {\n\t\t\tmediaType := mime.TypeByExtension(filepath.Ext(f.Name))\n\t\t\tif mediaType == \"\" {\n\t\t\t\tmediaType = \"application/octet-stream\"\n\t\t\t}\n\t\t\tf.setHeader(\"Content-Type\", mediaType+`; name=\"`+f.Name+`\"`)\n\t\t}\n\n\t\tif _, ok := f.Header[\"Content-Transfer-Encoding\"]; !ok {\n\t\t\tf.setHeader(\"Content-Transfer-Encoding\", string(encodingBase64))\n\t\t}\n\n\t\tif _, ok := f.Header[\"Content-Disposition\"]; !ok {\n\t\t\tvar disp string\n\t\t\tif isAttachment {\n\t\t\t\tdisp = \"attachment\"\n\t\t\t} else {\n\t\t\t\tdisp = \"inline\"\n\t\t\t}\n\t\t\tf.setHeader(\"Content-Disposition\", disp+`; filename=\"`+f.Name+`\"`)\n\t\t}\n\n\t\tif !isAttachment {\n\t\t\tif _, ok := f.Header[\"Content-ID\"]; !ok {\n\t\t\t\tf.setHeader(\"Content-ID\", \"<\"+f.Name+\">\")\n\t\t\t}\n\t\t}\n\t\tw.writeHeaders(f.Header)\n\t\tw.writeBody(f.CopyFunc, encodingBase64)\n\t}\n}\n\nfunc (w *messageWriter) Write(p []byte) (int, error) {\n\tif w.err != nil {\n\t\treturn 0, fmt.Errorf(\"gomail: cannot write as writer is in error\")\n\t}\n\n\tvar n int\n\tn, w.err = w.w.Write(p)\n\tw.n += int64(n)\n\treturn n, w.err\n}\n\nfunc (w *messageWriter) writeString(s string) {\n\tif w.err != nil {\n\t\treturn\n\t}\n\tvar n int\n\tn, w.err = io.WriteString(w.w, s)\n\tw.n += int64(n)\n}\n\nfunc (w *messageWriter) writeHeader(k string, v ...string) {\n\tw.writeString(k)\n\tif len(v) == 0 {\n\t\tw.writeString(\":\\r\\n\")\n\t\treturn\n\t}\n\tw.writeString(\": \")\n\n\tcharsLeft := 76 - len(k) - len(\": \")\n\n\tfor i, s := range v {\n\t\tif charsLeft < 1 {\n\t\t\tif i == 0 {\n\t\t\t\tw.writeString(\"\\r\\n \")\n\t\t\t} else {\n\t\t\t\tw.writeString(\",\\r\\n \")\n\t\t\t}\n\t\t\tcharsLeft = 75\n\t\t} else if i != 0 {\n\t\t\tw.writeString(\", \")\n\t\t\tcharsLeft -= 2\n\t\t}\n\n\t\tfor len(s) > charsLeft {\n\t\t\ts = w.writeLine(s, charsLeft)\n\t\t\tcharsLeft = 75\n\t\t}\n\t\tw.writeString(s)\n\t\tif i := lastIndexByte(s, '\\n'); i != -1 {\n\t\t\tcharsLeft = 75 - (len(s) - i - 1)\n\t\t} else {\n\t\t\tcharsLeft -= len(s)\n\t\t}\n\t}\n\tw.writeString(\"\\r\\n\")\n}\n\nfunc (w *messageWriter) writeLine(s string, charsLeft int) string {\n\tif i := strings.IndexByte(s, '\\n'); i != -1 && i < charsLeft {\n\t\tw.writeString(s[:i+1])\n\t\treturn s[i+1:]\n\t}\n\n\tfor i := charsLeft - 1; i >= 0; i-- {\n\t\tif s[i] == ' ' {\n\t\t\tw.writeString(s[:i])\n\t\t\tw.writeString(\"\\r\\n \")\n\t\t\treturn s[i+1:]\n\t\t}\n\t}\n\n\tfor i := 75; i < len(s); i++ {\n\t\tif s[i] == ' ' {\n\t\t\tw.writeString(s[:i])\n\t\t\tw.writeString(\"\\r\\n \")\n\t\t\treturn s[i+1:]\n\t\t}\n\t\tif s[i] == '\\n' {\n\t\t\tw.writeString(s[:i+1])\n\t\t\treturn s[i+1:]\n\t\t}\n\t}\n\n\tw.writeString(s)\n\treturn \"\"\n}\n\nfunc (w *messageWriter) writeHeaders(h map[string][]string) {\n\tif w.depth == 0 {\n\t\tfor k, v := range h {\n\t\t\tif k != \"Bcc\" {\n\t\t\t\tw.writeHeader(k, v...)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tw.createPart(h)\n\t}\n}\n\nfunc (w *messageWriter) writeBody(f func(io.Writer) error, enc encoding) {\n\tvar subWriter io.Writer\n\tif w.depth == 0 {\n\t\tw.writeString(\"\\r\\n\")\n\t\tsubWriter = w.w\n\t} else {\n\t\tsubWriter = w.partWriter\n\t}\n\n\tif enc == encodingBase64 {\n\t\twc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))\n\t\tw.err = f(wc)\n\t\twc.Close()\n\t} else if enc == encodingUnencoded {\n\t\tw.err = f(subWriter)\n\t} else {\n\t\twc := newQPWriter(subWriter)\n\t\tw.err = f(wc)\n\t\twc.Close()\n\t}\n}\n\nconst maxLineLen = 76\n\ntype base64LineWriter struct {\n\tw       io.Writer\n\tlineLen int\n}\n\nfunc newBase64LineWriter(w io.Writer) *base64LineWriter {\n\treturn &base64LineWriter{w: w}\n}\n\nfunc (w *base64LineWriter) Write(p []byte) (int, error) {\n\tn := 0\n\tfor len(p)+w.lineLen > maxLineLen {\n\t\tw.w.Write(p[:maxLineLen-w.lineLen])\n\t\tw.w.Write([]byte(\"\\r\\n\"))\n\t\tp = p[maxLineLen-w.lineLen:]\n\t\tn += maxLineLen - w.lineLen\n\t\tw.lineLen = 0\n\t}\n\n\tw.w.Write(p)\n\tw.lineLen += len(p)\n\n\treturn n + len(p), nil\n}\n\nvar now = time.Now\n"
  },
  {
    "path": "method_override.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gobuffalo/buffalo/internal/defaults\"\n)\n\n// MethodOverride is the default implementation for the\n// Options#MethodOverride. By default it will look for a form value\n// name `_method` and change the request method if that is\n// present and the original request is of type \"POST\". This is\n// added automatically when using `New` Buffalo, unless\n// an alternative is defined in the Options.\nfunc MethodOverride(res http.ResponseWriter, req *http.Request) {\n\tif req.Method == \"POST\" {\n\t\treq.Method = defaults.String(req.FormValue(\"_method\"), \"POST\")\n\t\treq.Form.Del(\"_method\")\n\t\treq.PostForm.Del(\"_method\")\n\t}\n}\n"
  },
  {
    "path": "method_override_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_MethodOverride(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.PUT(\"/\", func(c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"you put me!\"))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Post(url.Values{\"_method\": []string{\"PUT\"}})\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Equal(\"you put me!\", res.Body.String())\n}\n"
  },
  {
    "path": "middleware.go",
    "content": "package buffalo\n\nimport (\n\t\"maps\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// MiddlewareFunc defines the interface for a piece of Buffalo\n// Middleware.\n/*\n\tfunc DoSomething(next Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\t// do something before calling the next handler\n\t\t\terr := next(c)\n\t\t\t// do something after call the handler\n\t\t\treturn err\n\t\t}\n\t}\n*/\ntype MiddlewareFunc func(Handler) Handler\n\nconst funcKeyDelimeter = \":\"\n\n// Use the specified Middleware for the App.\n// When defined on an `*App` the specified middleware will be\n// inherited by any `Group` calls that are made on that on\n// the App.\nfunc (a *App) Use(mw ...MiddlewareFunc) {\n\ta.Middleware.Use(mw...)\n}\n\n// MiddlewareStack manages the middleware stack for an App/Group.\ntype MiddlewareStack struct {\n\tstack []MiddlewareFunc\n\tskips map[string]bool\n}\n\nfunc (ms MiddlewareStack) String() string {\n\ts := []string{}\n\tfor _, m := range ms.stack {\n\t\ts = append(s, funcKey(m))\n\t}\n\n\treturn strings.Join(s, \"\\n\")\n}\n\nfunc (ms *MiddlewareStack) clone() *MiddlewareStack {\n\tn := newMiddlewareStack()\n\tn.stack = append(n.stack, ms.stack...)\n\tmaps.Copy(n.skips, ms.skips)\n\treturn n\n}\n\n// Clear wipes out the current middleware stack for the App/Group,\n// any middleware previously defined will be removed leaving an empty\n// middleware stack.\nfunc (ms *MiddlewareStack) Clear() {\n\tms.stack = []MiddlewareFunc{}\n\tms.skips = map[string]bool{}\n}\n\n// Use the specified Middleware for the App.\n// When defined on an `*App` the specified middleware will be\n// inherited by any `Group` calls that are made on that on\n// the App.\nfunc (ms *MiddlewareStack) Use(mw ...MiddlewareFunc) {\n\tms.stack = append(ms.stack, mw...)\n}\n\n// Remove the specified Middleware(s) for the App/group. This is useful when\n// the middleware will be skipped by the entire group.\n/*\n\ta.Middleware.Remove(Authorization)\n*/\nfunc (ms *MiddlewareStack) Remove(mws ...MiddlewareFunc) {\n\tresult := []MiddlewareFunc{}\n\nbase:\n\tfor _, existing := range ms.stack {\n\t\tfor _, banned := range mws {\n\t\t\tif funcKey(existing) == funcKey(banned) {\n\t\t\t\tcontinue base\n\t\t\t}\n\t\t}\n\n\t\tresult = append(result, existing)\n\t}\n\n\tms.stack = result\n\n}\n\n// Skip a specified piece of middleware the specified Handlers.\n// This is useful for things like wrapping your application in an\n// authorization middleware, but skipping it for things the home\n// page, the login page, etc...\n/*\n\ta.Middleware.Skip(Authorization, HomeHandler, LoginHandler, RegistrationHandler)\n*/\nfunc (ms *MiddlewareStack) Skip(mw MiddlewareFunc, handlers ...Handler) {\n\tfor _, h := range handlers {\n\t\tkey := funcKey(mw, h)\n\t\tms.skips[key] = true\n\t}\n}\n\n// Replace a piece of middleware with another piece of middleware. Great for\n// testing.\nfunc (ms *MiddlewareStack) Replace(mw1 MiddlewareFunc, mw2 MiddlewareFunc) {\n\tm1k := funcKey(mw1)\n\tstack := []MiddlewareFunc{}\n\tfor _, mw := range ms.stack {\n\t\tif funcKey(mw) == m1k {\n\t\t\tstack = append(stack, mw2)\n\t\t} else {\n\t\t\tstack = append(stack, mw)\n\t\t}\n\t}\n\tms.stack = stack\n}\n\n// assertMiddleware is a hidden middleware that works just befor and after the\n// actual handler runs to make it sure everything is OK with the Handler\n// specification.\n//\n// It writes response header with `http.StatusOK` if the request handler exited\n// without error but the response status is still zero. Setting response is the\n// responsibility of handler but this middleware make it sure the response\n// should be compatible with middleware specification.\n//\n// See also: https://github.com/gobuffalo/buffalo/issues/2339\nfunc assertMiddleware(handler Handler) Handler {\n\treturn func(c Context) error {\n\t\terr := handler(c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif res, ok := c.Response().(*Response); ok {\n\t\t\tif res.Status == 0 {\n\t\t\t\tres.WriteHeader(http.StatusOK)\n\t\t\t\tc.Logger().Debug(\"warning: handler exited without setting the response status. 200 OK will be used.\")\n\t\t\t}\n\t\t}\n\n\t\treturn err\n\t}\n}\n\nfunc (ms *MiddlewareStack) handler(info RouteInfo) Handler {\n\ttstack := []MiddlewareFunc{assertMiddleware}\n\n\tif len(ms.stack) > 0 {\n\t\tsl := len(ms.stack) - 1\n\t\tfor i := sl; i >= 0; i-- {\n\t\t\tmw := ms.stack[i]\n\t\t\tkey := funcKey(mw, info)\n\t\t\tif !ms.skips[key] {\n\t\t\t\ttstack = append(tstack, mw)\n\t\t\t}\n\t\t}\n\t}\n\n\th := info.Handler\n\tfor _, mw := range tstack {\n\t\th = mw(h)\n\t}\n\n\treturn h\n}\n\nfunc newMiddlewareStack(mws ...MiddlewareFunc) *MiddlewareStack {\n\treturn &MiddlewareStack{\n\t\tstack: mws,\n\t\tskips: map[string]bool{},\n\t}\n}\n\nfunc funcKey(funcs ...any) string {\n\tnames := []string{}\n\tfor _, f := range funcs {\n\t\tif n, ok := f.(RouteInfo); ok {\n\t\t\tnames = append(names, n.HandlerName)\n\t\t\tcontinue\n\t\t}\n\t\trv := reflect.ValueOf(f)\n\t\tptr := rv.Pointer()\n\t\tkeyMapMutex.Lock()\n\t\tif n, ok := keyMap[ptr]; ok {\n\t\t\tkeyMapMutex.Unlock()\n\t\t\tnames = append(names, n)\n\t\t\tcontinue\n\t\t}\n\t\tkeyMapMutex.Unlock()\n\t\tn := ptrName(ptr)\n\t\tkeyMapMutex.Lock()\n\t\tkeyMap[ptr] = n\n\t\tkeyMapMutex.Unlock()\n\t\tnames = append(names, n)\n\t}\n\treturn strings.Join(names, funcKeyDelimeter)\n}\n\nfunc ptrName(ptr uintptr) string {\n\tfnc := runtime.FuncForPC(ptr)\n\tn := fnc.Name()\n\n\tn = strings.Replace(n, \"-fm\", \"\", 1)\n\tn = strings.Replace(n, \"(\", \"\", 1)\n\tn = strings.Replace(n, \")\", \"\", 1)\n\treturn n\n}\n\nfunc setFuncKey(f any, name string) {\n\trv := reflect.ValueOf(f)\n\tif rv.Kind() == reflect.Ptr {\n\t\trv = rv.Elem()\n\t}\n\tptr := rv.Pointer()\n\tkeyMapMutex.Lock()\n\tkeyMap[ptr] = name\n\tkeyMapMutex.Unlock()\n}\n\nvar keyMap = map[uintptr]string{}\nvar keyMapMutex = sync.Mutex{}\n"
  },
  {
    "path": "middleware_test.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// Test_App_Use tests that middleware gets added\nfunc Test_App_Use(t *testing.T) {\n\tr := require.New(t)\n\n\tlog := []string{}\n\ta := New(Options{})\n\ta.Use(func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"end\")\n\t\t\treturn err\n\t\t}\n\t})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\tlog = append(log, \"handler\")\n\t\treturn nil\n\t})\n\n\tw := httptest.New(a)\n\tw.HTML(\"/\").Get()\n\tr.Len(log, 3)\n\tr.Equal([]string{\"start\", \"handler\", \"end\"}, log)\n}\n\n// Test_Middleware_Replace tests that middleware gets added\nfunc Test_Middleware_Replace(t *testing.T) {\n\tr := require.New(t)\n\n\tlog := []string{}\n\ta := New(Options{})\n\tmw1 := func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"m1 start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"m1 end\")\n\t\t\treturn err\n\t\t}\n\t}\n\tmw2 := func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"m2 start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"m2 end\")\n\t\t\treturn err\n\t\t}\n\t}\n\ta.Use(mw1)\n\ta.Middleware.Replace(mw1, mw2)\n\n\ta.GET(\"/\", func(c Context) error {\n\t\tlog = append(log, \"handler\")\n\t\treturn nil\n\t})\n\n\tw := httptest.New(a)\n\tw.HTML(\"/\").Get()\n\tr.Len(log, 3)\n\tr.Equal([]string{\"m2 start\", \"handler\", \"m2 end\"}, log)\n}\n\n// Test_Middleware_Skip tests that middleware gets skipped\nfunc Test_Middleware_Skip(t *testing.T) {\n\tr := require.New(t)\n\n\tlog := []string{}\n\ta := New(Options{})\n\tmw1 := func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"mw1 start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"mw1 end\")\n\t\t\treturn err\n\t\t}\n\t}\n\tmw2 := func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"mw2 start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"mw2 end\")\n\t\t\treturn err\n\t\t}\n\t}\n\ta.Use(mw1)\n\ta.Use(mw2)\n\n\th1 := func(c Context) error {\n\t\tlog = append(log, \"h1\")\n\t\treturn nil\n\t}\n\th2 := func(c Context) error {\n\t\tlog = append(log, \"h2\")\n\t\treturn nil\n\t}\n\n\ta.GET(\"/h1\", h1)\n\ta.GET(\"/h2\", h2)\n\n\ta.Middleware.Skip(mw2, h2)\n\n\tw := httptest.New(a)\n\n\tw.HTML(\"/h2\").Get()\n\tr.Len(log, 3)\n\tr.Equal([]string{\"mw1 start\", \"h2\", \"mw1 end\"}, log)\n\n\tlog = []string{}\n\tw.HTML(\"/h1\").Get()\n\tr.Len(log, 5)\n\tr.Equal([]string{\"mw1 start\", \"mw2 start\", \"h1\", \"mw2 end\", \"mw1 end\"}, log)\n}\n\ntype carsResource struct {\n\tResource\n}\n\nfunc (ur *carsResource) Show(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"show\"))\n}\n\nfunc (ur *carsResource) List(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"list\"))\n}\n\n// Test_Middleware_Skip tests that middleware gets skipped\nfunc Test_Middleware_Skip_Resource(t *testing.T) {\n\tr := require.New(t)\n\n\tlog := []string{}\n\tmw1 := func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tlog = append(log, \"mw1 start\")\n\t\t\terr := h(c)\n\t\t\tlog = append(log, \"mw1 end\")\n\t\t\treturn err\n\t\t}\n\t}\n\n\ta := New(Options{})\n\tvar cr Resource = &carsResource{}\n\tg := a.Resource(\"/autos\", cr)\n\tg.Use(mw1)\n\n\tvar ur Resource = &carsResource{}\n\tg = a.Resource(\"/cars\", ur)\n\tg.Use(mw1)\n\n\t// fmt.Println(\"set up skip\")\n\tg.Middleware.Skip(mw1, ur.Show)\n\n\tw := httptest.New(a)\n\n\t// fmt.Println(\"make autos call\")\n\tlog = []string{}\n\tres := w.HTML(\"/autos/1\").Get()\n\tr.Len(log, 2)\n\tr.Equal(\"show\", res.Body.String())\n\n\t// fmt.Println(\"make list call\")\n\tlog = []string{}\n\tres = w.HTML(\"/cars\").Get()\n\tr.Len(log, 2)\n\tr.Equal([]string{\"mw1 start\", \"mw1 end\"}, log)\n\tr.Equal(\"list\", res.Body.String())\n\n\t// fmt.Println(\"make show call\")\n\tlog = []string{}\n\tres = w.HTML(\"/cars/1\").Get()\n\tr.Len(log, 0)\n\tr.Equal(\"show\", res.Body.String())\n\n}\n\n// Test_Middleware_Clear confirms that middle gets cleared\nfunc Test_Middleware_Clear(t *testing.T) {\n\tr := require.New(t)\n\tmws := newMiddlewareStack()\n\tmw := func(h Handler) Handler { return h }\n\tmws.Use(mw)\n\tmws.Skip(mw, voidHandler)\n\n\tr.Len(mws.stack, 1)\n\tr.Len(mws.skips, 1)\n\n\tmws.Clear()\n\n\tr.Len(mws.stack, 0)\n\tr.Len(mws.skips, 0)\n}\n\nfunc Test_Middleware_Remove(t *testing.T) {\n\tr := require.New(t)\n\tlog := []string{}\n\n\tmw1 := func(h Handler) Handler {\n\t\tlog = append(log, \"mw1\")\n\t\treturn h\n\t}\n\n\tmw2 := func(h Handler) Handler {\n\t\tlog = append(log, \"mw2\")\n\t\treturn h\n\t}\n\n\ta := New(Options{})\n\ta.Use(mw2)\n\ta.Use(mw1)\n\n\tvar cr Resource = &carsResource{}\n\tg := a.Resource(\"/autos\", cr)\n\tg.Middleware.Remove(mw2)\n\n\ta.Resource(\"/all_log_autos\", cr)\n\tw := httptest.New(a)\n\n\tng := a.Resource(\"/no_log_autos\", cr)\n\tng.Middleware.Remove(mw1, mw2)\n\n\t_ = w.HTML(\"/autos/1\").Get()\n\tr.Len(log, 1)\n\tr.Equal(\"mw1\", log[0])\n\n\tlog = []string{}\n\t_ = w.HTML(\"/all_log_autos/1\").Get()\n\tr.Len(log, 2)\n\tr.Contains(log, \"mw2\")\n\tr.Contains(log, \"mw1\")\n\n\tlog = []string{}\n\t_ = w.HTML(\"/no_log_autos/1\").Get()\n\tr.Len(log, 0)\n}\n\nfunc Test_AssertMiddleware_NilStatus200(t *testing.T) {\n\tr := require.New(t)\n\tvar status int\n\n\ta := New(Options{})\n\ta.Use(func(h Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\terr := h(c)\n\n\t\t\tres, ok := c.Response().(*Response)\n\t\t\tr.True(ok)\n\t\t\tstatus = res.Status\n\n\t\t\treturn err\n\t\t}\n\t})\n\n\ta.GET(\"/200\", func(c Context) error {\n\t\tc.Response().WriteHeader(http.StatusOK) // explicitly set\n\t\treturn nil\n\t})\n\n\ta.GET(\"/404\", func(c Context) error {\n\t\tc.Response().WriteHeader(http.StatusNotFound) //explicitly set\n\t\treturn nil\n\t})\n\n\ta.GET(\"/nil\", func(c Context) error {\n\t\treturn nil // return nil without setting response status. should be OK\n\t})\n\n\ta.GET(\"/500\", func(c Context) error {\n\t\treturn fmt.Errorf(\"error\") // return error\n\t})\n\n\ta.GET(\"/502\", func(c Context) error {\n\t\treturn HTTPError{Status: http.StatusBadGateway} // return HTTPError\n\t})\n\n\ta.GET(\"/panic\", func(c Context) error {\n\t\tpanic(\"hoy hoy\")\n\t})\n\n\ttests := []struct {\n\t\tpath   string\n\t\tcode   int\n\t\tstatus int\n\t}{\n\t\t{\"/200\", http.StatusOK, http.StatusOK}, // when the handler set response code explicitly (e.g. 200, 404)\n\t\t{\"/404\", http.StatusNotFound, http.StatusNotFound},\n\t\t{\"/nil\", http.StatusOK, http.StatusOK},        // when the handler returns nil without setting status code\n\t\t{\"/502\", http.StatusBadGateway, 0},            // set by defaultErrorHandler, when the handler just returns error\n\t\t{\"/500\", http.StatusInternalServerError, 0},   // set by defaultErrorHandler, when the handler returns HTTPError\n\t\t{\"/panic\", http.StatusInternalServerError, 0}, // set by PanicHandler\n\t}\n\tw := httptest.New(a)\n\n\tfor _, tc := range tests {\n\t\tres := w.HTML(\"%s\", tc.path).Get()\n\t\tr.Equal(tc.status, status)\n\t\tr.Equal(tc.code, res.Code)\n\t}\n}\n"
  },
  {
    "path": "not_found_test.go",
    "content": "package buffalo\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_App_Dev_NotFound(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.Env = \"development\"\n\ta.GET(\"/foo\", func(c Context) error { return nil })\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/bad\").Get()\n\n\tbody := res.Body.String()\n\tr.Contains(body, \"404 - ERROR!\")\n\tr.Contains(body, \"/foo\")\n\tr.Equal(http.StatusNotFound, res.Code)\n}\n\nfunc Test_App_Dev_NotFound_JSON(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.Env = \"development\"\n\ta.GET(\"/foo\", func(c Context) error { return nil })\n\n\tw := httptest.New(a)\n\tres := w.JSON(\"/bad\").Get()\n\tr.Equal(http.StatusNotFound, res.Code)\n\n\tjb := map[string]any{}\n\terr := json.NewDecoder(res.Body).Decode(&jb)\n\tr.NoError(err)\n\tr.Equal(float64(http.StatusNotFound), jb[\"code\"])\n}\n\nfunc Test_App_Override_NotFound(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.ErrorHandlers[http.StatusNotFound] = func(status int, err error, c Context) error {\n\t\tc.Response().WriteHeader(http.StatusNotFound)\n\t\tc.Response().Write([]byte(\"oops!!!\"))\n\t\treturn nil\n\t}\n\ta.GET(\"/foo\", func(c Context) error { return nil })\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/bad\").Get()\n\tr.Equal(http.StatusNotFound, res.Code)\n\n\tbody := res.Body.String()\n\tr.Equal(body, \"oops!!!\")\n\tr.NotContains(body, \"/foo\")\n}\n"
  },
  {
    "path": "options.go",
    "content": "package buffalo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/defaults\"\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gobuffalo/buffalo/worker\"\n\t\"github.com/gobuffalo/logger\"\n\t\"github.com/gorilla/sessions\"\n)\n\n// Options are used to configure and define how your application should run.\ntype Options struct {\n\tName string `json:\"name\"`\n\t// Addr is the bind address provided to http.Server. Default is \"127.0.0.1:3000\"\n\t// Can be set using ENV vars \"ADDR\" and \"PORT\".\n\tAddr string `json:\"addr\"`\n\t// Host that this application will be available at. Default is \"http://127.0.0.1:[$PORT|3000]\".\n\tHost string `json:\"host\"`\n\n\t// Env is the \"environment\" in which the App is running. Default is \"development\".\n\tEnv string `json:\"env\"`\n\n\t// LogLvl defaults to logger.DebugLvl.\n\tLogLvl logger.Level `json:\"log_lvl\"`\n\t// Logger to be used with the application. A default one is provided.\n\tLogger Logger `json:\"-\"`\n\n\t// MethodOverride allows for changing of the request method type. See the default\n\t// implementation at buffalo.MethodOverride\n\tMethodOverride http.HandlerFunc `json:\"-\"`\n\n\t// SessionStore is the `github.com/gorilla/sessions` store used to back\n\t// the session. It defaults to use a cookie store and the ENV variable\n\t// `SESSION_SECRET`.\n\tSessionStore sessions.Store `json:\"-\"`\n\t// SessionName is the name of the session cookie that is set. This defaults\n\t// to \"_buffalo_session\".\n\tSessionName string `json:\"session_name\"`\n\n\t// Timeout in second for ongoing requests when shutdown the server.\n\t// The default value is 60.\n\tTimeoutSecondShutdown int `json:\"timeout_second_shutdown\"`\n\n\t// Worker implements the Worker interface and can process tasks in the background.\n\t// Default is \"github.com/gobuffalo/worker.Simple.\n\tWorker worker.Worker `json:\"-\"`\n\t// WorkerOff tells App.Start() whether to start the Worker process or not. Default is \"false\".\n\tWorkerOff bool `json:\"worker_off\"`\n\n\t// PreHandlers are http.Handlers that are called between the http.Server\n\t// and the buffalo Application.\n\tPreHandlers []http.Handler `json:\"-\"`\n\t// PreWare takes an http.Handler and returns an http.Handler\n\t// and acts as a pseudo-middleware between the http.Server and\n\t// a Buffalo application.\n\tPreWares []PreWare `json:\"-\"`\n\n\t// CompressFiles enables gzip compression of static files served by ServeFiles using\n\t// gorilla's CompressHandler (https://godoc.org/github.com/gorilla/handlers#CompressHandler).\n\t// Default is \"false\".\n\tCompressFiles bool `json:\"compress_files\"`\n\n\tPrefix  string          `json:\"prefix\"`\n\tContext context.Context `json:\"-\"`\n\n\tcancel context.CancelFunc\n}\n\n// PreWare takes an http.Handler and returns an http.Handler\n// and acts as a pseudo-middleware between the http.Server and\n// a Buffalo application.\ntype PreWare func(http.Handler) http.Handler\n\n// NewOptions returns a new Options instance with sensible defaults\nfunc NewOptions() Options {\n\treturn optionsWithDefaults(Options{})\n}\n\nfunc optionsWithDefaults(opts Options) Options {\n\topts.Env = defaults.String(opts.Env, env.Get(\"GO_ENV\", \"development\"))\n\topts.Name = defaults.String(opts.Name, \"/\")\n\taddr := \"0.0.0.0\"\n\tif opts.Env == \"development\" {\n\t\taddr = \"127.0.0.1\"\n\t}\n\tenvAddr := env.Get(\"ADDR\", addr)\n\n\tif strings.HasPrefix(envAddr, \"unix:\") {\n\t\t// UNIX domain socket doesn't have a port\n\t\topts.Addr = envAddr\n\t} else {\n\t\t// TCP case\n\t\topts.Addr = defaults.String(opts.Addr, fmt.Sprintf(\"%s:%s\", envAddr, env.Get(\"PORT\", \"3000\")))\n\t}\n\topts.Host = defaults.String(opts.Host, env.Get(\"HOST\", fmt.Sprintf(\"http://127.0.0.1:%s\", env.Get(\"PORT\", \"3000\"))))\n\n\tif opts.PreWares == nil {\n\t\topts.PreWares = []PreWare{}\n\t}\n\tif opts.PreHandlers == nil {\n\t\topts.PreHandlers = []http.Handler{}\n\t}\n\n\tif opts.Context == nil {\n\t\topts.Context = context.Background()\n\t}\n\topts.Context, opts.cancel = context.WithCancel(opts.Context)\n\n\tif opts.Logger == nil {\n\t\tif lvl, err := env.MustGet(\"LOG_LEVEL\"); err == nil {\n\t\t\topts.LogLvl, err = logger.ParseLevel(lvl)\n\t\t\tif err != nil {\n\t\t\t\topts.LogLvl = logger.DebugLevel\n\t\t\t}\n\t\t}\n\n\t\tif opts.LogLvl == 0 {\n\t\t\topts.LogLvl = logger.DebugLevel\n\t\t}\n\n\t\topts.Logger = logger.New(opts.LogLvl)\n\t}\n\n\tif opts.SessionStore == nil {\n\t\tsecret := env.Get(\"SESSION_SECRET\", \"\")\n\n\t\tif secret == \"\" && (opts.Env == \"development\" || opts.Env == \"test\") {\n\t\t\tsecret = \"buffalo-secret\"\n\t\t}\n\n\t\t// In production a SESSION_SECRET must be set!\n\t\tif secret == \"\" {\n\t\t\topts.Logger.Warn(\"Unless you set SESSION_SECRET env variable, your session storage is not protected!\")\n\t\t}\n\n\t\tcookieStore := sessions.NewCookieStore([]byte(secret))\n\n\t\t//Cookie secure attributes, see: https://www.owasp.org/index.php/Testing_for_cookies_attributes_(OTG-SESS-002)\n\t\tcookieStore.Options.HttpOnly = true\n\t\tif opts.Env == \"production\" {\n\t\t\tcookieStore.Options.Secure = true\n\t\t}\n\n\t\topts.SessionStore = cookieStore\n\t}\n\topts.SessionName = defaults.String(opts.SessionName, \"_buffalo_session\")\n\n\tif opts.Worker == nil {\n\t\tw := worker.NewSimpleWithContext(opts.Context)\n\t\tw.Logger = opts.Logger\n\t\topts.Worker = w\n\t}\n\n\topts.TimeoutSecondShutdown = defaults.Int(opts.TimeoutSecondShutdown, 60)\n\n\treturn opts\n}\n"
  },
  {
    "path": "options_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestOptions_NewOptions(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tenv       string\n\t\tsecret    string\n\t\texpectErr string\n\t}{\n\t\t{name: \"Development doesn't fail with no secret\", env: \"development\", secret: \"\", expectErr: \"securecookie:\"},\n\t\t{name: \"Development doesn't fail with secret set\", env: \"development\", secret: \"secrets\", expectErr: \"securecookie:\"},\n\t\t{name: \"Test doesn't fail with secret set\", env: \"test\", secret: \"\", expectErr: \"securecookie:\"},\n\t\t{name: \"Test doesn't fail with secret set\", env: \"test\", secret: \"secrets\", expectErr: \"securecookie:\"},\n\t\t{name: \"Production fails with no secret\", env: \"production\", secret: \"\", expectErr: \"securecookie:\"},\n\t\t{name: \"Production doesn't fail with secret set\", env: \"production\", secret: \"secrets\", expectErr: \"securecookie:\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tr := require.New(t)\n\t\t\tenv.Temp(func() {\n\t\t\t\tenv.Set(\"GO_ENV\", test.env)\n\t\t\t\tenv.Set(\"SESSION_SECRET\", test.secret)\n\n\t\t\t\topts := NewOptions()\n\n\t\t\t\treq, _ := http.NewRequest(\"GET\", \"/\", strings.NewReader(\"\"))\n\t\t\t\treq.AddCookie(&http.Cookie{Name: \"_buffalo_session\"})\n\n\t\t\t\t_, err := opts.SessionStore.New(req, \"_buffalo_session\")\n\n\t\t\t\tr.Error(err)\n\t\t\t\tr.Contains(err.Error(), test.expectErr)\n\t\t\t})\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "plugins/cache.go",
    "content": "package plugins\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n)\n\ntype cachedPlugin struct {\n\tCommands Commands `json:\"commands\"`\n\tCheckSum string   `json:\"check_sum\"`\n}\n\ntype cachedPlugins map[string]cachedPlugin\n\n// CachePath returns the path to the plugins cache\nvar CachePath = func() string {\n\thome := \".\"\n\tif usr, err := user.Current(); err == nil {\n\t\thome = usr.HomeDir\n\t}\n\treturn filepath.Join(home, \".buffalo\", \"plugin.cache\")\n}()\n\nvar cacheMoot sync.RWMutex\n\nvar cacheOn = env.Get(\"BUFFALO_PLUGIN_CACHE\", \"on\")\n\nvar cache = func() cachedPlugins {\n\tm := cachedPlugins{}\n\tif cacheOn != \"on\" {\n\t\treturn m\n\t}\n\tf, err := os.Open(CachePath)\n\tif err != nil {\n\t\treturn m\n\t}\n\tdefer f.Close()\n\tif err := json.NewDecoder(f).Decode(&m); err != nil {\n\t\tf.Close()\n\t\tos.Remove(f.Name())\n\t}\n\treturn m\n}()\n\nfunc findInCache(path string) (cachedPlugin, bool) {\n\tcacheMoot.RLock()\n\tdefer cacheMoot.RUnlock()\n\tcp, ok := cache[path]\n\treturn cp, ok\n}\n\nfunc saveCache() error {\n\tif cacheOn != \"on\" {\n\t\treturn nil\n\t}\n\tcacheMoot.Lock()\n\tdefer cacheMoot.Unlock()\n\tos.MkdirAll(filepath.Dir(CachePath), 0744)\n\tf, err := os.Create(CachePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.NewEncoder(f).Encode(cache)\n}\n\nfunc sum(path string) string {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tdefer f.Close()\n\thash := sha256.New()\n\tif _, err := io.Copy(hash, f); err != nil {\n\t\treturn \"\"\n\t}\n\tsum := hash.Sum(nil)\n\n\ts := fmt.Sprintf(\"%x\", sum)\n\treturn s\n}\n\nfunc addToCache(path string, cp cachedPlugin) {\n\tif cp.CheckSum == \"\" {\n\t\tcp.CheckSum = sum(path)\n\t}\n\tcacheMoot.Lock()\n\tdefer cacheMoot.Unlock()\n\tcache[path] = cp\n}\n"
  },
  {
    "path": "plugins/command.go",
    "content": "package plugins\n\n// Command that the plugin supplies\ntype Command struct {\n\t// Name \"foo\"\n\tName string `json:\"name\"`\n\t// UseCommand \"bar\"\n\tUseCommand string `json:\"use_command\"`\n\t// BuffaloCommand \"generate\"\n\tBuffaloCommand string `json:\"buffalo_command\"`\n\t// Description \"generates a foo\"\n\tDescription string   `json:\"description,omitempty\"`\n\tAliases     []string `json:\"aliases,omitempty\"`\n\tBinary      string   `json:\"-\"`\n\tFlags       []string `json:\"flags,omitempty\"`\n\t// Filters events to listen to (\"\" or \"*\") is all events\n\tListenFor string `json:\"listen_for,omitempty\"`\n}\n\n// Commands is a slice of Command\ntype Commands []Command\n"
  },
  {
    "path": "plugins/decorate.go",
    "content": "package plugins\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/spf13/cobra\"\n)\n\n// ErrPlugMissing error for when a plugin is missing\nvar ErrPlugMissing = fmt.Errorf(\"plugin missing\")\n\n// Decorate setup cobra Commands for plugins\nfunc Decorate(c Command) *cobra.Command {\n\tvar flags []string\n\tif len(c.Flags) > 0 {\n\t\tflags = append(flags, c.Flags...)\n\t}\n\tcc := &cobra.Command{\n\t\tUse:     c.Name,\n\t\tShort:   fmt.Sprintf(\"[PLUGIN] %s\", c.Description),\n\t\tAliases: c.Aliases,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tplugCmd := c.Name\n\t\t\tif c.UseCommand != \"\" {\n\t\t\t\tplugCmd = c.UseCommand\n\t\t\t}\n\n\t\t\tax := []string{plugCmd}\n\t\t\tif plugCmd == \"-\" {\n\t\t\t\tax = []string{}\n\t\t\t}\n\n\t\t\tax = append(ax, args...)\n\t\t\tax = append(ax, flags...)\n\n\t\t\tbin, err := LookPath(c.Binary)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tex := exec.Command(bin, ax...)\n\t\t\tif runtime.GOOS != \"windows\" {\n\t\t\t\tex.Env = append(env.Environ(), \"BUFFALO_PLUGIN=1\")\n\t\t\t}\n\t\t\tex.Stdin = os.Stdin\n\t\t\tex.Stdout = os.Stdout\n\t\t\tex.Stderr = os.Stderr\n\t\t\treturn log(strings.Join(ex.Args, \" \"), ex.Run)\n\t\t},\n\t}\n\tcc.DisableFlagParsing = true\n\treturn cc\n}\n\n// LookPath for plugin\nfunc LookPath(s string) (string, error) {\n\tif _, err := os.Stat(s); err == nil {\n\t\treturn s, nil\n\t}\n\n\tif lp, err := exec.LookPath(s); err == nil {\n\t\treturn lp, err\n\t}\n\n\tvar bin string\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar looks []string\n\tif from, err := env.MustGet(\"BUFFALO_PLUGIN_PATH\"); err == nil {\n\t\tlooks = append(looks, from)\n\t} else {\n\t\tlooks = []string{filepath.Join(pwd, \"plugins\"), filepath.Join(env.GoPath(), \"bin\"), env.Get(\"PATH\", \"\")}\n\t}\n\n\tfor _, p := range looks {\n\t\tlp := filepath.Join(p, s)\n\t\tif lp, err = filepath.EvalSymlinks(lp); err == nil {\n\t\t\tbin = lp\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(bin) == 0 {\n\t\treturn \"\", ErrPlugMissing\n\t}\n\treturn bin, nil\n}\n"
  },
  {
    "path": "plugins/events.go",
    "content": "package plugins\n\nconst (\n\tEvtSetupStarted  = \"buffalo-plugins:setup:started\"\n\tEvtSetupErr      = \"buffalo-plugins:setup:err\"\n\tEvtSetupFinished = \"buffalo-plugins:setup:finished\"\n)\n"
  },
  {
    "path": "plugins/log.go",
    "content": "//go:build !debug\n\npackage plugins\n\nfunc log(_ string, fn func() error) error {\n\treturn fn()\n}\n"
  },
  {
    "path": "plugins/log_debug.go",
    "content": "//go:build debug\n\npackage plugins\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc log(name string, fn func() error) error {\n\tstart := time.Now()\n\tdefer fmt.Println(name, time.Now().Sub(start))\n\treturn fn()\n}\n"
  },
  {
    "path": "plugins/plugcmds/available.go",
    "content": "package plugcmds\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/gobuffalo/buffalo/plugins\"\n\t\"github.com/gobuffalo/events\"\n\t\"github.com/spf13/cobra\"\n)\n\n// NewAvailable returns a fully formed Available type\nfunc NewAvailable() *Available {\n\treturn &Available{\n\t\tplugs: plugMap{},\n\t}\n}\n\n// Available used to manage all of the available commands\n// for the plugin\ntype Available struct {\n\tplugs plugMap\n}\n\ntype plug struct {\n\tBuffaloCommand string\n\tCmd            *cobra.Command\n\tPlugin         plugins.Command\n}\n\nfunc (p plug) String() string {\n\tb, _ := json.Marshal(p.Plugin)\n\treturn string(b)\n}\n\n// Cmd returns the \"available\" command\nfunc (a *Available) Cmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"available\",\n\t\tShort: \"a list of available buffalo plugins\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn a.Encode(os.Stdout)\n\t\t},\n\t}\n}\n\n// Commands returns all of the commands that are available\nfunc (a *Available) Commands() []*cobra.Command {\n\tcmds := []*cobra.Command{a.Cmd()}\n\ta.plugs.Range(func(_ string, p plug) bool {\n\t\tcmds = append(cmds, p.Cmd)\n\t\treturn true\n\t})\n\treturn cmds\n}\n\n// Add a new command to this list of available ones.\n// The bufCmd should corresponding buffalo command that\n// command should live below.\n//\n// Special \"commands\":\n//\n//\t\"root\" - is the `buffalo` command\n//\t\"events\" - listens for emitted events\nfunc (a *Available) Add(bufCmd string, cmd *cobra.Command) error {\n\tif len(cmd.Aliases) == 0 {\n\t\tcmd.Aliases = []string{}\n\t}\n\tp := plug{\n\t\tBuffaloCommand: bufCmd,\n\t\tCmd:            cmd,\n\t\tPlugin: plugins.Command{\n\t\t\tName:           cmd.Use,\n\t\t\tBuffaloCommand: bufCmd,\n\t\t\tDescription:    cmd.Short,\n\t\t\tAliases:        cmd.Aliases,\n\t\t\tUseCommand:     cmd.Use,\n\t\t},\n\t}\n\ta.plugs.Store(p.String(), p)\n\treturn nil\n}\n\n// Mount all of the commands that are available\n// on to the other command. This is the recommended\n// approach for using Available.\n//\n//\ta.Mount(rootCmd)\nfunc (a *Available) Mount(cmd *cobra.Command) {\n\t// mount all the cmds on to the cobra command\n\tcmd.AddCommand(a.Cmd())\n\ta.plugs.Range(func(_ string, p plug) bool {\n\t\tcmd.AddCommand(p.Cmd)\n\t\treturn true\n\t})\n}\n\n// Encode into the required Buffalo plugins available\n// format\nfunc (a *Available) Encode(w io.Writer) error {\n\tvar plugs plugins.Commands\n\ta.plugs.Range(func(_ string, p plug) bool {\n\t\tplugs = append(plugs, p.Plugin)\n\t\treturn true\n\t})\n\treturn json.NewEncoder(w).Encode(plugs)\n}\n\n// Listen adds a command for github.com/gobuffalo/events.\n// This will listen for ALL events. Use ListenFor to\n// listen to a regex of events.\nfunc (a *Available) Listen(fn func(e events.Event) error) error {\n\treturn a.Add(\"events\", buildListen(fn))\n}\n\n// ListenFor adds a command for github.com/gobuffalo/events.\n// This will only listen for events that match the regex provided.\nfunc (a *Available) ListenFor(rx string, fn func(e events.Event) error) error {\n\tcmd := buildListen(fn)\n\tp := plug{\n\t\tBuffaloCommand: \"events\",\n\t\tCmd:            cmd,\n\t\tPlugin: plugins.Command{\n\t\t\tName:           cmd.Use,\n\t\t\tBuffaloCommand: \"events\",\n\t\t\tDescription:    cmd.Short,\n\t\t\tAliases:        cmd.Aliases,\n\t\t\tUseCommand:     cmd.Use,\n\t\t\tListenFor:      rx,\n\t\t},\n\t}\n\ta.plugs.Store(p.String(), p)\n\treturn nil\n}\n\nfunc buildListen(fn func(e events.Event) error) *cobra.Command {\n\tlistenCmd := &cobra.Command{\n\t\tUse:     \"listen\",\n\t\tShort:   \"listens to github.com/gobuffalo/events\",\n\t\tAliases: []string{},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) == 0 {\n\t\t\t\treturn fmt.Errorf(\"must pass a payload\")\n\t\t\t}\n\n\t\t\te := events.Event{}\n\t\t\terr := json.Unmarshal([]byte(args[0]), &e)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn fn(e)\n\t\t},\n\t}\n\treturn listenCmd\n}\n"
  },
  {
    "path": "plugins/plugcmds/available_test.go",
    "content": "package plugcmds\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/events\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Available_Add(t *testing.T) {\n\tr := require.New(t)\n\n\ta := NewAvailable()\n\terr := a.Add(\"generate\", &cobra.Command{\n\t\tUse:     \"foo\",\n\t\tShort:   \"generates foo\",\n\t\tAliases: []string{\"f\"},\n\t})\n\tr.NoError(err)\n\n\tr.Len(a.Commands(), 2)\n}\n\nfunc Test_Available_Encode(t *testing.T) {\n\tr := require.New(t)\n\n\tbb := &bytes.Buffer{}\n\n\ta := NewAvailable()\n\terr := a.Add(\"generate\", &cobra.Command{\n\t\tUse:     \"foo\",\n\t\tShort:   \"generates foo\",\n\t\tAliases: []string{\"f\"},\n\t})\n\tr.NoError(err)\n\n\tr.NoError(a.Encode(bb))\n\tconst exp = `[{\"name\":\"foo\",\"use_command\":\"foo\",\"buffalo_command\":\"generate\",\"description\":\"generates foo\",\"aliases\":[\"f\"]}]`\n\tr.Equal(exp, strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Available_Listen(t *testing.T) {\n\tr := require.New(t)\n\n\ta := NewAvailable()\n\terr := a.Listen(func(e events.Event) error {\n\t\treturn nil\n\t})\n\tr.NoError(err)\n\n\tr.Len(a.Commands(), 2)\n}\n"
  },
  {
    "path": "plugins/plugcmds/plug_map.go",
    "content": "//go:generate mapgen -name \"plug\" -zero \"plug{}\" -go-type \"plug\" -pkg \"\" -a \"nil\" -b \"nil\" -c \"nil\" -bb \"nil\" -destination \"plugcmds\"\n// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.\n\npackage plugcmds\n\nimport (\n\t\"slices\"\n\t\"sync\"\n)\n\n// plugMap wraps sync.Map and uses the following types:\n// key:   string\n// value: plug\ntype plugMap struct {\n\tdata sync.Map\n}\n\n// Delete the key from the map\nfunc (m *plugMap) Delete(key string) {\n\tm.data.Delete(key)\n}\n\n// Load the key from the map.\n// Returns plug or bool.\n// A false return indicates either the key was not found\n// or the value is not of type plug\nfunc (m *plugMap) Load(key string) (plug, bool) {\n\ti, ok := m.data.Load(key)\n\tif !ok {\n\t\treturn plug{}, false\n\t}\n\ts, ok := i.(plug)\n\treturn s, ok\n}\n\n// LoadOrStore will return an existing key or\n// store the value if not already in the map\nfunc (m *plugMap) LoadOrStore(key string, value plug) (plug, bool) {\n\ti, _ := m.data.LoadOrStore(key, value)\n\ts, ok := i.(plug)\n\treturn s, ok\n}\n\n// Range over the plug values in the map\nfunc (m *plugMap) Range(f func(key string, value plug) bool) {\n\tm.data.Range(func(k, v any) bool {\n\t\tkey, ok := k.(string)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tvalue, ok := v.(plug)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn f(key, value)\n\t})\n}\n\n// Store a plug in the map\nfunc (m *plugMap) Store(key string, value plug) {\n\tm.data.Store(key, value)\n}\n\n// Keys returns a list of keys in the map\nfunc (m *plugMap) Keys() []string {\n\tvar keys []string\n\tm.Range(func(key string, value plug) bool {\n\t\tkeys = append(keys, key)\n\t\treturn true\n\t})\n\tslices.Sort(keys)\n\treturn keys\n}\n"
  },
  {
    "path": "plugins/plugcmds/plug_map_test.go",
    "content": "// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.\n\npackage plugcmds\n\nimport (\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_plugMap(t *testing.T) {\n\tr := require.New(t)\n\n\tsm := &plugMap{}\n\n\tsm.Store(\"a\", plug{})\n\n\ts, ok := sm.Load(\"a\")\n\tr.True(ok)\n\tr.Equal(plug{}, s)\n\n\ts, ok = sm.LoadOrStore(\"b\", plug{})\n\tr.True(ok)\n\tr.Equal(plug{}, s)\n\n\ts, ok = sm.LoadOrStore(\"b\", plug{})\n\tr.True(ok)\n\tr.Equal(plug{}, s)\n\n\tvar keys []string\n\n\tsm.Range(func(key string, value plug) bool {\n\t\tkeys = append(keys, key)\n\t\treturn true\n\t})\n\n\tslices.Sort(keys)\n\n\tr.Equal(sm.Keys(), keys)\n\n\tsm.Delete(\"b\")\n\tr.Equal([]string{\"a\", \"b\"}, keys)\n\n\tsm.Delete(\"b\")\n\t_, ok = sm.Load(\"b\")\n\tr.False(ok)\n\tp := plug{\n\t\tBuffaloCommand: \"foo\",\n\t}\n\tfunc(m *plugMap) {\n\t\tm.Store(\"c\", p)\n\t}(sm)\n\ts, ok = sm.Load(\"c\")\n\tr.True(ok)\n\tr.Equal(p, s)\n}\n"
  },
  {
    "path": "plugins/plugdeps/command.go",
    "content": "package plugdeps\n\nimport \"encoding/json\"\n\n// Command is the plugin command you want to control further\ntype Command struct {\n\tName     string    `toml:\"name\" json:\"name\"`\n\tFlags    []string  `toml:\"flags,omitempty\" json:\"flags,omitempty\"`\n\tCommands []Command `toml:\"command,omitempty\" json:\"commands,omitempty\"`\n}\n\n// String implementation of fmt.Stringer\nfunc (p Command) String() string {\n\tb, _ := json.Marshal(p)\n\treturn string(b)\n}\n"
  },
  {
    "path": "plugins/plugdeps/plugdeps.go",
    "content": "package plugdeps\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/meta\"\n)\n\n// ErrMissingConfig is if config/buffalo-plugins.toml file is not found. Use plugdeps#On(app) to test if plugdeps are being used\nvar ErrMissingConfig = fmt.Errorf(\"could not find a buffalo-plugins config file at %s\", ConfigPath(meta.New(\".\")))\n\n// List all of the plugins the application depends on. Will return ErrMissingConfig\n// if the app is not using config/buffalo-plugins.toml to manage their plugins.\n// Use plugdeps#On(app) to test if plugdeps are being used.\nfunc List(app meta.App) (*Plugins, error) {\n\tplugs := New()\n\tif app.WithPop {\n\t\tplugs.Add(pop)\n\t}\n\n\tlp, err := listLocal(app)\n\tif err != nil {\n\t\treturn plugs, err\n\t}\n\tplugs.Add(lp.List()...)\n\n\tif !On(app) {\n\t\treturn plugs, ErrMissingConfig\n\t}\n\n\tp := ConfigPath(app)\n\ttf, err := os.Open(p)\n\tif err != nil {\n\t\treturn plugs, err\n\t}\n\tif err := plugs.Decode(tf); err != nil {\n\t\treturn plugs, err\n\t}\n\n\treturn plugs, nil\n}\n\nfunc listLocal(app meta.App) (*Plugins, error) {\n\tplugs := New()\n\tpRoot := filepath.Join(app.Root, \"plugins\")\n\tif _, err := os.Stat(pRoot); err != nil {\n\t\treturn plugs, nil\n\t}\n\terr := filepath.WalkDir(pRoot, func(path string, d fs.DirEntry, err error) error {\n\t\tif d.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tif !strings.HasPrefix(d.Name(), \"buffalo-\") {\n\t\t\treturn nil\n\t\t}\n\n\t\tplugs.Add(Plugin{\n\t\t\tBinary: d.Name(),\n\t\t\tLocal:  \".\" + strings.TrimPrefix(path, app.Root),\n\t\t})\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn plugs, err\n\t}\n\n\treturn plugs, nil\n}\n\n// ConfigPath returns the path to the config/buffalo-plugins.toml file\n// relative to the app\nfunc ConfigPath(app meta.App) string {\n\treturn filepath.Join(app.Root, \"config\", \"buffalo-plugins.toml\")\n}\n\n// On checks for the existence of config/buffalo-plugins.toml if this\n// file exists its contents will be used to list plugins. If the file is not\n// found, then the BUFFALO_PLUGIN_PATH and ./plugins folders are consulted.\nfunc On(app meta.App) bool {\n\t_, err := os.Stat(ConfigPath(app))\n\treturn err == nil\n}\n"
  },
  {
    "path": "plugins/plugdeps/plugdeps_test.go",
    "content": "package plugdeps\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/internal/meta\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar heroku = Plugin{\n\tBinary: \"buffalo-heroku\",\n\tGoGet:  \"github.com/gobuffalo/buffalo-heroku\",\n\tCommands: []Command{\n\t\t{Name: \"deploy\", Flags: []string{\"-v\"}},\n\t},\n\tTags: []string{\"foo\", \"bar\"},\n}\n\nvar local = Plugin{\n\tBinary: \"buffalo-hello.rb\",\n\tLocal:  \"./plugins/buffalo-hello.rb\",\n}\n\nfunc Test_ConfigPath(t *testing.T) {\n\tr := require.New(t)\n\n\tx := ConfigPath(meta.App{Root: \"foo\"})\n\tr.Equal(x, filepath.Join(\"foo\", \"config\", \"buffalo-plugins.toml\"))\n}\n\nfunc Test_List_Off(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := meta.App{}\n\tplugs, err := List(app)\n\tr.Error(err)\n\tr.True(errors.Is(err, ErrMissingConfig))\n\tr.Len(plugs.List(), 0)\n}\n\nfunc Test_List_On(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := meta.New(os.TempDir())\n\n\tp := ConfigPath(app)\n\tr.NoError(os.MkdirAll(filepath.Dir(p), 0755))\n\tf, err := os.Create(p)\n\tr.NoError(err)\n\tf.WriteString(eToml)\n\tr.NoError(f.Close())\n\n\tplugs, err := List(app)\n\tr.NoError(err)\n\tr.Len(plugs.List(), 3)\n}\n\nconst eToml = `[[plugin]]\n  binary = \"buffalo-hello.rb\"\n  local = \"./plugins/buffalo-hello.rb\"\n\n[[plugin]]\n  binary = \"buffalo-heroku\"\n  go_get = \"github.com/gobuffalo/buffalo-heroku\"\n  tags = [\"foo\", \"bar\"]\n\n  [[plugin.command]]\n    name = \"deploy\"\n    flags = [\"-v\"]\n\n[[plugin]]\n  binary = \"buffalo-pop\"\n  go_get = \"github.com/gobuffalo/buffalo-pop/v2\"\n`\n"
  },
  {
    "path": "plugins/plugdeps/plugin.go",
    "content": "package plugdeps\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/gobuffalo/buffalo/internal/meta\"\n)\n\n// Plugin represents a Go plugin for Buffalo applications\ntype Plugin struct {\n\tBinary   string         `toml:\"binary\" json:\"binary\"`\n\tGoGet    string         `toml:\"go_get,omitempty\" json:\"go_get,omitempty\"`\n\tLocal    string         `toml:\"local,omitempty\" json:\"local,omitempty\"`\n\tCommands []Command      `toml:\"command,omitempty\" json:\"commands,omitempty\"`\n\tTags     meta.BuildTags `toml:\"tags,omitempty\" json:\"tags,omitempty\"`\n}\n\n// String implementation of fmt.Stringer\nfunc (p Plugin) String() string {\n\tb, _ := json.Marshal(p)\n\treturn string(b)\n}\n\nfunc (p Plugin) key() string {\n\treturn p.Binary + p.GoGet + p.Local\n}\n"
  },
  {
    "path": "plugins/plugdeps/plugin_test.go",
    "content": "package plugdeps\n"
  },
  {
    "path": "plugins/plugdeps/plugins.go",
    "content": "package plugdeps\n\nimport (\n\t\"io\"\n\t\"sort\"\n\t\"sync\"\n\n\t\"github.com/BurntSushi/toml\"\n)\n\n// Plugins manages the config/buffalo-plugins.toml file\n// as well as the plugins available from the file.\ntype Plugins struct {\n\tplugins map[string]Plugin\n\tmoot    *sync.RWMutex\n}\n\n// Encode the list of plugins, in TOML format, to the reader\nfunc (plugs *Plugins) Encode(w io.Writer) error {\n\ttp := tomlPlugins{\n\t\tPlugins: plugs.List(),\n\t}\n\n\tif err := toml.NewEncoder(w).Encode(tp); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Decode the list of plugins, in TOML format, from the reader\nfunc (plugs *Plugins) Decode(r io.Reader) error {\n\ttp := &tomlPlugins{\n\t\tPlugins: []Plugin{},\n\t}\n\tif _, err := toml.NewDecoder(r).Decode(tp); err != nil {\n\t\treturn err\n\t}\n\tfor _, p := range tp.Plugins {\n\t\tplugs.Add(p)\n\t}\n\treturn nil\n}\n\n// List of dependent plugins listed in order of Plugin.String()\nfunc (plugs *Plugins) List() []Plugin {\n\tm := map[string]Plugin{}\n\tplugs.moot.RLock()\n\tfor _, p := range plugs.plugins {\n\t\tm[p.key()] = p\n\t}\n\tplugs.moot.RUnlock()\n\tvar pp []Plugin\n\tfor _, v := range m {\n\t\tpp = append(pp, v)\n\t}\n\tsort.Slice(pp, func(a, b int) bool {\n\t\treturn pp[a].Binary < pp[b].Binary\n\t})\n\treturn pp\n}\n\n// Add plugin(s) to the list of dependencies\nfunc (plugs *Plugins) Add(pp ...Plugin) {\n\tplugs.moot.Lock()\n\tfor _, p := range pp {\n\t\tplugs.plugins[p.key()] = p\n\t}\n\tplugs.moot.Unlock()\n}\n\n// Remove plugin(s) from the list of dependencies\nfunc (plugs *Plugins) Remove(pp ...Plugin) {\n\tplugs.moot.Lock()\n\tfor _, p := range pp {\n\t\tdelete(plugs.plugins, p.key())\n\t}\n\tplugs.moot.Unlock()\n}\n\n// New returns a configured *Plugins value\nfunc New() *Plugins {\n\tplugs := &Plugins{\n\t\tplugins: map[string]Plugin{},\n\t\tmoot:    &sync.RWMutex{},\n\t}\n\treturn plugs\n}\n\ntype tomlPlugins struct {\n\tPlugins []Plugin `toml:\"plugin\"`\n}\n"
  },
  {
    "path": "plugins/plugdeps/plugins_test.go",
    "content": "package plugdeps\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Plugins_Encode(t *testing.T) {\n\tr := require.New(t)\n\n\tbb := &bytes.Buffer{}\n\n\tplugs := New()\n\tplugs.Add(pop, heroku, local)\n\n\tr.NoError(plugs.Encode(bb))\n\n\tfmt.Println(bb.String())\n\tact := strings.TrimSpace(bb.String())\n\texp := strings.TrimSpace(eToml)\n\tr.Equal(exp, act)\n}\n\nfunc Test_Plugins_Decode(t *testing.T) {\n\tr := require.New(t)\n\n\tplugs := New()\n\tr.NoError(plugs.Decode(strings.NewReader(eToml)))\n\n\tnames := []string{\"buffalo-hello.rb\", \"buffalo-heroku\", \"buffalo-pop\"}\n\tlist := plugs.List()\n\n\tr.Len(list, len(names))\n\tfor i, p := range list {\n\t\tr.Equal(names[i], p.Binary)\n\t}\n}\n\nfunc Test_Plugins_Remove(t *testing.T) {\n\tr := require.New(t)\n\n\tplugs := New()\n\tplugs.Add(pop, heroku)\n\tr.Len(plugs.List(), 2)\n\tplugs.Remove(pop)\n\tr.Len(plugs.List(), 1)\n}\n"
  },
  {
    "path": "plugins/plugdeps/pop.go",
    "content": "package plugdeps\n\nvar pop = Plugin{\n\tBinary: \"buffalo-pop\",\n\tGoGet:  \"github.com/gobuffalo/buffalo-pop/v2\",\n}\n"
  },
  {
    "path": "plugins/plugins.go",
    "content": "package plugins\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gobuffalo/buffalo/internal/meta\"\n\t\"github.com/gobuffalo/buffalo/plugins/plugdeps\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nconst timeoutEnv = \"BUFFALO_PLUGIN_TIMEOUT\"\n\nvar availableOnce sync.Once\n\nvar timeout = sync.OnceValue(func() time.Duration {\n\tt := time.Second * 2\n\trawTimeout, err := env.MustGet(timeoutEnv)\n\tif err == nil {\n\t\tif parsed, err := time.ParseDuration(rawTimeout); err == nil {\n\t\t\tt = parsed\n\t\t} else {\n\t\t\tlogrus.Errorf(\"%q value is malformed assuming default %q: %v\", timeoutEnv, t, err)\n\t\t}\n\t} else {\n\t\tlogrus.Debugf(\"%q not set, assuming default of %v\", timeoutEnv, t)\n\t}\n\treturn t\n})\n\n// List maps a Buffalo command to a slice of Command\ntype List map[string]Commands\n\nvar _list List\n\n// Available plugins for the `buffalo` command.\n// It will look in $GOPATH/bin and the `./plugins` directory.\n// This can be changed by setting the $BUFFALO_PLUGIN_PATH\n// environment variable.\n//\n// Requirements:\n//   - file/command must be executable\n//   - file/command must start with `buffalo-`\n//   - file/command must respond to `available` and return JSON of\n//     plugins.Commands{}\n//\n// # Limit full path scan with direct plugin path\n//\n// If a file/command doesn't respond to being invoked with `available`\n// within one second, buffalo will assume that it is unable to load. This\n// can be changed by setting the $BUFFALO_PLUGIN_TIMEOUT environment\n// variable. It must be set to a duration that `time.ParseDuration` can\n// process.\nfunc Available() (List, error) {\n\tvar err error\n\tavailableOnce.Do(func() {\n\t\tdefer func() {\n\t\t\tif err := saveCache(); err != nil {\n\t\t\t\tlogrus.Error(err)\n\t\t\t}\n\t\t}()\n\n\t\tapp := meta.New(\".\")\n\n\t\tif plugdeps.On(app) {\n\t\t\t_list, err = listPlugDeps(app)\n\t\t\treturn\n\t\t}\n\n\t\tpaths := []string{\"plugins\"}\n\n\t\tfrom, err := env.MustGet(\"BUFFALO_PLUGIN_PATH\")\n\t\tif err != nil {\n\t\t\tfrom, err = env.MustGet(\"GOPATH\")\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfrom = filepath.Join(from, \"bin\")\n\t\t}\n\n\t\tpaths = append(paths, strings.Split(from, string(os.PathListSeparator))...)\n\n\t\tlist := List{}\n\t\tfor _, p := range paths {\n\t\t\tif ignorePath(p) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, err := os.Stat(p); err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\terr := filepath.WalkDir(p, func(path string, d fs.DirEntry, err error) error {\n\t\t\t\tif err != nil {\n\t\t\t\t\t// May indicate a permissions problem with the path, skip it\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif d.IsDir() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif !strings.HasPrefix(d.Name(), \"buffalo-\") {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif strings.HasPrefix(d.Name(), \"buffalo-plugins\") {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), timeout())\n\t\t\t\tcommands := askBin(ctx, path)\n\t\t\t\tcancel()\n\t\t\t\tfor _, c := range commands {\n\t\t\t\t\tbc := c.BuffaloCommand\n\t\t\t\t\tif _, ok := list[bc]; !ok {\n\t\t\t\t\t\tlist[bc] = Commands{}\n\t\t\t\t\t}\n\t\t\t\t\tc.Binary = path\n\t\t\t\t\tlist[bc] = append(list[bc], c)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\t_list = list\n\t})\n\treturn _list, err\n}\n\nfunc askBin(ctx context.Context, path string) Commands {\n\tstart := time.Now()\n\tdefer func() {\n\t\tlogrus.Debugf(\"askBin %s=%.4f s\", path, time.Since(start).Seconds())\n\t}()\n\n\tcommands := Commands{}\n\tif cp, ok := findInCache(path); ok {\n\t\ts := sum(path)\n\t\tif s == cp.CheckSum {\n\t\t\tlogrus.Debugf(\"cache hit: %s\", path)\n\t\t\tcommands = cp.Commands\n\t\t\treturn commands\n\t\t}\n\t}\n\tlogrus.Debugf(\"cache miss: %s\", path)\n\tif strings.HasPrefix(filepath.Base(path), \"buffalo-no-sqlite\") {\n\t\treturn commands\n\t}\n\n\tcmd := exec.CommandContext(ctx, path, \"available\")\n\tbb := &bytes.Buffer{}\n\tcmd.Stdout = bb\n\terr := cmd.Run()\n\tif err != nil {\n\t\tlogrus.Errorf(\"[PLUGIN] error loading plugin %s: %s\\n\", path, err)\n\t\treturn commands\n\t}\n\n\tmsg := bb.String()\n\tfor len(msg) > 0 {\n\t\terr = json.NewDecoder(strings.NewReader(msg)).Decode(&commands)\n\t\tif err == nil {\n\t\t\taddToCache(path, cachedPlugin{\n\t\t\t\tCommands: commands,\n\t\t\t})\n\t\t\treturn commands\n\t\t}\n\t\tmsg = msg[1:]\n\t}\n\tlogrus.Errorf(\"[PLUGIN] error decoding plugin %s: %s\\n%s\\n\", path, err, msg)\n\treturn commands\n}\n\nfunc ignorePath(p string) bool {\n\tp = strings.ToLower(p)\n\tfor _, x := range []string{`c:\\windows`, `c:\\program`} {\n\t\tif strings.HasPrefix(p, x) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc listPlugDeps(app meta.App) (List, error) {\n\tlist := List{}\n\tplugs, err := plugdeps.List(app)\n\tif err != nil {\n\t\treturn list, err\n\t}\n\tfor _, p := range plugs.List() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), timeout())\n\t\tdefer cancel()\n\t\tbin := p.Binary\n\t\tif len(p.Local) != 0 {\n\t\t\tbin = p.Local\n\t\t}\n\t\tbin, err := LookPath(bin)\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, ErrPlugMissing) {\n\t\t\t\treturn list, err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tcommands := askBin(ctx, bin)\n\t\tcancel()\n\t\tfor _, c := range commands {\n\t\t\tbc := c.BuffaloCommand\n\t\t\tif _, ok := list[bc]; !ok {\n\t\t\t\tlist[bc] = Commands{}\n\t\t\t}\n\t\t\tc.Binary = p.Binary\n\t\t\tfor _, pc := range p.Commands {\n\t\t\t\tif c.Name == pc.Name {\n\t\t\t\t\tc.Flags = pc.Flags\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tlist[bc] = append(list[bc], c)\n\t\t}\n\t}\n\treturn list, nil\n}\n"
  },
  {
    "path": "plugins/plugins_test.go",
    "content": "package plugins\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAskBin_respectsTimeout(t *testing.T) {\n\tr := require.New(t)\n\n\tfrom, err := env.MustGet(\"BUFFALO_PLUGIN_PATH\")\n\tif err != nil {\n\t\tt.Skipf(\"BUFFALO_PLUGIN_PATH not set.\")\n\t\treturn\n\t}\n\n\tif fileEntries, err := os.ReadDir(from); err == nil {\n\t\tfound := false\n\t\tfor _, e := range fileEntries {\n\t\t\tif strings.HasPrefix(e.Name(), \"buffalo-\") {\n\t\t\t\tfrom = e.Name()\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tt.Skipf(\"no plugins found\")\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tr.Error(err, \"plugin path not able to be read\")\n\t\treturn\n\t}\n\n\tconst tooShort = time.Millisecond\n\timpossible, cancel := context.WithTimeout(context.Background(), tooShort)\n\tdefer cancel()\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\taskBin(impossible, from)\n\t\tclose(done)\n\t}()\n\n\tselect {\n\tcase <-time.After(tooShort + 80*time.Millisecond):\n\t\tr.Fail(\"did not time-out quickly enough\")\n\tcase <-done:\n\t\tt.Log(\"timed-out successfully\")\n\t}\n\n\tif _, ok := findInCache(from); ok {\n\t\tr.Fail(\"expected plugin not to be added to cache on failure, but it was in cache\")\n\t}\n}\n"
  },
  {
    "path": "plugins.go",
    "content": "package buffalo\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gobuffalo/buffalo/plugins\"\n\t\"github.com/gobuffalo/events\"\n)\n\n// LoadPlugins will add listeners for any plugins that support \"events\"\nvar LoadPlugins = sync.OnceValue(func() error {\n\t// don't send plugins events during testing\n\tif env.Get(\"GO_ENV\", \"development\") == \"test\" {\n\t\treturn nil\n\t}\n\tplugs, err := plugins.Available()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, cmds := range plugs {\n\t\tfor _, c := range cmds {\n\t\t\tif c.BuffaloCommand != \"events\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr := func(c plugins.Command) error {\n\t\t\t\tn := fmt.Sprintf(\"[PLUGIN] %s %s\", c.Binary, c.Name)\n\t\t\t\tfn := func(e events.Event) {\n\t\t\t\t\tb, err := json.Marshal(e)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"error trying to marshal event\", e, err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tcmd := exec.Command(c.Binary, c.UseCommand, string(b))\n\t\t\t\t\tcmd.Stderr = os.Stderr\n\t\t\t\t\tcmd.Stdout = os.Stdout\n\t\t\t\t\tcmd.Stdin = os.Stdin\n\t\t\t\t\tif err := cmd.Run(); err != nil {\n\t\t\t\t\t\tfmt.Println(\"error trying to send event\", strings.Join(cmd.Args, \" \"), err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_, err := events.NamedListen(n, events.Filter(c.ListenFor, fn))\n\t\t\t\treturn err\n\t\t\t}(c)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n})\n"
  },
  {
    "path": "render/auto.go",
    "content": "package render\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"path\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/flect/name\"\n)\n\nvar errNoID = fmt.Errorf(\"no ID on model\")\n\n// ErrRedirect indicates to Context#Render that this is a\n// redirect and a template shouldn't be rendered.\ntype ErrRedirect struct {\n\tStatus int\n\tURL    string\n}\n\nfunc (ErrRedirect) Error() string {\n\treturn \"\"\n}\n\n// Auto figures out how to render the model based information\n// about the request and the name of the model. Auto supports\n// automatic rendering of HTML, JSON, and XML. Any status code\n// give to Context#Render between 300 - 400 will be respected\n// by Auto. Other status codes are not.\n/*\n# Rules for HTML template lookup:\nGET /users - users/index.html\nGET /users/id - users/show.html\nGET /users/new - users/new.html\nGET /users/id/edit - users/edit.html\nPOST /users - (redirect to /users/id or render user/new.html)\nPUT /users/edit - (redirect to /users/id or render user/edit.html)\nDELETE /users/id - redirect to /users\n*/\nfunc Auto(ctx context.Context, i any) Renderer {\n\te := New(Options{})\n\treturn e.Auto(ctx, i)\n}\n\n// Auto figures out how to render the model based information\n// about the request and the name of the model. Auto supports\n// automatic rendering of HTML, JSON, and XML. Any status code\n// give to Context#Render between 300 - 400 will be respected\n// by Auto. Other status codes are not.\n/*\n# Rules for HTML template lookup:\nGET /users - users/index.html\nGET /users/id - users/show.html\nGET /users/new - users/new.html\nGET /users/id/edit - users/edit.html\nPOST /users - (redirect to /users/id or render user/new.html)\nPUT /users/edit - (redirect to /users/id or render user/edit.html)\nDELETE /users/id - redirect to /users\n*/\nfunc (e *Engine) Auto(ctx context.Context, i any) Renderer {\n\tct, _ := ctx.Value(\"contentType\").(string)\n\tif ct == \"\" {\n\t\tct = e.DefaultContentType\n\t}\n\tct = strings.TrimSpace(strings.ToLower(ct))\n\n\tif strings.Contains(ct, \"json\") {\n\t\treturn e.JSON(i)\n\t}\n\n\tif strings.Contains(ct, \"xml\") {\n\t\treturn e.XML(i)\n\t}\n\n\treturn htmlAutoRenderer{\n\t\tEngine: e,\n\t\tmodel:  i,\n\t}\n}\n\ntype htmlAutoRenderer struct {\n\t*Engine\n\tmodel any\n}\n\nfunc (htmlAutoRenderer) ContentType() string {\n\treturn \"text/html\"\n}\n\nfunc (ir htmlAutoRenderer) Render(w io.Writer, data Data) error {\n\tn := name.New(ir.typeName())\n\tpname := name.New(n.Pluralize().String())\n\tisPlural := ir.isPlural()\n\n\tif isPlural {\n\t\tdata[pname.VarCasePlural().String()] = ir.model\n\t} else {\n\t\tdata[n.VarCaseSingle().String()] = ir.model\n\t}\n\n\ttemplatePrefix := pname.File()\n\tif pf, ok := data[\"template_prefix\"].(string); ok {\n\t\ttemplatePrefix = name.New(pf)\n\t}\n\n\tswitch data[\"method\"] {\n\tcase \"PUT\", \"POST\", \"DELETE\":\n\t\tif err := ir.redirect(pname, w, data); err != nil {\n\t\t\tvar er ErrRedirect\n\t\t\tif errors.As(err, &er) && er.Status >= http.StatusMultipleChoices && er.Status < http.StatusBadRequest {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif data[\"method\"] == \"PUT\" {\n\t\t\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"edit.html\")).Render(w, data)\n\t\t\t}\n\n\t\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"new.html\")).Render(w, data)\n\t\t}\n\t\treturn nil\n\t}\n\n\tcp, ok := data[\"current_path\"].(string)\n\n\tdefCase := func() error {\n\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"index.html\")).Render(w, data)\n\t}\n\n\tif !ok {\n\t\treturn defCase()\n\t}\n\n\tif strings.HasSuffix(cp, \"/edit/\") {\n\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"edit.html\")).Render(w, data)\n\t}\n\n\tif strings.HasSuffix(cp, \"/new/\") {\n\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"new.html\")).Render(w, data)\n\t}\n\n\tif !isPlural {\n\t\treturn ir.HTML(path.Join(templatePrefix.String(), \"show.html\")).Render(w, data)\n\t}\n\n\treturn defCase()\n}\n\nfunc (ir htmlAutoRenderer) redirect(name name.Ident, w io.Writer, data Data) error {\n\trv := reflect.Indirect(reflect.ValueOf(ir.model))\n\tf := rv.FieldByName(\"ID\")\n\tif !f.IsValid() {\n\t\treturn errNoID\n\t}\n\n\tfi := f.Interface()\n\trt := reflect.TypeOf(fi)\n\tzero := reflect.Zero(rt)\n\tif fi != zero.Interface() {\n\t\tm, ok := data[\"method\"].(string)\n\t\tif !ok {\n\t\t\tm = \"GET\"\n\t\t}\n\t\turl := fmt.Sprint(data[\"current_path\"])\n\t\tid := fmt.Sprint(f.Interface())\n\t\turl = strings.TrimSuffix(url, \"/\")\n\t\tswitch m {\n\t\tcase \"DELETE\":\n\t\t\turl = strings.TrimSuffix(url, id)\n\t\tdefault:\n\t\t\tif !strings.HasSuffix(url, id) {\n\t\t\t\turl = path.Join(url, id)\n\t\t\t}\n\t\t}\n\n\t\tcode := http.StatusFound\n\t\tif i, ok := data[\"status\"].(int); ok {\n\t\t\tif i >= http.StatusMultipleChoices {\n\t\t\t\tcode = i\n\t\t\t}\n\t\t}\n\t\treturn ErrRedirect{\n\t\t\tStatus: code,\n\t\t\tURL:    url,\n\t\t}\n\t}\n\treturn errNoID\n}\n\nfunc (ir htmlAutoRenderer) typeName() string {\n\trv := reflect.Indirect(reflect.ValueOf(ir.model))\n\trt := rv.Type()\n\tswitch rt.Kind() {\n\tcase reflect.Slice, reflect.Array:\n\t\tel := rt.Elem()\n\t\treturn el.Name()\n\tdefault:\n\t\treturn rt.Name()\n\t}\n}\n\nfunc (ir htmlAutoRenderer) isPlural() bool {\n\trv := reflect.Indirect(reflect.ValueOf(ir.model))\n\trt := rv.Type()\n\tswitch rt.Kind() {\n\tcase reflect.Slice, reflect.Array, reflect.Map:\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "render/auto_test.go",
    "content": "package render_test\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/gobuffalo/buffalo\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype Car struct {\n\tID   int\n\tName string\n}\n\ntype Cars []Car\n\nfunc Test_Auto_DefaultContentType(t *testing.T) {\n\tr := require.New(t)\n\n\tre := render.New(render.Options{\n\t\tDefaultContentType: \"application/json\",\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, []string{\"Honda\", \"Toyota\", \"Ford\", \"Chevy\"}))\n\t})\n\n\tres := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/cars\", nil)\n\tapp.ServeHTTP(res, req)\n\n\tr.Equal(`[\"Honda\",\"Toyota\",\"Ford\",\"Chevy\"]`, strings.TrimSpace(res.Body.String()))\n}\n\nfunc Test_Auto_JSON(t *testing.T) {\n\tr := require.New(t)\n\n\tre := render.New(render.Options{})\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, []string{\"Honda\", \"Toyota\", \"Ford\", \"Chevy\"}))\n\t})\n\n\tw := httptest.New(app)\n\n\tres := w.JSON(\"/cars\").Get()\n\tr.Equal(`[\"Honda\",\"Toyota\",\"Ford\",\"Chevy\"]`, strings.TrimSpace(res.Body.String()))\n}\n\nfunc Test_Auto_XML(t *testing.T) {\n\tr := require.New(t)\n\n\tre := render.New(render.Options{})\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, []string{\"Honda\", \"Toyota\", \"Ford\", \"Chevy\"}))\n\t})\n\n\tw := httptest.New(app)\n\n\tres := w.XML(\"/cars\").Get()\n\tr.Equal(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<string>Honda</string>\\n<string>Toyota</string>\\n<string>Ford</string>\\n<string>Chevy</string>\", strings.TrimSpace(res.Body.String()))\n}\n\nfunc Test_Auto_HTML_List(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"INDEX: <%= len(cars) %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.NewEngine()\n\tre.TemplatesFS = rootFS\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, Cars{\n\t\t\t{Name: \"Ford\"},\n\t\t\t{Name: \"Chevy\"},\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars\").Get()\n\n\tr.Contains(res.Body.String(), \"INDEX: 2\")\n}\n\nfunc Test_Auto_HTML_List_Plural(t *testing.T) {\n\tr := require.New(t)\n\n\ttype Person struct {\n\t\tName string\n\t}\n\n\ttype People []Person\n\n\trootFS := fstest.MapFS{\n\t\t\"people/index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"INDEX: <%= len(people) %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/people\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, People{\n\t\t\tPerson{Name: \"Ford\"},\n\t\t\tPerson{Name: \"Chevy\"},\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/people\").Get()\n\n\tr.Contains(res.Body.String(), \"INDEX: 2\")\n}\n\nfunc Test_Auto_HTML_Show(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/show.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"Show: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars/{id}\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, Car{Name: \"Honda\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1\").Get()\n\tr.Contains(res.Body.String(), \"Show: Honda\")\n}\n\nfunc Test_Auto_HTML_New(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/new.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"New: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars/new\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, Car{Name: \"Honda\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/new\").Get()\n\tr.Contains(res.Body.String(), \"New: Honda\")\n}\n\nfunc Test_Auto_HTML_Create(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/new.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"New: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.POST(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusCreated, re.Auto(c, Car{Name: \"Honda\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars\").Post(nil)\n\tr.Contains(res.Body.String(), \"New: Honda\")\n}\n\nfunc Test_Auto_HTML_Create_Redirect(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.POST(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusCreated, render.Auto(c, Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars\").Post(nil)\n\tr.Equal(\"/cars/1\", res.Location())\n\tr.Equal(http.StatusFound, res.Code)\n}\n\nfunc Test_Auto_HTML_Create_Redirect_Error(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/new.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"Create: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.POST(\"/cars\", func(c buffalo.Context) error {\n\t\tb := Car{\n\t\t\tName: \"Honda\",\n\t\t}\n\t\treturn c.Render(http.StatusUnprocessableEntity, re.Auto(c, b))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars\").Post(nil)\n\tr.Equal(http.StatusUnprocessableEntity, res.Code)\n\tr.Contains(res.Body.String(), \"Create: Honda\")\n}\n\nfunc Test_Auto_HTML_Create_Nested_Redirect(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := buffalo.New(buffalo.Options{})\n\tadmin := app.Group(\"/admin\")\n\tadmin.POST(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusCreated, render.Auto(c, Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/admin/cars\").Post(nil)\n\tr.Equal(\"/admin/cars/1\", res.Location())\n\tr.Equal(http.StatusFound, res.Code)\n}\n\nfunc Test_Auto_HTML_Destroy_Nested_Redirect(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := buffalo.New(buffalo.Options{})\n\tadmin := app.Group(\"/admin\")\n\tadmin.DELETE(\"/cars\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, render.Auto(c, Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/admin/cars\").Delete()\n\tr.Equal(\"/admin/cars\", res.Location())\n\tr.Equal(http.StatusFound, res.Code)\n}\n\nfunc Test_Auto_HTML_Edit(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/edit.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"Edit: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/cars/{id}/edit\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, Car{Name: \"Honda\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1/edit\").Get()\n\tr.Contains(res.Body.String(), \"Edit: Honda\")\n}\n\nfunc Test_Auto_HTML_Update(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/edit.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"Update: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.PUT(\"/cars/{id}\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, Car{Name: \"Honda\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1\").Put(nil)\n\n\tr.Contains(res.Body.String(), \"Update: Honda\")\n}\n\nfunc Test_Auto_HTML_Update_Redirect(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.PUT(\"/cars/{id}\", func(c buffalo.Context) error {\n\t\tb := Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}\n\t\treturn c.Render(http.StatusOK, render.Auto(c, b))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1\").Put(nil)\n\tr.Equal(\"/cars/1\", res.Location())\n\tr.Equal(http.StatusFound, res.Code)\n}\n\nfunc Test_Auto_HTML_Update_Redirect_Error(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"cars/edit.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"Update: <%= car.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.PUT(\"/cars/{id}\", func(c buffalo.Context) error {\n\t\tb := Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}\n\t\treturn c.Render(http.StatusUnprocessableEntity, re.Auto(c, b))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1\").Put(nil)\n\tr.Equal(http.StatusUnprocessableEntity, res.Code)\n\tr.Contains(res.Body.String(), \"Update: Honda\")\n}\n\nfunc Test_Auto_HTML_Destroy_Redirect(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.DELETE(\"/cars/{id}\", func(c buffalo.Context) error {\n\t\tb := Car{\n\t\t\tID:   1,\n\t\t\tName: \"Honda\",\n\t\t}\n\t\treturn c.Render(http.StatusOK, render.Auto(c, b))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/cars/1\").Delete()\n\tr.Equal(\"/cars/\", res.Location())\n\tr.Equal(http.StatusFound, res.Code)\n}\n\nfunc Test_Auto_HTML_List_Plural_MultiWord(t *testing.T) {\n\tr := require.New(t)\n\n\ttype RoomProvider struct {\n\t\tName string\n\t}\n\n\ttype RoomProviders []RoomProvider\n\n\trootFS := fstest.MapFS{\n\t\t\"room_providers/index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"INDEX: <%= len(roomProviders) %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/room_providers\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, RoomProviders{\n\t\t\tRoomProvider{Name: \"Ford\"},\n\t\t\tRoomProvider{Name: \"Chevy\"},\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/room_providers\").Get()\n\n\tr.Contains(res.Body.String(), \"INDEX: 2\")\n}\n\nfunc Test_Auto_HTML_List_Plural_MultiWord_Dashed(t *testing.T) {\n\tr := require.New(t)\n\n\ttype RoomProvider struct {\n\t\tName string\n\t}\n\n\ttype RoomProviders []RoomProvider\n\n\trootFS := fstest.MapFS{\n\t\t\"room_providers/index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"INDEX: <%= len(roomProviders) %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/room-providers\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, RoomProviders{\n\t\t\tRoomProvider{Name: \"Ford\"},\n\t\t\tRoomProvider{Name: \"Chevy\"},\n\t\t}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/room-providers\").Get()\n\n\tr.Contains(res.Body.String(), \"INDEX: 2\")\n}\n\nfunc Test_Auto_HTML_Show_MultiWord_Dashed(t *testing.T) {\n\tr := require.New(t)\n\n\ttype RoomProvider struct {\n\t\tID   int\n\t\tName string\n\t}\n\n\trootFS := fstest.MapFS{\n\t\t\"room_providers/show.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"SHOW: <%= roomProvider.Name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\tre := render.New(render.Options{\n\t\tTemplatesFS: rootFS,\n\t})\n\n\tapp := buffalo.New(buffalo.Options{})\n\tapp.GET(\"/room-providers/{id}\", func(c buffalo.Context) error {\n\t\treturn c.Render(http.StatusOK, re.Auto(c, RoomProvider{ID: 1, Name: \"Ford\"}))\n\t})\n\n\tw := httptest.New(app)\n\tres := w.HTML(\"/room-providers/1\").Get()\n\n\tr.Contains(res.Body.String(), \"SHOW: Ford\")\n}\n"
  },
  {
    "path": "render/download.go",
    "content": "package render\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strconv\"\n)\n\ntype downloadRenderer struct {\n\tctx    context.Context\n\tname   string\n\treader io.Reader\n}\n\nfunc (r downloadRenderer) ContentType() string {\n\text := filepath.Ext(r.name)\n\tt := mime.TypeByExtension(ext)\n\tif t == \"\" {\n\t\tt = \"application/octet-stream\"\n\t}\n\n\treturn t\n}\n\nfunc (r downloadRenderer) Render(w io.Writer, d Data) error {\n\twritten, err := io.Copy(w, r.reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, ok := r.ctx.(responsible)\n\tif !ok {\n\t\treturn fmt.Errorf(\"context has no response writer\")\n\t}\n\n\theader := ctx.Response().Header()\n\tdisposition := fmt.Sprintf(\"attachment; filename=%s\", r.name)\n\theader.Add(\"Content-Disposition\", disposition)\n\tcontentLength := strconv.Itoa(int(written))\n\theader.Add(\"Content-Length\", contentLength)\n\n\treturn nil\n}\n\n// Download renders a file attachment automatically setting following headers:\n//\n//\tContent-Type\n//\tContent-Length\n//\tContent-Disposition\n//\n// Content-Type is set using mime#TypeByExtension with the filename's extension. Content-Type will default to\n// application/octet-stream if using a filename with an unknown extension.\n//\n// Note: the purpose of this function is not serving static files but to support\n// downloading of dynamically genrated data as a file. For example, you can use\n// this function when you implement CSV file download feature for the result of\n// a database query.\n//\n// Do not use this function for large io.Reader. It could cause out of memory if\n// the size of io.Reader is too big.\nfunc Download(ctx context.Context, name string, r io.Reader) Renderer {\n\treturn downloadRenderer{\n\t\tctx:    ctx,\n\t\tname:   name,\n\t\treader: r,\n\t}\n}\n\n// Download renders a file attachment automatically setting following headers:\n//\n//\tContent-Type\n//\tContent-Length\n//\tContent-Disposition\n//\n// Content-Type is set using mime#TypeByExtension with the filename's extension. Content-Type will default to\n// application/octet-stream if using a filename with an unknown extension.\n//\n// Note: the purpose of this method is not serving static files but to support\n// downloading of dynamically genrated data as a file. For example, you can use\n// this method when you implement CSV file download feature for the result of\n// a database query.\n//\n// Do not use this method for large io.Reader. It could cause out of memory if\n// the size of io.Reader is too big.\nfunc (e *Engine) Download(ctx context.Context, name string, r io.Reader) Renderer {\n\treturn Download(ctx, name, r)\n}\n\ntype responsible interface {\n\tResponse() http.ResponseWriter\n}\n"
  },
  {
    "path": "render/download_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype dlRenderer func(context.Context, string, io.Reader) Renderer\n\ntype dlContext struct {\n\tcontext.Context\n\trw http.ResponseWriter\n}\n\nfunc (c dlContext) Response() http.ResponseWriter {\n\treturn c.rw\n}\n\nvar data = []byte(\"data\")\n\nfunc Test_Download_KnownExtension(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []dlRenderer{\n\t\tDownload,\n\t\tNew(Options{}).Download,\n\t}\n\n\tfor _, dl := range table {\n\t\tctx := dlContext{rw: httptest.NewRecorder()}\n\n\t\tre := dl(ctx, \"filename.pdf\", bytes.NewReader(data))\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.NoError(re.Render(bb, nil))\n\n\t\tr.Equal(data, bb.Bytes())\n\t\tr.Equal(strconv.Itoa(len(data)), ctx.Response().Header().Get(\"Content-Length\"))\n\t\tr.Equal(\"attachment; filename=filename.pdf\", ctx.Response().Header().Get(\"Content-Disposition\"))\n\t\tr.Equal(\"application/pdf\", re.ContentType())\n\t}\n}\n\nfunc Test_Download_UnknownExtension(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []dlRenderer{\n\t\tDownload,\n\t\tNew(Options{}).Download,\n\t}\n\n\tfor _, dl := range table {\n\t\tctx := dlContext{rw: httptest.NewRecorder()}\n\t\tre := dl(ctx, \"filename\", bytes.NewReader(data))\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.NoError(re.Render(bb, nil))\n\n\t\tr.Equal(data, bb.Bytes())\n\t\tr.Equal(strconv.Itoa(len(data)), ctx.Response().Header().Get(\"Content-Length\"))\n\t\tr.Equal(\"attachment; filename=filename\", ctx.Response().Header().Get(\"Content-Disposition\"))\n\t\tr.Equal(\"application/octet-stream\", re.ContentType())\n\t}\n}\n\nfunc Test_InvalidContext(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []dlRenderer{\n\t\tDownload,\n\t\tNew(Options{}).Download,\n\t}\n\n\tfor _, dl := range table {\n\t\tre := dl(context.TODO(), \"filename\", bytes.NewReader(data))\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.Error(re.Render(bb, nil))\n\t}\n}\n"
  },
  {
    "path": "render/func.go",
    "content": "package render\n\nimport \"io\"\n\n// RendererFunc is the interface for the the function\n// needed by the Func renderer.\ntype RendererFunc func(io.Writer, Data) error\n\ntype funcRenderer struct {\n\tcontentType string\n\trenderFunc  RendererFunc\n}\n\n// ContentType returns the content type for this render.\n// Examples would be \"text/html\" or \"application/json\".\nfunc (s funcRenderer) ContentType() string {\n\treturn s.contentType\n}\n\n// Render the provided Data to the provider Writer using the\n// RendererFunc provide.\nfunc (s funcRenderer) Render(w io.Writer, data Data) error {\n\treturn s.renderFunc(w, data)\n}\n\n// Func renderer allows for easily building one of renderers\n// using just a RendererFunc and not having to build a whole\n// implementation of the Render interface.\nfunc Func(s string, fn RendererFunc) Renderer {\n\treturn funcRenderer{\n\t\tcontentType: s,\n\t\trenderFunc:  fn,\n\t}\n}\n\n// Func renderer allows for easily building one of renderers\n// using just a RendererFunc and not having to build a whole\n// implementation of the Render interface.\nfunc (e *Engine) Func(s string, fn RendererFunc) Renderer {\n\treturn Func(s, fn)\n}\n"
  },
  {
    "path": "render/func_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Func(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []rendFriend{\n\t\tFunc,\n\t\tNew(Options{}).Func,\n\t}\n\n\tfor _, tt := range table {\n\t\tbb := &bytes.Buffer{}\n\n\t\tre := tt(\"foo/bar\", func(w io.Writer, data Data) error {\n\t\t\t_, err := w.Write([]byte(data[\"name\"].(string)))\n\t\t\treturn err\n\t\t})\n\n\t\tr.Equal(\"foo/bar\", re.ContentType())\n\t\terr := re.Render(bb, Data{\"name\": \"Mark\"})\n\t\tr.NoError(err)\n\t\tr.Equal(\"Mark\", bb.String())\n\t}\n\n}\n"
  },
  {
    "path": "render/helpers.go",
    "content": "package render\n\nimport (\n\t\"html/template\"\n\t\"net/http\"\n\n\t\"github.com/gobuffalo/helpers/forms\"\n\t\"github.com/gobuffalo/helpers/forms/bootstrap\"\n\t\"github.com/gobuffalo/plush/v5\"\n\t\"github.com/gobuffalo/tags/v3\"\n)\n\nfunc init() {\n\tplush.Helpers.Add(\"paginator\", func(pagination any, opts map[string]any, help plush.HelperContext) (template.HTML, error) {\n\t\tif opts[\"path\"] == nil {\n\t\t\tif req, ok := help.Value(\"request\").(*http.Request); ok {\n\t\t\t\topts[\"path\"] = req.URL.String()\n\t\t\t}\n\t\t}\n\t\tt, err := tags.Pagination(pagination, opts)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn t.HTML(), nil\n\t})\n\tplush.Helpers.Add(forms.RemoteFormKey, bootstrap.RemoteForm)\n\tplush.Helpers.Add(forms.RemoteFormForKey, bootstrap.RemoteFormFor)\n}\n"
  },
  {
    "path": "render/html.go",
    "content": "package render\n\nimport (\n\t\"html\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/github_flavored_markdown\"\n\t\"github.com/gobuffalo/plush/v5\"\n)\n\n// HTML renders the named files using the 'text/html'\n// content type and the github.com/gobuffalo/plush/v5\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\".\nfunc HTML(names ...string) Renderer {\n\te := New(Options{})\n\treturn e.HTML(names...)\n}\n\n// HTML renders the named files using the 'text/html'\n// content type and the github.com/gobuffalo/plush/v5\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\". If no\n// second file is provided and an `HTMLLayout` is specified\n// in the options, then that layout file will be used\n// automatically.\nfunc (e *Engine) HTML(names ...string) Renderer {\n\t// just allow leading slash and remove them here.\n\t// generated actions were various by buffalo versions.\n\ttmp := []string{}\n\tfor _, name := range names {\n\t\ttmp = append(tmp, strings.TrimPrefix(name, \"/\"))\n\t}\n\tnames = tmp\n\n\tif e.HTMLLayout != \"\" && len(names) == 1 {\n\t\tnames = append(names, e.HTMLLayout)\n\t}\n\thr := &templateRenderer{\n\t\tEngine:      e,\n\t\tcontentType: \"text/html; charset=utf-8\",\n\t\tnames:       names,\n\t}\n\treturn hr\n}\n\n// MDTemplateEngine runs the input through github flavored markdown before sending it to the Plush engine.\nfunc MDTemplateEngine(input string, data map[string]any, helpers map[string]any) (string, error) {\n\tif ct, ok := data[\"contentType\"].(string); ok && ct == \"text/plain\" {\n\t\treturn plush.BuffaloRenderer(input, data, helpers)\n\t}\n\tsource := github_flavored_markdown.Markdown([]byte(input))\n\tsource = []byte(html.UnescapeString(string(source)))\n\treturn plush.BuffaloRenderer(string(source), data, helpers)\n}\n"
  },
  {
    "path": "render/html_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst htmlLayout = \"layout.html\"\nconst htmlAltLayout = \"alt_layout.plush.html\"\nconst htmlTemplate = \"my-template.html\"\n\nfunc Test_HTML_WithoutLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\th := e.HTML(htmlTemplate)\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"Mark\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_HTML_WithLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\thtmlLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"<body><%= yield %></body>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.HTMLLayout = htmlLayout\n\n\th := e.HTML(htmlTemplate)\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"<body>Mark</body>\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_HTML_WithLayout_Override(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\thtmlLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"<body><%= yield %></body>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\thtmlAltLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"<html><%= yield %></html>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.HTMLLayout = htmlLayout\n\n\th := e.HTML(htmlTemplate, htmlAltLayout)\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"<html>Mark</html>\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_HTML_LeadingSlash(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\thtmlLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"<body><%= yield %></body>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.HTMLLayout = htmlLayout\n\n\th := e.HTML(\"/my-template.html\") // instead of \"my-template.html\"\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"<body>Mark</body>\", strings.TrimSpace(bb.String()))\n}\n"
  },
  {
    "path": "render/js.go",
    "content": "package render\n\n// JavaScript renders the named files using the 'application/javascript'\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\".\nfunc JavaScript(names ...string) Renderer {\n\te := New(Options{})\n\treturn e.JavaScript(names...)\n}\n\n// JavaScript renders the named files using the 'application/javascript'\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\". If no\n// second file is provided and an `JavaScriptLayout` is specified\n// in the options, then that layout file will be used\n// automatically.\nfunc (e *Engine) JavaScript(names ...string) Renderer {\n\tif e.JavaScriptLayout != \"\" && len(names) == 1 {\n\t\tnames = append(names, e.JavaScriptLayout)\n\t}\n\thr := &templateRenderer{\n\t\tEngine:      e,\n\t\tcontentType: \"application/javascript\",\n\t\tnames:       names,\n\t}\n\treturn hr\n}\n"
  },
  {
    "path": "render/js_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst jsLayout = \"layout.js\"\nconst jsAltLayout = \"alt_layout.plush.js\"\nconst jsTemplate = \"my-template.js\"\n\nfunc Test_JavaScript_WithoutLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"alert(<%= name %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\th := e.JavaScript(jsTemplate)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"alert(Mark)\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_JavaScript_WithLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"alert(<%= name %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\tjsLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"$(<%= yield %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.JavaScriptLayout = jsLayout\n\n\th := e.JavaScript(jsTemplate)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"$(alert(Mark))\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_JavaScript_WithLayout_Override(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"alert(<%= name %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\tjsLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"$(<%= yield %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\tjsAltLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"_(<%= yield %>)\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.JavaScriptLayout = jsLayout\n\n\th := e.JavaScript(jsTemplate, jsAltLayout)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"_(alert(Mark))\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_JavaScript_Partial_Without_Extension(t *testing.T) {\n\tconst tmpl = \"let a = 1;\\n<%= partial(\\\"part\\\") %>\"\n\tconst part = \"alert('Hi <%= name %>!');\"\n\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(tmpl),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_part.js\": &fstest.MapFile{\n\t\t\tData: []byte(part),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\th := e.JavaScript(jsTemplate)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Yonghwan\"}))\n\tr.Equal(\"let a = 1;\\nalert('Hi Yonghwan!');\", bb.String())\n}\n\nfunc Test_JavaScript_Partial(t *testing.T) {\n\tconst tmpl = \"let a = 1;\\n<%= partial(\\\"part.js\\\") %>\"\n\tconst part = \"alert('Hi <%= name %>!');\"\n\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(tmpl),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_part.js\": &fstest.MapFile{\n\t\t\tData: []byte(part),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\th := e.JavaScript(jsTemplate)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Yonghwan\"}))\n\tr.Equal(\"let a = 1;\\nalert('Hi Yonghwan!');\", bb.String())\n}\n\nfunc Test_JavaScript_HTML_Partial(t *testing.T) {\n\tconst tmpl = \"let a = \\\"<%= partial(\\\"part.html\\\") %>\\\"\"\n\tconst part = `<div id=\"foo\">\n\t<p>hi</p>\n</div>`\n\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tjsTemplate: &fstest.MapFile{\n\t\t\tData: []byte(tmpl),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_part.html\": &fstest.MapFile{\n\t\t\tData: []byte(part),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\th := e.JavaScript(jsTemplate)\n\tr.Equal(\"application/javascript\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{}))\n\tr.Contains(bb.String(), `id`)\n\tr.Contains(bb.String(), `foo`)\n\n\t// To check it has escaped the partial\n\tr.NotContains(bb.String(), `<div id=\"foo\">`)\n}\n"
  },
  {
    "path": "render/json.go",
    "content": "package render\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\ntype jsonRenderer struct {\n\tvalue any\n}\n\nfunc (s jsonRenderer) ContentType() string {\n\treturn \"application/json; charset=utf-8\"\n}\n\nfunc (s jsonRenderer) Render(w io.Writer, data Data) error {\n\treturn json.NewEncoder(w).Encode(s.value)\n}\n\n// JSON renders the value using the \"application/json\"\n// content type.\nfunc JSON(v any) Renderer {\n\treturn jsonRenderer{value: v}\n}\n\n// JSON renders the value using the \"application/json\"\n// content type.\nfunc (e *Engine) JSON(v any) Renderer {\n\treturn JSON(v)\n}\n"
  },
  {
    "path": "render/json_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_JSON(t *testing.T) {\n\tr := require.New(t)\n\n\te := NewEngine()\n\n\tre := e.JSON(Data{\"hello\": \"world\"})\n\tr.Equal(\"application/json; charset=utf-8\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(re.Render(bb, nil))\n\tr.Equal(`{\"hello\":\"world\"}`, strings.TrimSpace(bb.String()))\n}\n"
  },
  {
    "path": "render/markdown_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst mdTemplate = \"my-template.md\"\n\nfunc Test_MD_WithoutLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tmdTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\th := e.HTML(mdTemplate)\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"<p>Mark</p>\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_MD_WithLayout(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\tmdTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\thtmlLayout: &fstest.MapFile{\n\t\t\tData: []byte(\"<body><%= yield %></body>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\te.HTMLLayout = htmlLayout\n\n\th := e.HTML(mdTemplate)\n\tr.Equal(\"text/html; charset=utf-8\", h.ContentType())\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(h.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"<body><p>Mark</p>\\n</body>\", strings.TrimSpace(bb.String()))\n}\n"
  },
  {
    "path": "render/options.go",
    "content": "package render\n\nimport (\n\t\"io/fs\"\n\n\t\"github.com/gobuffalo/plush/v5/helpers/hctx\"\n)\n\n// Helpers to be included in all templates\ntype Helpers hctx.Map\n\n// Options for render.Engine\ntype Options struct {\n\t// HTMLLayout is the default layout to be used with all HTML renders.\n\tHTMLLayout string\n\n\t// JavaScriptLayout is the default layout to be used with all JavaScript renders.\n\tJavaScriptLayout string\n\n\t// TemplateFS is the fs.FS that holds the templates\n\tTemplatesFS fs.FS\n\n\t// AssetsFS is the fs.FS that holds the of the public assets the app will serve.\n\tAssetsFS fs.FS\n\n\t// Helpers to be rendered with the templates\n\tHelpers Helpers\n\n\t// TemplateEngine to be used for rendering HTML templates\n\tTemplateEngines map[string]TemplateEngine\n\n\t// DefaultContentType instructs the engine what it should fall back to if\n\t// the \"content-type\" is unknown\n\tDefaultContentType string\n\n\t// TemplateMetadataKeys allows users to specify custom keys for template metadata\n\t// If nil, uses default Buffalo metadata approach\n\tTemplateMetadataKeys map[string]string\n\n\tTemplateBaseDir string\n}\n\n// Default metadata keys\nvar defaultTemplateMetadataKeys = map[string]string{\n\t\"template_file\": \"_buffalo_template_file\",\n\t\"base_name\":     \"_buffalo_base_name\",\n\t\"extension\":     \"_buffalo_extension\",\n\t\"last_modified\": \"_buffalo_last_modification\",\n}\n"
  },
  {
    "path": "render/partials_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Template_Partial(t *testing.T) {\n\tr := require.New(t)\n\n\tconst part = `<%= partial(\"foo.html\") %>`\n\tconst tmpl = \"Foo > <%= name %>\"\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(tmpl),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_foo.html\": &fstest.MapFile{\n\t\t\tData: []byte(part),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"foo/bar\", htmlTemplate)\n\tr.NoError(re.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"Foo > Mark\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_PartialCustomFeeder(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"base.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(`<%= partial(\"foo.plush.html\") %>`),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_foo.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"other\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tt.Run(\"Custom Feeder\", func(t *testing.T) {\n\t\te.Helpers[\"partialFeeder\"] = func(path string) (string, error) {\n\t\t\treturn \"custom\", nil\n\t\t}\n\n\t\tbb := &bytes.Buffer{}\n\n\t\tre := e.HTML(\"base.plush.html\")\n\t\tr.NoError(re.Render(bb, Data{}))\n\t\tr.Equal(\"custom\", strings.TrimSpace(bb.String()))\n\t})\n\n\tt.Run(\"Default Feeder\", func(t *testing.T) {\n\t\te.Helpers[\"partialFeeder\"] = nil\n\n\t\tbb := &bytes.Buffer{}\n\n\t\tre := e.HTML(\"base.plush.html\")\n\t\tr.NoError(re.Render(bb, Data{}))\n\t\tr.Equal(\"other\", strings.TrimSpace(bb.String()))\n\t})\n}\n\nfunc Test_Template_Partial_WithoutExtension(t *testing.T) {\n\tr := require.New(t)\n\n\tconst part = `<%= partial(\"foo\") %>`\n\tconst tmpl = \"Foo > <%= name %>\"\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(tmpl),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_foo.html\": &fstest.MapFile{\n\t\t\tData: []byte(part),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"foo/bar\", htmlTemplate)\n\tr.NoError(re.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(\"Foo > Mark\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_Partial_Form(t *testing.T) {\n\tr := require.New(t)\n\n\tconst newHTML = `<%= form_for(user, {}) { return partial(\"form.html\") } %>`\n\tconst formHTML = `<%= f.InputTag(\"Name\") %>`\n\tconst result = `<form action=\"/Mark\" id=\"widget-form\" method=\"POST\"><div class=\"form-group\"><label class=\"form-label\" for=\"widget-Name\">Name</label><input class=\"form-control\" id=\"widget-Name\" name=\"Name\" type=\"text\" value=\"Mark\" /></div></form>`\n\n\trootFS := fstest.MapFS{\n\t\t\"new.html\": &fstest.MapFile{\n\t\t\tData: []byte(newHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_form.html\": &fstest.MapFile{\n\t\t\tData: []byte(formHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tu := Widget{Name: \"Mark\"}\n\n\tbb := &bytes.Buffer{}\n\tre := e.HTML(\"new.html\")\n\tr.NoError(re.Render(bb, Data{\"user\": u}))\n\tr.Equal(result, strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_Partial_With_For(t *testing.T) {\n\tr := require.New(t)\n\n\tconst forHTML = `<%= for(user) in users { %><%= partial(\"row\") %><% } %>`\n\tconst rowHTML = `Hi <%= user.Name %>, `\n\tconst result = `Hi Mark, Hi Yonghwan,`\n\n\trootFS := fstest.MapFS{\n\t\t\"for.html\": &fstest.MapFile{\n\t\t\tData: []byte(forHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_row.html\": &fstest.MapFile{\n\t\t\tData: []byte(rowHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"text/html; charset=utf-8\", \"for.html\")\n\tr.Equal(\"text/html; charset=utf-8\", re.ContentType())\n\n\terr := re.Render(bb, Data{\"users\": []Widget{\n\t\t{Name: \"Mark\"},\n\t\t{Name: \"Yonghwan\"},\n\t}})\n\n\tr.NoError(err)\n\tr.Equal(result, strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_Partial_With_For_And_Local(t *testing.T) {\n\tr := require.New(t)\n\n\tconst forHTML = `<%= for(user) in users { %><%= partial(\"row\", {say:\"Hi\"}) %><% } %>`\n\tconst rowHTML = `<%= say %> <%= user.Name %>, `\n\tconst result = `Hi Mark, Hi Yonghwan,`\n\n\trootFS := fstest.MapFS{\n\t\t\"for.html\": &fstest.MapFile{\n\t\t\tData: []byte(forHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_row.html\": &fstest.MapFile{\n\t\t\tData: []byte(rowHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"text/html; charset=utf-8\", \"for.html\")\n\tr.Equal(\"text/html; charset=utf-8\", re.ContentType())\n\n\terr := re.Render(bb, Data{\"users\": []Widget{\n\t\t{Name: \"Mark\"},\n\t\t{Name: \"Yonghwan\"},\n\t}})\n\n\tr.NoError(err)\n\tr.Equal(result, strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_Partial_Recursive_With_Global_And_Local_Context(t *testing.T) {\n\tr := require.New(t)\n\n\tconst indexHTML = `<%= partial(\"foo.html\", {other: \"Other\"}) %>`\n\tconst fooHTML = `<%= other %>|<%= name %>`\n\tconst result = `Other|Mark`\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(indexHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_foo.html\": &fstest.MapFile{\n\t\t\tData: []byte(fooHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.NoError(re.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(result, strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_Partial_With_Layout(t *testing.T) {\n\tr := require.New(t)\n\n\tconst indexHTML = `<%= partial(\"foo.html\",{layout:\"layout.html\"}) %>`\n\tconst layoutHTML = `Layout > <%= yield %>`\n\tconst fooHTML = \"Foo > <%= name %>\"\n\tconst result = `Layout > Foo > Mark`\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(indexHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_layout.html\": &fstest.MapFile{\n\t\t\tData: []byte(layoutHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"_foo.html\": &fstest.MapFile{\n\t\t\tData: []byte(fooHTML),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tbb := &bytes.Buffer{}\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.NoError(re.Render(bb, Data{\"name\": \"Mark\"}))\n\tr.Equal(result, strings.TrimSpace(bb.String()))\n}\n"
  },
  {
    "path": "render/plain.go",
    "content": "package render\n\n// Plain renders the named files using the 'text/html'\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\".\nfunc Plain(names ...string) Renderer {\n\te := New(Options{})\n\treturn e.Plain(names...)\n}\n\n// Plain renders the named files using the 'text/plain'\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"<%= yield %>\".\nfunc (e *Engine) Plain(names ...string) Renderer {\n\thr := &templateRenderer{\n\t\tEngine:      e,\n\t\tcontentType: \"text/plain; charset=utf-8\",\n\t\tnames:       names,\n\t}\n\treturn hr\n}\n"
  },
  {
    "path": "render/plain_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Plain(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"test.txt\": &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Plain(\"test.txt\")\n\tr.Equal(\"text/plain; charset=utf-8\", re.ContentType())\n\n\tvar examples = []string{\"Mark\", \"Jém\"}\n\tfor _, example := range examples {\n\t\tbb := &bytes.Buffer{}\n\t\tr.NoError(re.Render(bb, Data{\"name\": example}))\n\t\tr.Equal(example, bb.String())\n\t}\n}\n"
  },
  {
    "path": "render/render.go",
    "content": "package render\n\nimport (\n\t\"github.com/gobuffalo/helpers\"\n\t\"github.com/gobuffalo/helpers/forms\"\n\t\"github.com/gobuffalo/helpers/forms/bootstrap\"\n\t\"github.com/gobuffalo/plush/v5\"\n)\n\n// Engine used to power all defined renderers.\n// This allows you to configure the system to your\n// preferred settings, instead of just getting\n// the defaults.\ntype Engine struct {\n\tOptions\n}\n\n// New render.Engine ready to go with your Options\n// and some defaults we think you might like.\nfunc New(opts Options) *Engine {\n\tif opts.Helpers == nil {\n\t\topts.Helpers = Helpers{}\n\t}\n\n\tif len(opts.Helpers) == 0 {\n\t\topts.Helpers = defaultHelpers()\n\t}\n\n\tif opts.TemplateEngines == nil {\n\t\topts.TemplateEngines = map[string]TemplateEngine{}\n\t}\n\tif _, ok := opts.TemplateEngines[\"html\"]; !ok {\n\t\topts.TemplateEngines[\"html\"] = plush.BuffaloRenderer\n\t}\n\tif _, ok := opts.TemplateEngines[\"plush\"]; !ok {\n\t\topts.TemplateEngines[\"plush\"] = plush.BuffaloRenderer\n\t}\n\tif _, ok := opts.TemplateEngines[\"text\"]; !ok {\n\t\topts.TemplateEngines[\"text\"] = plush.BuffaloRenderer\n\t}\n\tif _, ok := opts.TemplateEngines[\"txt\"]; !ok {\n\t\topts.TemplateEngines[\"txt\"] = plush.BuffaloRenderer\n\t}\n\tif _, ok := opts.TemplateEngines[\"js\"]; !ok {\n\t\topts.TemplateEngines[\"js\"] = plush.BuffaloRenderer\n\t}\n\tif _, ok := opts.TemplateEngines[\"md\"]; !ok {\n\t\topts.TemplateEngines[\"md\"] = MDTemplateEngine\n\t}\n\tif _, ok := opts.TemplateEngines[\"tmpl\"]; !ok {\n\t\topts.TemplateEngines[\"tmpl\"] = GoTemplateEngine\n\t}\n\n\tif opts.DefaultContentType == \"\" {\n\t\topts.DefaultContentType = \"text/html; charset=utf-8\"\n\t}\n\n\tif opts.TemplateMetadataKeys == nil {\n\t\topts.TemplateMetadataKeys = defaultTemplateMetadataKeys\n\t}\n\n\te := &Engine{\n\t\tOptions: opts,\n\t}\n\treturn e\n}\n\nfunc defaultHelpers() Helpers {\n\th := Helpers(helpers.ALL())\n\th[forms.FormKey] = bootstrap.Form\n\th[forms.FormForKey] = bootstrap.FormFor\n\th[\"form_for\"] = bootstrap.FormFor\n\treturn h\n}\n"
  },
  {
    "path": "render/render_test.go",
    "content": "package render\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"testing/fstest\"\n)\n\ntype Widget struct {\n\tName string\n}\n\nfunc (w Widget) ToPath() string {\n\treturn w.Name\n}\n\nfunc NewEngine() *Engine {\n\treturn New(Options{\n\t\tTemplatesFS: fstest.MapFS{},\n\t\tAssetsFS:    fstest.MapFS{},\n\t})\n}\n\ntype rendFriend func(string, RendererFunc) Renderer\n\nfunc TestMain(m *testing.M) {\n\tcode := m.Run()\n\tos.Exit(code)\n}\n\nfunc init() {\n\tassetMap.Range(func(key, value string) bool {\n\t\tassetMap.Delete(key)\n\t\treturn true\n\t})\n}\n"
  },
  {
    "path": "render/renderer.go",
    "content": "package render\n\nimport \"io\"\n\n// Renderer interface that must be satisfied to be used with\n// buffalo.Context.Render\ntype Renderer interface {\n\tContentType() string\n\tRender(io.Writer, Data) error\n}\n\n// Data type to be provided to the Render function on the\n// Renderer interface.\ntype Data map[string]any\n"
  },
  {
    "path": "render/sse.go",
    "content": "package render\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\ntype sse struct {\n\tData any    `json:\"data\"`\n\tType string `json:\"type\"`\n}\n\nfunc (s *sse) String() string {\n\tb, _ := json.Marshal(s)\n\treturn fmt.Sprintf(\"data: %s\\n\\n\", string(b))\n}\n\nfunc (s *sse) Bytes() []byte {\n\treturn []byte(s.String())\n}\n\n// EventSource is designed to work with JavaScript EventSource objects.\n// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource for\n// more details\ntype EventSource struct {\n\tw  http.ResponseWriter\n\tfl http.Flusher\n}\n\nfunc (es *EventSource) Write(t string, d any) error {\n\ts := &sse{Type: t, Data: d}\n\t_, err := es.w.Write(s.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\tes.Flush()\n\treturn nil\n}\n\n// Flush messages down the pipe. If messages aren't flushed they\n// won't be sent.\nfunc (es *EventSource) Flush() {\n\tes.fl.Flush()\n}\n\ntype closeNotifier interface {\n\tCloseNotify() <-chan bool\n}\n\n// CloseNotify return true across the channel when the connection\n// in the browser has been severed.\nfunc (es *EventSource) CloseNotify() <-chan bool {\n\tif cn, ok := es.w.(closeNotifier); ok {\n\t\treturn cn.CloseNotify()\n\t}\n\treturn nil\n}\n\n// NewEventSource returns a new EventSource instance while ensuring\n// that the http.ResponseWriter is able to handle EventSource messages.\n// It also makes sure to set the proper response heads.\nfunc NewEventSource(w http.ResponseWriter) (*EventSource, error) {\n\tes := &EventSource{w: w}\n\tvar ok bool\n\tes.fl, ok = w.(http.Flusher)\n\tif !ok {\n\t\treturn es, fmt.Errorf(\"streaming is not supported\")\n\t}\n\n\tes.w.Header().Set(\"Content-Type\", \"text/event-stream\")\n\tes.w.Header().Set(\"Cache-Control\", \"no-cache\")\n\tes.w.Header().Set(\"Connection\", \"keep-alive\")\n\tes.w.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\treturn es, nil\n}\n"
  },
  {
    "path": "render/string.go",
    "content": "package render\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\ntype stringRenderer struct {\n\t*Engine\n\tbody string\n}\n\nfunc (s stringRenderer) ContentType() string {\n\treturn \"text/plain; charset=utf-8\"\n}\n\nfunc (s stringRenderer) Render(w io.Writer, data Data) error {\n\tte, ok := s.TemplateEngines[\"text\"]\n\tif !ok {\n\t\treturn fmt.Errorf(\"could not find a template engine for text\")\n\t}\n\tt, err := te(s.body, data, s.Helpers)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.Write([]byte(t))\n\treturn err\n}\n\n// String renderer that will run the string through\n// the github.com/gobuffalo/plush package and return\n// \"text/plain\" as the content type.\nfunc String(s string, args ...any) Renderer {\n\te := New(Options{})\n\treturn e.String(s, args...)\n}\n\n// String renderer that will run the string through\n// the github.com/gobuffalo/plush package and return\n// \"text/plain\" as the content type.\nfunc (e *Engine) String(s string, args ...any) Renderer {\n\tif len(args) > 0 {\n\t\ts = fmt.Sprintf(s, args...)\n\t}\n\treturn stringRenderer{\n\t\tEngine: e,\n\t\tbody:   s,\n\t}\n}\n"
  },
  {
    "path": "render/string_map.go",
    "content": "package render\n\nimport (\n\t\"slices\"\n\t\"sync\"\n)\n\n// stringMap wraps sync.Map and uses the following types:\n// key:   string\n// value: string\ntype stringMap struct {\n\tdata sync.Map\n}\n\n// Delete the key from the map\nfunc (m *stringMap) Delete(key string) {\n\tm.data.Delete(key)\n}\n\n// Load the key from the map.\n// Returns string or bool.\n// A false return indicates either the key was not found\n// or the value is not of type string\nfunc (m *stringMap) Load(key string) (string, bool) {\n\ti, ok := m.data.Load(key)\n\tif !ok {\n\t\treturn ``, false\n\t}\n\ts, ok := i.(string)\n\treturn s, ok\n}\n\n// LoadOrStore will return an existing key or\n// store the value if not already in the map\nfunc (m *stringMap) LoadOrStore(key string, value string) (string, bool) {\n\ti, _ := m.data.LoadOrStore(key, value)\n\ts, ok := i.(string)\n\treturn s, ok\n}\n\n// Range over the string values in the map\nfunc (m *stringMap) Range(f func(key string, value string) bool) {\n\tm.data.Range(func(k, v any) bool {\n\t\tkey, ok := k.(string)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tvalue, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn f(key, value)\n\t})\n}\n\n// Store a string in the map\nfunc (m *stringMap) Store(key string, value string) {\n\tm.data.Store(key, value)\n}\n\n// Keys returns a list of keys in the map\nfunc (m *stringMap) Keys() []string {\n\tvar keys []string\n\tm.Range(func(key string, value string) bool {\n\t\tkeys = append(keys, key)\n\t\treturn true\n\t})\n\tslices.Sort(keys)\n\treturn keys\n}\n\n// Clear removes all entries from the map\nfunc (m *stringMap) Clear() {\n\tm.data.Clear()\n}\n"
  },
  {
    "path": "render/string_map_test.go",
    "content": "package render\n\nimport (\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_stringMap(t *testing.T) {\n\tr := require.New(t)\n\n\tsm := &stringMap{}\n\n\tsm.Store(\"a\", `A`)\n\n\ts, ok := sm.Load(\"a\")\n\tr.True(ok)\n\tr.Equal(`A`, s)\n\n\ts, ok = sm.LoadOrStore(\"b\", `B`)\n\tr.True(ok)\n\tr.Equal(`B`, s)\n\n\ts, ok = sm.LoadOrStore(\"b\", `BB`)\n\tr.True(ok)\n\tr.Equal(`B`, s)\n\n\tvar keys []string\n\n\tsm.Range(func(key string, value string) bool {\n\t\tkeys = append(keys, key)\n\t\treturn true\n\t})\n\n\tslices.Sort(keys)\n\n\tr.Equal(sm.Keys(), keys)\n\n\tsm.Delete(\"b\")\n\tr.Equal([]string{\"a\", \"b\"}, keys)\n\n\tsm.Delete(\"b\")\n\t_, ok = sm.Load(\"b\")\n\tr.False(ok)\n\n\tfunc(m *stringMap) {\n\t\tm.Store(\"c\", `C`)\n\t}(sm)\n\ts, ok = sm.Load(\"c\")\n\tr.True(ok)\n\tr.Equal(`C`, s)\n}\n"
  },
  {
    "path": "render/string_test.go",
    "content": "package render_test\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_String(t *testing.T) {\n\tr := require.New(t)\n\n\tj := render.New(render.Options{}).String\n\n\tre := j(\"<%= name %>\")\n\tr.Equal(\"text/plain; charset=utf-8\", re.ContentType())\n\n\tvar examples = []string{\"Mark\", \"Jém\"}\n\tfor _, example := range examples {\n\t\tbb := &bytes.Buffer{}\n\t\terr := re.Render(bb, map[string]any{\"name\": example})\n\t\tr.NoError(err)\n\t\tr.Equal(example, bb.String())\n\t}\n\n}\n"
  },
  {
    "path": "render/template.go",
    "content": "package render\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/fs\"\n\t\"maps\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"unsafe\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"golang.org/x/text/language\"\n)\n\ntype templateRenderer struct {\n\t*Engine\n\tcontentType string\n\tnames       []string\n\taliases     sync.Map\n}\n\nfunc (s *templateRenderer) ContentType() string {\n\treturn s.contentType\n}\n\nfunc (s *templateRenderer) resolve(name string) ([]byte, fs.FileInfo, error) {\n\tif s.TemplatesFS == nil {\n\t\treturn nil, nil, fmt.Errorf(\"no templates fs defined\")\n\t}\n\n\tf, err := s.TemplatesFS.Open(name)\n\tif err == nil {\n\t\tcontents, err := io.ReadAll(f)\n\t\tff, _ := f.Stat()\n\t\treturn contents, ff, err\n\t}\n\n\tv, ok := s.aliases.Load(name)\n\tif !ok {\n\t\treturn nil, nil, fmt.Errorf(\"could not find template %s\", name)\n\t}\n\n\tf, err = s.TemplatesFS.Open(v.(string))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcontents, err := io.ReadAll(f)\n\tff, _ := f.Stat()\n\treturn contents, ff, err\n}\n\nfunc (s *templateRenderer) Render(w io.Writer, data Data) error {\n\n\tif err := s.updateAliases(); err != nil {\n\t\treturn err\n\t}\n\n\tvar body template.HTML\n\tfor _, name := range s.names {\n\t\tvar err error\n\t\tbody, err = s.exec(name, data)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s: %w\", name, err)\n\t\t}\n\t\tdata[\"yield\"] = body\n\t}\n\n\t_, err := w.Write(unsafe.Slice(unsafe.StringData(string(body)), len(body)))\n\treturn err\n}\n\nfunc (s *templateRenderer) updateAliases() error {\n\tif s.TemplatesFS == nil {\n\t\treturn nil\n\t}\n\n\treturn fs.WalkDir(s.TemplatesFS, \".\", func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif d.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\t// index.plush.ko-kr.html as index.ko-kr.html\n\t\tshortcut := strings.Replace(path, \".plush.\", \".\", 1)\n\t\ts.aliases.Store(shortcut, path)\n\n\t\t// register short version (lang only) of shortcut\n\t\twords := strings.Split(filepath.Base(shortcut), \".\")\n\t\tif len(words) > 2 {\n\t\t\tfor _, w := range words[1 : len(words)-1] {\n\t\t\t\tif tag, err := language.Parse(w); err == nil {\n\t\t\t\t\tbase, confidence := tag.Base()\n\t\t\t\t\tif confidence == language.Exact || confidence == language.High {\n\t\t\t\t\t\t// index.plush.ko-kr.html as index.ko.html\n\t\t\t\t\t\tshortcut := strings.Replace(shortcut, w, base.String(), 1)\n\t\t\t\t\t\ts.aliases.Store(shortcut, path)\n\n\t\t\t\t\t\t// index.plush.ko-kr.html as index.plush.ko.html\n\t\t\t\t\t\tshortcut = strings.Replace(path, w, base.String(), 1)\n\t\t\t\t\t\ts.aliases.Store(shortcut, path)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc fixExtension(name string, ct string) string {\n\tif filepath.Ext(name) == \"\" {\n\t\tswitch {\n\t\tcase strings.Contains(ct, \"html\"):\n\t\t\tname += \".html\"\n\t\tcase strings.Contains(ct, \"javascript\"):\n\t\t\tname += \".js\"\n\t\tcase strings.Contains(ct, \"markdown\"):\n\t\t\tname += \".md\"\n\t\t}\n\t}\n\treturn name\n}\n\n// partialFeeder returns template string for the name from `TemplateBox`.\n// It should be registered as helper named `partialFeeder` so plush can\n// find it with the name.\nfunc (s *templateRenderer) partialFeeder(name string) (string, error) {\n\tct := strings.ToLower(s.contentType)\n\n\td, f := filepath.Split(name)\n\tname = filepath.Join(d, \"_\"+f)\n\tname = fixExtension(name, ct)\n\n\tb, _, err := s.resolve(name)\n\treturn string(b), err\n}\n\nfunc (s *templateRenderer) exec(name string, data Data) (template.HTML, error) {\n\tct := strings.ToLower(s.contentType)\n\tdata[\"contentType\"] = ct\n\n\tname = fixExtension(name, ct)\n\n\tsource, file, err := s.localizedResolve(name, data)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\thelpers := maps.Clone(s.Helpers)\n\n\t// Allows to specify custom partialFeeder\n\tif helpers[\"partialFeeder\"] == nil {\n\t\thelpers[\"partialFeeder\"] = s.partialFeeder\n\t}\n\n\thelpers = s.addAssetsHelpers(helpers)\n\texts, fileName := s.extsAndBase(name)\n\tbody := string(source)\n\tfor _, ext := range exts {\n\t\ts.addTemplateMetadata(data, fileName, ext, file)\n\t\tte, ok := s.TemplateEngines[ext]\n\t\tif !ok {\n\t\t\tlogrus.Errorf(\"could not find a template engine for %s\", ext)\n\t\t\tcontinue\n\t\t}\n\t\tbody, err = te(body, data, helpers)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\treturn template.HTML(body), nil\n}\n\n// next step, deprecate if this is no longer required.\nfunc (s *templateRenderer) localizedName(name string, data Data) string {\n\ttemplateName := name\n\n\tlanguages, ok := data[\"languages\"].([]string)\n\tif !ok || len(languages) == 0 {\n\t\treturn templateName\n\t}\n\n\tll := len(languages)\n\t// Default language is the last in the list\n\tdefaultLanguage := languages[ll-1]\n\text := filepath.Ext(name)\n\trawName := strings.TrimSuffix(name, ext)\n\n\tfor _, l := range languages {\n\t\tvar candidateName string\n\t\tif l == defaultLanguage {\n\t\t\tbreak\n\t\t}\n\n\t\tcandidateName = rawName + \".\" + strings.ToLower(l) + ext\n\t\tif _, _, err := s.resolve(candidateName); err == nil {\n\t\t\t// Replace name with the existing suffixed version\n\t\t\ttemplateName = candidateName\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn templateName\n}\n\nfunc (s *templateRenderer) localizedResolve(name string, data Data) ([]byte, fs.FileInfo, error) {\n\tlanguages, ok := data[\"languages\"].([]string)\n\tif !ok || len(languages) == 0 {\n\t\treturn s.resolve(name)\n\t}\n\n\tdefaultLang := languages[len(languages)-1] // default language\n\text := filepath.Ext(name)\n\trawName := strings.TrimSuffix(name, ext)\n\n\tfor _, lang := range languages {\n\t\tif lang == defaultLang {\n\t\t\tbreak\n\t\t}\n\n\t\tfullLower := strings.ToLower(lang)        // forms of ko-kr or ko\n\t\tshort := strings.Split(fullLower, \"-\")[0] // form of ko\n\n\t\tfullLocale := rawName + \".\" + fullLower + ext\n\t\tif source, file, err := s.resolve(fullLocale); err == nil {\n\t\t\treturn source, file, nil\n\t\t}\n\n\t\tif fullLower != short {\n\t\t\tlangOnly := rawName + \".\" + short + ext\n\t\t\tif source, file, err := s.resolve(langOnly); err == nil {\n\t\t\t\treturn source, file, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn s.resolve(name)\n}\n\nfunc (s *templateRenderer) addTemplateMetadata(data Data, fileName, ext string, info fs.FileInfo) {\n\t// Get metadata keys from options, fallback to defaults\n\tmetaKeys := s.TemplateMetadataKeys\n\tif metaKeys == nil {\n\t\tmetaKeys = defaultTemplateMetadataKeys\n\t}\n\n\t// Map each metadata type to user-defined key\n\tif templateFileKey, exists := metaKeys[\"template_file\"]; exists && templateFileKey != \"\" {\n\t\tdata[templateFileKey] = filepath.Join(s.TemplateBaseDir, fileName) + \".\" + ext\n\t}\n\n\t// base_name and extension are derived from the name passed to exec\n\t// last_modified is derived from the fs.FileInfo passed to exec\n\t// if available\n\tif baseNameKey, exists := metaKeys[\"base_name\"]; exists && baseNameKey != \"\" {\n\t\tdata[baseNameKey] = fileName\n\t}\n\n\tif extensionKey, exists := metaKeys[\"extension\"]; exists && extensionKey != \"\" {\n\t\tdata[extensionKey] = ext\n\t}\n\n\tif lastModKey, exists := metaKeys[\"last_modified\"]; exists && lastModKey != \"\" {\n\t\tdata[lastModKey] = info.ModTime()\n\t}\n}\n\nfunc (s *templateRenderer) extsAndBase(name string) ([]string, string) {\n\texts := []string{}\n\tbaseName := name\n\tfor {\n\t\text := filepath.Ext(baseName)\n\t\tif ext == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tbaseName = strings.TrimSuffix(baseName, ext)\n\t\texts = append(exts, strings.ToLower(ext[1:]))\n\t}\n\tif len(exts) == 0 {\n\t\treturn []string{\"html\"}, baseName\n\t}\n\tsort.Sort(sort.Reverse(sort.StringSlice(exts)))\n\treturn exts, baseName\n}\n\nfunc (s *templateRenderer) exts(name string) []string {\n\texts := []string{}\n\tfor {\n\t\text := filepath.Ext(name)\n\t\tif ext == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tname = strings.TrimSuffix(name, ext)\n\t\texts = append(exts, strings.ToLower(ext[1:]))\n\t}\n\tif len(exts) == 0 {\n\t\treturn []string{\"html\"}\n\t}\n\tsort.Sort(sort.Reverse(sort.StringSlice(exts)))\n\treturn exts\n}\n\nfunc (s *templateRenderer) assetPath(file string) (string, error) {\n\n\tif len(assetMap.Keys()) == 0 || os.Getenv(\"GO_ENV\") != \"production\" {\n\t\tmanifest, err := s.AssetsFS.Open(\"manifest.json\")\n\t\tif err != nil {\n\t\t\tmanifest, err = s.AssetsFS.Open(\"assets/manifest.json\")\n\t\t\tif err != nil {\n\t\t\t\treturn assetPathFor(file), nil\n\t\t\t}\n\t\t}\n\t\tdefer manifest.Close()\n\n\t\terr = loadManifest(manifest)\n\t\tif err != nil {\n\t\t\treturn assetPathFor(file), fmt.Errorf(\"your manifest.json is not correct: %s\", err)\n\t\t}\n\t}\n\n\treturn assetPathFor(file), nil\n}\n\n// Template renders the named files using the specified\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"{{yield}}\".\nfunc Template(c string, names ...string) Renderer {\n\te := New(Options{})\n\treturn e.Template(c, names...)\n}\n\n// Template renders the named files using the specified\n// content type and the github.com/gobuffalo/plush\n// package for templating. If more than 1 file is provided\n// the second file will be considered a \"layout\" file\n// and the first file will be the \"content\" file which will\n// be placed into the \"layout\" using \"{{yield}}\".\nfunc (e *Engine) Template(c string, names ...string) Renderer {\n\treturn &templateRenderer{\n\t\tEngine:      e,\n\t\tcontentType: c,\n\t\tnames:       names,\n\t}\n}\n"
  },
  {
    "path": "render/template_engine.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"html/template\"\n)\n\n// TemplateEngine needs to be implemented for a template system to be able to be used with Buffalo.\ntype TemplateEngine func(input string, data map[string]any, helpers map[string]any) (string, error)\n\n// GoTemplateEngine implements the TemplateEngine interface for using standard Go templates\nfunc GoTemplateEngine(input string, data map[string]any, helpers map[string]any) (string, error) {\n\tt := template.New(input)\n\n\tt, err := t.Parse(input)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbb := &bytes.Buffer{}\n\terr = t.Execute(bb, data)\n\treturn bb.String(), err\n}\n"
  },
  {
    "path": "render/template_helpers.go",
    "content": "package render\n\nimport (\n\t\"encoding/json\"\n\t\"html/template\"\n\t\"io\"\n\t\"path\"\n\t\"path/filepath\"\n\n\tht \"github.com/gobuffalo/helpers/tags\"\n\t\"github.com/gobuffalo/tags/v3\"\n)\n\ntype helperTag struct {\n\tname string\n\tfn   func(string, tags.Options) template.HTML\n}\n\nfunc (s *templateRenderer) addAssetsHelpers(helpers Helpers) Helpers {\n\thelpers[\"assetPath\"] = s.assetPath\n\n\tah := []helperTag{\n\t\t{\"javascriptTag\", ht.JS},\n\t\t{\"stylesheetTag\", ht.CSS},\n\t\t{\"imgTag\", ht.Img},\n\t}\n\n\tfor _, h := range ah {\n\t\tfunc(h helperTag) {\n\t\t\thelpers[h.name] = func(file string, options tags.Options) (template.HTML, error) {\n\t\t\t\tif options == nil {\n\t\t\t\t\toptions = tags.Options{}\n\t\t\t\t}\n\t\t\t\tf, err := s.assetPath(file)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\treturn h.fn(f, options), nil\n\t\t\t}\n\t\t}(h)\n\t}\n\n\treturn helpers\n}\n\nvar assetMap = stringMap{}\n\nfunc assetPathFor(file string) string {\n\tfilePath, ok := assetMap.Load(file)\n\tif filePath == \"\" || !ok {\n\t\tfilePath = file\n\t}\n\treturn path.Join(\"/assets\", filePath)\n}\n\nfunc loadManifest(manifest io.Reader) error {\n\tm := map[string]string{}\n\n\tif err := json.NewDecoder(manifest).Decode(&m); err != nil {\n\t\treturn err\n\t}\n\tfor k, v := range m {\n\t\t// I don't think v has backslash but if so, correct them when\n\t\t// creating the map instead using the value in `assetPathFor()`.\n\t\tassetMap.Store(k, filepath.ToSlash(v))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "render/template_helpers_test.go",
    "content": "package render\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/gobuffalo/tags/v3\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype tagHelper = func(string, tags.Options) (template.HTML, error)\n\nfunc tag(name string) (tagHelper, error) {\n\t// Clear assetMap to ensure test isolation from other tests that may have loaded manifests\n\tassetMap.Clear()\n\n\te := NewEngine()\n\t// Use empty AssetsFS for these tests to avoid manifest lookup\n\te.AssetsFS = fstest.MapFS{}\n\ttr := e.Template(\"\").(*templateRenderer)\n\n\th := tr.addAssetsHelpers(Helpers{})\n\tjt := h[name]\n\tf, ok := jt.(func(string, tags.Options) (template.HTML, error))\n\tif !ok {\n\t\treturn f, fmt.Errorf(\"expected tagHelper got %T\", jt)\n\t}\n\treturn f, nil\n}\n\nfunc Test_javascriptTag(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"javascriptTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"application.js\", nil)\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<script src=\"/assets/application.js\" type=\"text/javascript\"></script>`), s)\n}\n\nfunc Test_javascriptTag_Options(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"javascriptTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"application.js\", tags.Options{\"class\": \"foo\"})\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<script class=\"foo\" src=\"/assets/application.js\" type=\"text/javascript\"></script>`), s)\n}\n\nfunc Test_stylesheetTag(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"stylesheetTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"application.css\", nil)\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<link href=\"/assets/application.css\" media=\"screen\" rel=\"stylesheet\" />`), s)\n}\n\nfunc Test_stylesheetTag_Options(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"stylesheetTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"application.css\", tags.Options{\"class\": \"foo\"})\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<link class=\"foo\" href=\"/assets/application.css\" media=\"screen\" rel=\"stylesheet\" />`), s)\n}\n\nfunc Test_imgTag(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"imgTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"foo.png\", nil)\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<img src=\"/assets/foo.png\" />`), s)\n}\n\nfunc Test_imgTag_Options(t *testing.T) {\n\tr := require.New(t)\n\n\tf, err := tag(\"imgTag\")\n\tr.NoError(err)\n\n\ts, err := f(\"foo.png\", tags.Options{\"class\": \"foo\"})\n\tr.NoError(err)\n\tr.Equal(template.HTML(`<img class=\"foo\" src=\"/assets/foo.png\" />`), s)\n}\n"
  },
  {
    "path": "render/template_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Template(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\thtmlTemplate: &fstest.MapFile{\n\t\t\tData: []byte(\"<%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", htmlTemplate)\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Mark\"}))\n}\n\nfunc Test_AssetPath(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"manifest.json\": &fstest.MapFile{\n\t\t\tData: []byte(`{\n\t\t\"application.css\": \"application.aabbc123.css\"\n\t}`),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.AssetsFS = rootFS\n\n\tcases := map[string]string{\n\t\t\"something.txt\":         \"/assets/something.txt\",\n\t\t\"images/something.png\":  \"/assets/images/something.png\",\n\t\t\"/images/something.png\": \"/assets/images/something.png\",\n\t\t\"application.css\":       \"/assets/application.aabbc123.css\",\n\t}\n\n\tfor original, expected := range cases {\n\t\trootFS := fstest.MapFS{\n\t\t\thtmlTemplate: &fstest.MapFile{\n\t\t\t\tData: []byte(\"<%= assetPath(\\\"\" + original + \"\\\") %>\"),\n\t\t\t\tMode: 0644,\n\t\t\t},\n\t\t}\n\t\te.TemplatesFS = rootFS\n\n\t\tre := e.Template(\"text/html; charset=utf-8\", htmlTemplate)\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.NoError(re.Render(bb, Data{}))\n\t\tr.Equal(expected, strings.TrimSpace(bb.String()))\n\t}\n\n}\n\nfunc Test_AssetPathNoManifest(t *testing.T) {\n\tr := require.New(t)\n\n\te := NewEngine()\n\n\tcases := map[string]string{\n\t\t\"something.txt\": \"/assets/something.txt\",\n\t}\n\n\tfor original, expected := range cases {\n\t\trootFS := fstest.MapFS{\n\t\t\thtmlTemplate: &fstest.MapFile{\n\t\t\t\tData: []byte(\"<%= assetPath(\\\"\" + original + \"\\\") %>\"),\n\t\t\t\tMode: 0644,\n\t\t\t},\n\t\t}\n\t\te.TemplatesFS = rootFS\n\n\t\tre := e.Template(\"text/html; charset=utf-8\", htmlTemplate)\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.NoError(re.Render(bb, Data{}))\n\t\tr.Equal(expected, strings.TrimSpace(bb.String()))\n\t}\n}\n\nfunc Test_AssetPathNoManifestCorrupt(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"manifest.json\": &fstest.MapFile{\n\t\t\tData: []byte(\"//shdnn Corrupt!\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.AssetsFS = rootFS\n\n\tcases := map[string]string{\n\t\t\"something.txt\": \"manifest.json is not correct\",\n\t\t\"other.txt\":     \"manifest.json is not correct\",\n\t}\n\n\tfor original, expected := range cases {\n\t\trootFS := fstest.MapFS{\n\t\t\thtmlTemplate: &fstest.MapFile{\n\t\t\t\tData: []byte(\"<%= assetPath(\\\"\" + original + \"\\\") %>\"),\n\t\t\t\tMode: 0644,\n\t\t\t},\n\t\t}\n\t\te.TemplatesFS = rootFS\n\n\t\tre := e.Template(\"text/html; charset=utf-8\", htmlTemplate)\n\n\t\tbb := &bytes.Buffer{}\n\t\tr.Error(re.Render(bb, Data{}))\n\t\tr.NotEqual(expected, strings.TrimSpace(bb.String()))\n\t}\n}\n\n/* test if i18n files (both with plush mid-extension and latecy) proceeded correctly.\n */\nfunc Test_Template_resolve_DefaultLang_Plush(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.plush.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"es\", \"en\"}}))\n\tr.Equal(\"default Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_UserLang_Plush(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.plush.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_DefaultLang_Legacy(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"es\", \"en\"}}))\n\tr.Equal(\"default Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_UserLang_Legacy(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_DefaultLang_Mixed(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\t// `buffalo fix` renames templates but does not fix actions\n\t// in this case, aliases will be used for template matching\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"es\", \"en\"}}))\n\tr.Equal(\"default Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_UserLang_Mixed(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\t// `buffalo fix` renames templates but does not fix actions\n\t// in this case, aliases will be used for template matching\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\n// support short language-only version of template e.g. index.plush.ko.html\nfunc Test_Template_resolve_FullLocale_ShortFile(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.plush.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_LangOnly_FullFile(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.plush.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_FullLocale_ShortFile_Legacy(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.ko.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_LangOnly_FullFile_Legacy(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_FullLocale_ShortFile_Mixed(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko-KR\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_resolve_LangOnly_FullFile_Mixed(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"index.plush.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"default <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t\t\"index.plush.ko-kr.html\": &fstest.MapFile{\n\t\t\tData: []byte(\"korean <%= name %>\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\n\te := NewEngine()\n\te.TemplatesFS = rootFS\n\n\tre := e.Template(\"foo/bar\", \"index.html\")\n\tr.Equal(\"foo/bar\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\tr.NoError(re.Render(bb, Data{\"name\": \"Paul\", \"languages\": []string{\"ko\", \"en\"}}))\n\tr.Equal(\"korean Paul\", strings.TrimSpace(bb.String()))\n}\n\nfunc Test_Template_extsAndBase(t *testing.T) {\n\tr := require.New(t)\n\n\ttests := []struct {\n\t\tname         string\n\t\tinput        string\n\t\texpectedExts []string\n\t\texpectedBase string\n\t}{\n\t\t{\n\t\t\tname:         \"single extension\",\n\t\t\tinput:        \"index.html\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"index\",\n\t\t},\n\t\t{\n\t\t\tname:         \"multiple extensions\",\n\t\t\tinput:        \"template.html.plush\",\n\t\t\texpectedExts: []string{\"plush\", \"html\"},\n\t\t\texpectedBase: \"template\",\n\t\t},\n\t\t{\n\t\t\tname:         \"three extensions\",\n\t\t\tinput:        \"layout.md.html.plush\",\n\t\t\texpectedExts: []string{\"plush\", \"md\", \"html\"},\n\t\t\texpectedBase: \"layout\",\n\t\t},\n\t\t{\n\t\t\tname:         \"no extension\",\n\t\t\tinput:        \"template\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"template\",\n\t\t},\n\t\t{\n\t\t\tname:         \"empty string\",\n\t\t\tinput:        \"\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"extension with uppercase\",\n\t\t\tinput:        \"index.HTML\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"index\",\n\t\t},\n\t\t{\n\t\t\tname:         \"mixed case extensions\",\n\t\t\tinput:        \"template.MD.HTML.PLUSH\",\n\t\t\texpectedExts: []string{\"plush\", \"md\", \"html\"},\n\t\t\texpectedBase: \"template\",\n\t\t},\n\t\t{\n\t\t\tname:         \"path with directories\",\n\t\t\tinput:        \"layouts/application.html.plush\",\n\t\t\texpectedExts: []string{\"plush\", \"html\"},\n\t\t\texpectedBase: \"layouts/application\",\n\t\t},\n\t\t{\n\t\t\tname:         \"nested path no extension\",\n\t\t\tinput:        \"views/users/index\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"views/users/index\",\n\t\t},\n\t\t{\n\t\t\tname:         \"dotfile\",\n\t\t\tinput:        \".gitignore\",\n\t\t\texpectedExts: []string{\"gitignore\"},\n\t\t\texpectedBase: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"dotfile with extension\",\n\t\t\tinput:        \".env.local\",\n\t\t\texpectedExts: []string{\"local\", \"env\"},\n\t\t\texpectedBase: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"complex filename\",\n\t\t\tinput:        \"user-profile.en-US.html.plush\",\n\t\t\texpectedExts: []string{\"plush\", \"html\", \"en-us\"},\n\t\t\texpectedBase: \"user-profile\",\n\t\t},\n\t\t{\n\t\t\tname:         \"only extension\",\n\t\t\tinput:        \".html\",\n\t\t\texpectedExts: []string{\"html\"},\n\t\t\texpectedBase: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"locale and template extensions\",\n\t\t\tinput:        \"welcome.fr-FR.html.plush\",\n\t\t\texpectedExts: []string{\"plush\", \"html\", \"fr-fr\"},\n\t\t\texpectedBase: \"welcome\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trenderer := &templateRenderer{}\n\n\t\t\tgotExts, gotBase := renderer.extsAndBase(tt.input)\n\n\t\t\tr.Equal(tt.expectedExts, gotExts, \"extensions should match\")\n\t\t\tr.Equal(tt.expectedBase, gotBase, \"base name should match\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "render/xml.go",
    "content": "package render\n\nimport (\n\t\"encoding/xml\"\n\t\"io\"\n)\n\ntype xmlRenderer struct {\n\tvalue any\n}\n\nfunc (s xmlRenderer) ContentType() string {\n\treturn \"application/xml; charset=utf-8\"\n}\n\nfunc (s xmlRenderer) Render(w io.Writer, data Data) error {\n\tio.WriteString(w, xml.Header)\n\tenc := xml.NewEncoder(w)\n\tenc.Indent(\"\", \"  \")\n\treturn enc.Encode(s.value)\n}\n\n// XML renders the value using the \"application/xml\"\n// content type.\nfunc XML(v any) Renderer {\n\treturn xmlRenderer{value: v}\n}\n\n// XML renders the value using the \"application/xml\"\n// content type.\nfunc (e *Engine) XML(v any) Renderer {\n\treturn XML(v)\n}\n"
  },
  {
    "path": "render/xml_test.go",
    "content": "package render\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_XML(t *testing.T) {\n\tr := require.New(t)\n\n\ttype user struct {\n\t\tName string\n\t}\n\n\te := NewEngine()\n\n\tre := e.XML(user{Name: \"Mark\"})\n\tr.Equal(\"application/xml; charset=utf-8\", re.ContentType())\n\n\tbb := &bytes.Buffer{}\n\n\tr.NoError(re.Render(bb, nil))\n\tr.Equal(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<user>\\n  <Name>Mark</Name>\\n</user>\", strings.TrimSpace(bb.String()))\n}\n"
  },
  {
    "path": "request_data.go",
    "content": "package buffalo\n\nimport \"sync\"\n\ntype requestData struct {\n\td    map[string]any\n\tmoot *sync.RWMutex\n}\n\nfunc newRequestData() *requestData {\n\treturn &requestData{\n\t\td:    make(map[string]any),\n\t\tmoot: &sync.RWMutex{},\n\t}\n}\n"
  },
  {
    "path": "request_data_test.go",
    "content": "package buffalo\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_newRequestData(t *testing.T) {\n\n\tr := require.New(t)\n\tts := newRequestData()\n\tr.NotNil(ts)\n\tr.NotNil(ts.moot)\n\tr.NotNil(ts.d)\n}\n"
  },
  {
    "path": "request_logger.go",
    "content": "package buffalo\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/internal/httpx\"\n)\n\n// RequestLogger can be be overridden to a user specified\n// function that can be used to log the request.\nvar RequestLogger = RequestLoggerFunc\n\nfunc randString(i int) (string, error) {\n\tif i == 0 {\n\t\ti = 64\n\t}\n\tb := make([]byte, i)\n\t_, err := rand.Read(b)\n\treturn hex.EncodeToString(b), err\n}\n\n// formatBytes converts bytes to human-readable format (e.g., \"1.5 MB\")\nfunc formatBytes(b uint64) string {\n\tconst unit = 1024\n\tif b < unit {\n\t\treturn fmt.Sprintf(\"%d B\", b)\n\t}\n\tdiv, exp := uint64(unit), 0\n\tfor n := b / unit; n >= unit; n /= unit {\n\t\tdiv *= unit\n\t\texp++\n\t}\n\treturn fmt.Sprintf(\"%.1f %cB\", float64(b)/float64(div), \"KMGTPE\"[exp])\n}\n\n// RequestLoggerFunc is the default implementation of the RequestLogger.\n// By default it will log a uniq \"request_id\", the HTTP Method of the request,\n// the path that was requested, the duration (time) it took to process the\n// request, the size of the response (and the \"human\" size), and the status\n// code of the response.\nfunc RequestLoggerFunc(h Handler) Handler {\n\treturn func(c Context) error {\n\t\trs, err := randString(10)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar irid any\n\t\tif irid = c.Session().Get(\"requestor_id\"); irid == nil {\n\t\t\trs, err := randString(10)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tirid = rs\n\t\t\tc.Session().Set(\"requestor_id\", irid)\n\t\t}\n\n\t\trid := irid.(string) + \"-\" + rs\n\t\tc.Set(\"request_id\", rid)\n\t\tc.LogField(\"request_id\", rid)\n\n\t\tstart := time.Now()\n\t\tdefer func() {\n\t\t\tws, ok := c.Response().(*Response)\n\t\t\tif !ok {\n\t\t\t\tws = &Response{ResponseWriter: c.Response()}\n\t\t\t\tws.Status = http.StatusOK\n\t\t\t}\n\t\t\treq := c.Request()\n\t\t\tct := httpx.ContentType(req)\n\t\t\tif ct != \"\" {\n\t\t\t\tc.LogField(\"content_type\", ct)\n\t\t\t}\n\t\t\tc.LogFields(map[string]any{\n\t\t\t\t\"method\":     req.Method,\n\t\t\t\t\"path\":       req.URL.String(),\n\t\t\t\t\"duration\":   time.Since(start),\n\t\t\t\t\"size\":       ws.Size,\n\t\t\t\t\"human_size\": formatBytes(uint64(ws.Size)),\n\t\t\t\t\"status\":     ws.Status,\n\t\t\t})\n\t\t\tc.Logger().Info(req.URL.String())\n\t\t}()\n\t\treturn h(c)\n\t}\n}\n"
  },
  {
    "path": "resource.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Resource interface allows for the easy mapping\n// of common RESTful actions to a set of paths. See\n// the a.Resource documentation for more details.\n// NOTE: When skipping Resource handlers, you need to first declare your\n// resource handler as a type of buffalo.Resource for the Skip function to\n// properly recognize and match it.\n/*\n\t// Works:\n\tvar cr Resource\n\tcr = &carsResource{&buffaloBaseResource{}}\n\tg = a.Resource(\"/cars\", cr)\n\tg.Use(SomeMiddleware)\n\tg.Middleware.Skip(SomeMiddleware, cr.Show)\n\n\t// Doesn't Work:\n\tcr := &carsResource{&buffaloBaseResource{}}\n\tg = a.Resource(\"/cars\", cr)\n\tg.Use(SomeMiddleware)\n\tg.Middleware.Skip(SomeMiddleware, cr.Show)\n*/\ntype Resource interface {\n\tList(Context) error\n\tShow(Context) error\n\tCreate(Context) error\n\tUpdate(Context) error\n\tDestroy(Context) error\n}\n\n// Middler can be implemented to specify additional\n// middleware specific to the resource\ntype Middler interface {\n\tUse() []MiddlewareFunc\n}\n\n// BaseResource fills in the gaps for any Resource interface\n// functions you don't want/need to implement.\n/*\n\ttype UsersResource struct {\n\t\tResource\n\t}\n\n\tfunc (ur *UsersResource) List(c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"hello\")\n\t}\n\n\t// This will fulfill the Resource interface, despite only having\n\t// one of the functions defined.\n\t&UsersResource{&BaseResource{})\n*/\ntype BaseResource struct{}\n\n// List default implementation. Returns a 404\nfunc (v BaseResource) List(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\n// Show default implementation. Returns a 404\nfunc (v BaseResource) Show(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\n// Create default implementation. Returns a 404\nfunc (v BaseResource) Create(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\n// Update default implementation. Returns a 404\nfunc (v BaseResource) Update(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\n// Destroy default implementation. Returns a 404\nfunc (v BaseResource) Destroy(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n"
  },
  {
    "path": "response.go",
    "content": "package buffalo\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// Response implements the http.ResponseWriter interface and allows\n// for the capture of the response status and size to be used for things\n// like logging requests.\ntype Response struct {\n\tStatus int\n\tSize   int\n\thttp.ResponseWriter\n}\n\n// WriteHeader sets the status code for a response\nfunc (w *Response) WriteHeader(code int) {\n\tif code == w.Status {\n\t\treturn\n\t}\n\n\tif w.Status > 0 {\n\t\tfmt.Printf(\"[WARNING] Headers were already written. Wanted to override status code %d with %d\", w.Status, code)\n\t\treturn\n\t}\n\n\tw.Status = code\n\tw.ResponseWriter.WriteHeader(code)\n}\n\n// Write the body of the response\nfunc (w *Response) Write(b []byte) (int, error) {\n\tw.Size = binary.Size(b)\n\treturn w.ResponseWriter.Write(b)\n}\n\n// Hijack implements the http.Hijacker interface to allow for things like websockets.\nfunc (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tif hj, ok := w.ResponseWriter.(http.Hijacker); ok {\n\t\treturn hj.Hijack()\n\t}\n\treturn nil, nil, fmt.Errorf(\"does not implement http.Hijack\")\n}\n\n// Flush the response\nfunc (w *Response) Flush() {\n\tif f, ok := w.ResponseWriter.(http.Flusher); ok {\n\t\tf.Flush()\n\t}\n}\n\ntype closeNotifier interface {\n\tCloseNotify() <-chan bool\n}\n\n// CloseNotify implements the http.CloseNotifier interface\nfunc (w *Response) CloseNotify() <-chan bool {\n\tif cn, ok := w.ResponseWriter.(closeNotifier); ok {\n\t\treturn cn.CloseNotify()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "response_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Response_MultipleWrite(t *testing.T) {\n\tr := require.New(t)\n\tresWr := httptest.NewRecorder()\n\tres := Response{\n\t\tResponseWriter: resWr,\n\t}\n\n\tres.WriteHeader(http.StatusOK)\n\tres.WriteHeader(http.StatusInternalServerError)\n\n\tr.Equal(res.Status, http.StatusOK)\n}\n"
  },
  {
    "path": "route.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/url\"\n\t\"slices\"\n\t\"strings\"\n)\n\n// Routes returns a list of all of the routes defined\n// in this application.\nfunc (a *App) Routes() RouteList {\n\t// CHKME: why this function is exported? can we deprecate it?\n\tif a.root != nil {\n\t\treturn a.root.routes\n\t}\n\treturn a.routes\n}\n\nfunc addExtraParamsTo(path string, opts map[string]any) string {\n\tpendingParams := map[string]string{}\n\tkeys := []string{}\n\tfor k, v := range opts {\n\t\tif strings.Contains(path, fmt.Sprintf(\"%v\", v)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tkeys = append(keys, k)\n\t\tpendingParams[k] = fmt.Sprintf(\"%v\", v)\n\t}\n\n\tif len(keys) == 0 {\n\t\treturn path\n\t}\n\n\tif !strings.Contains(path, \"?\") {\n\t\tpath = path + \"?\"\n\t} else {\n\t\tif !strings.HasSuffix(path, \"?\") {\n\t\t\tpath = path + \"&\"\n\t\t}\n\t}\n\n\tslices.Sort(keys)\n\n\tfor index, k := range keys {\n\t\tformat := \"%v=%v\"\n\n\t\tif index > 0 {\n\t\t\tformat = \"&%v=%v\"\n\t\t}\n\n\t\tpath = path + fmt.Sprintf(format, url.QueryEscape(k), url.QueryEscape(pendingParams[k]))\n\t}\n\n\treturn path\n}\n\n// RouteHelperFunc represents the function that takes the route and the opts and build the path\ntype RouteHelperFunc func(opts map[string]any) (template.HTML, error)\n\n// RouteList contains a mapping of the routes defined\n// in the application. This listing contains, Method, Path,\n// and the name of the Handler defined to process that route.\ntype RouteList []*RouteInfo\n\nvar methodOrder = map[string]string{\n\t\"GET\":    \"1\",\n\t\"POST\":   \"2\",\n\t\"PUT\":    \"3\",\n\t\"DELETE\": \"4\",\n}\n\nfunc (a RouteList) Len() int      { return len(a) }\nfunc (a RouteList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a RouteList) Less(i, j int) bool {\n\t// NOTE: it was used for sorting of app.routes but we don't sort the routes anymore.\n\t// keep it for compatibility but could be deprecated.\n\tx := a[i].App.host + a[i].Path + methodOrder[a[i].Method]\n\ty := a[j].App.host + a[j].Path + methodOrder[a[j].Method]\n\treturn x < y\n}\n\n// Lookup search a specific PathName in the RouteList and return the *RouteInfo\nfunc (a RouteList) Lookup(name string) (*RouteInfo, error) {\n\tfor _, ri := range a {\n\t\tif ri.PathName == name {\n\t\t\treturn ri, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"path name not found\")\n}\n"
  },
  {
    "path": "route_info.go",
    "content": "package buffalo\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/flect\"\n\n\t\"github.com/gobuffalo/events\"\n\t\"github.com/gorilla/mux\"\n)\n\n// RouteInfo provides information about the underlying route that\n// was built.\ntype RouteInfo struct {\n\tMethod       string     `json:\"method\"`\n\tPath         string     `json:\"path\"`\n\tHandlerName  string     `json:\"handler\"`\n\tResourceName string     `json:\"resourceName,omitempty\"`\n\tPathName     string     `json:\"pathName\"`\n\tAliases      []string   `json:\"aliases\"`\n\tMuxRoute     *mux.Route `json:\"-\"`\n\tHandler      Handler    `json:\"-\"`\n\tApp          *App       `json:\"-\"`\n}\n\n// String returns a JSON representation of the RouteInfo\nfunc (ri RouteInfo) String() string {\n\tb, _ := json.MarshalIndent(ri, \"\", \"  \")\n\treturn string(b)\n}\n\n// Alias path patterns to the this route. This is not the\n// same as a redirect.\nfunc (ri *RouteInfo) Alias(aliases ...string) *RouteInfo {\n\tri.Aliases = append(ri.Aliases, aliases...)\n\tfor _, a := range aliases {\n\t\tri.App.router.Handle(a, ri).Methods(ri.Method)\n\t}\n\treturn ri\n}\n\n// Name allows users to set custom names for the routes.\nfunc (ri *RouteInfo) Name(name string) *RouteInfo {\n\trouteIndex := -1\n\tfor index, route := range ri.App.Routes() {\n\t\tif route.App.host == ri.App.host && route.Path == ri.Path && route.Method == ri.Method {\n\t\t\trouteIndex = index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tname = flect.Camelize(name)\n\n\tif !strings.HasSuffix(name, \"Path\") {\n\t\tname = name + \"Path\"\n\t}\n\n\tri.PathName = name\n\tif routeIndex != -1 {\n\t\tri.App.Routes()[routeIndex] = reflect.ValueOf(ri).Interface().(*RouteInfo)\n\t}\n\n\treturn ri\n}\n\n// BuildPathHelper Builds a routeHelperfunc for a particular RouteInfo\nfunc (ri *RouteInfo) BuildPathHelper() RouteHelperFunc {\n\tcRoute := ri\n\treturn func(opts map[string]any) (template.HTML, error) {\n\t\tpairs := []string{}\n\t\tfor k, v := range opts {\n\t\t\tpairs = append(pairs, k)\n\t\t\tpairs = append(pairs, fmt.Sprintf(\"%v\", v))\n\t\t}\n\n\t\turl, err := cRoute.MuxRoute.URL(pairs...)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"missing parameters for %v: %s\", cRoute.Path, err)\n\t\t}\n\n\t\tresult := url.String()\n\t\tresult = addExtraParamsTo(result, opts)\n\n\t\treturn template.HTML(result), nil\n\t}\n}\n\nfunc (ri RouteInfo) ServeHTTP(res http.ResponseWriter, req *http.Request) {\n\ta := ri.App\n\n\tc := a.newContext(ri, res, req)\n\tpayload := events.Payload{\n\t\t\"route\":   ri,\n\t\t\"app\":     a,\n\t\t\"context\": c,\n\t}\n\n\tevents.EmitPayload(EvtRouteStarted, payload)\n\terr := a.Middleware.handler(ri)(c)\n\n\tif err != nil {\n\t\tstatus := http.StatusInternalServerError\n\t\tvar he HTTPError\n\t\tif errors.As(err, &he) {\n\t\t\tstatus = he.Status\n\t\t}\n\t\tevents.EmitError(EvtRouteErr, err, payload)\n\t\t// things have really hit the fan if we're here!!\n\t\ta.Logger.Error(err)\n\t\tc.Response().WriteHeader(status)\n\t\tc.Response().Write([]byte(err.Error()))\n\t}\n\n\tevents.EmitPayload(EvtRouteFinished, payload)\n}\n"
  },
  {
    "path": "route_info_test.go",
    "content": "package buffalo\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_RouteInfo_ServeHTTP_SQL_Error(t *testing.T) {\n\tr := require.New(t)\n\n\tapp := New(Options{})\n\tapp.GET(\"/good\", func(c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"hi\"))\n\t})\n\n\tapp.GET(\"/bad\", func(c Context) error {\n\t\treturn sql.ErrNoRows\n\t})\n\n\tapp.GET(\"/bad-2\", func(c Context) error {\n\t\treturn sql.ErrTxDone\n\t})\n\n\tapp.GET(\"/gone-unwrap\", func(c Context) error {\n\t\treturn c.Error(http.StatusGone, sql.ErrTxDone)\n\t})\n\n\tapp.GET(\"/gone-wrap\", func(c Context) error {\n\t\treturn c.Error(http.StatusGone, fmt.Errorf(\"some error wrapping here: %w\", sql.ErrNoRows))\n\t})\n\n\tw := httptest.New(app)\n\n\tres := w.HTML(\"/good\").Get()\n\tr.Equal(http.StatusOK, res.Code)\n\n\tres = w.HTML(\"/bad\").Get()\n\tr.Equal(http.StatusNotFound, res.Code)\n\n\tres = w.HTML(\"/gone-wrap\").Get()\n\tr.Equal(http.StatusGone, res.Code)\n\n\tres = w.HTML(\"/gone-unwrap\").Get()\n\tr.Equal(http.StatusGone, res.Code)\n}\n"
  },
  {
    "path": "route_mappings.go",
    "content": "package buffalo\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gobuffalo/flect/name\"\n\t\"github.com/gorilla/handlers\"\n)\n\nconst (\n\t// AssetsAgeVarName is the ENV variable used to specify max age when ServeFiles is used.\n\tAssetsAgeVarName = \"ASSETS_MAX_AGE\"\n)\n\n// These method functions will be moved to Home structure.\n\n// GET maps an HTTP \"GET\" request to the path and the specified handler.\nfunc (a *App) GET(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"GET\", p, h)\n}\n\n// POST maps an HTTP \"POST\" request to the path and the specified handler.\nfunc (a *App) POST(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"POST\", p, h)\n}\n\n// PUT maps an HTTP \"PUT\" request to the path and the specified handler.\nfunc (a *App) PUT(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"PUT\", p, h)\n}\n\n// DELETE maps an HTTP \"DELETE\" request to the path and the specified handler.\nfunc (a *App) DELETE(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"DELETE\", p, h)\n}\n\n// HEAD maps an HTTP \"HEAD\" request to the path and the specified handler.\nfunc (a *App) HEAD(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"HEAD\", p, h)\n}\n\n// OPTIONS maps an HTTP \"OPTIONS\" request to the path and the specified handler.\nfunc (a *App) OPTIONS(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"OPTIONS\", p, h)\n}\n\n// PATCH maps an HTTP \"PATCH\" request to the path and the specified handler.\nfunc (a *App) PATCH(p string, h Handler) *RouteInfo {\n\treturn a.addRoute(\"PATCH\", p, h)\n}\n\n// Redirect from one URL to another URL. Only works for \"GET\" requests.\nfunc (a *App) Redirect(status int, from, to string) *RouteInfo {\n\treturn a.GET(from, func(c Context) error {\n\t\treturn c.Redirect(status, to)\n\t})\n}\n\n// Mount mounts a http.Handler (or Buffalo app) and passes through all requests to it.\n//\n//\tfunc muxer() http.Handler {\n//\t\tf := func(res http.ResponseWriter, req *http.Request) {\n//\t\t\tfmt.Fprintf(res, \"%s - %s\", req.Method, req.URL.String())\n//\t\t}\n//\t\tmux := mux.NewRouter()\n//\t\tmux.HandleFunc(\"/foo\", f).Methods(\"GET\")\n//\t\tmux.HandleFunc(\"/bar\", f).Methods(\"POST\")\n//\t\tmux.HandleFunc(\"/baz/baz\", f).Methods(\"DELETE\")\n//\t\treturn mux\n//\t}\n//\n//\ta.Mount(\"/admin\", muxer())\n//\n//\t$ curl -X DELETE http://localhost:3000/admin/baz/baz\nfunc (a *App) Mount(p string, h http.Handler) {\n\tprefix := path.Join(a.Prefix, p)\n\tpath := path.Join(p, \"{path:.+}\")\n\ta.ANY(path, WrapHandler(http.StripPrefix(prefix, h)))\n}\n\n// ServeFiles maps an path to a directory on disk to serve static files.\n// Useful for JavaScript, images, CSS, etc...\n/*\n\ta.ServeFiles(\"/assets\", http.Dir(\"path/to/assets\"))\n*/\nfunc (a *App) ServeFiles(p string, root http.FileSystem) {\n\tpath := path.Join(a.Prefix, p)\n\ta.filepaths = append(a.filepaths, path)\n\n\th := stripAsset(path, a.fileServer(root), a)\n\ta.router.PathPrefix(path).Handler(h)\n}\n\nfunc (a *App) fileServer(fs http.FileSystem) http.Handler {\n\tfsh := http.FileServer(fs)\n\tbaseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tf, err := fs.Open(path.Clean(r.URL.Path))\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\teh := a.ErrorHandlers.Get(http.StatusNotFound)\n\t\t\teh(http.StatusNotFound, fmt.Errorf(\"could not find %s\", r.URL.Path), a.newContext(RouteInfo{}, w, r))\n\t\t\treturn\n\t\t}\n\n\t\tstat, _ := f.Stat()\n\t\tmaxAge := env.Get(AssetsAgeVarName, \"31536000\")\n\t\tw.Header().Add(\"ETag\", fmt.Sprintf(\"%x\", stat.ModTime().UnixNano()))\n\t\tw.Header().Add(\"Cache-Control\", fmt.Sprintf(\"max-age=%s\", maxAge))\n\t\tfsh.ServeHTTP(w, r)\n\t})\n\n\tif a.CompressFiles {\n\t\treturn handlers.CompressHandler(baseHandler)\n\t}\n\n\treturn baseHandler\n}\n\ntype newable interface {\n\tNew(Context) error\n}\n\ntype editable interface {\n\tEdit(Context) error\n}\n\n// Resource maps an implementation of the Resource interface\n// to the appropriate RESTful mappings. Resource returns the *App\n// associated with this group of mappings so you can set middleware, etc...\n// on that group, just as if you had used the a.Group functionality.\n//\n// Resource automatically creates a URL `/resources/new` if the resource\n// has a function `New()`. So it could act as a restriction for the value\n// of `resource_id`. URL `/resources/new` will always show the resource\n// creation page instead of showing the resource called `new`.\n/*\n\ta.Resource(\"/users\", &UsersResource{})\n\n\t// Is equal to this:\n\n\tur := &UsersResource{}\n\tg := a.Group(\"/users\")\n\tg.GET(\"/\", ur.List) // GET /users => ur.List\n\tg.POST(\"/\", ur.Create) // POST /users => ur.Create\n\tg.GET(\"/new\", ur.New) // GET /users/new => ur.New\n\tg.GET(\"/{user_id}\", ur.Show) // GET /users/{user_id} => ur.Show\n\tg.PUT(\"/{user_id}\", ur.Update) // PUT /users/{user_id} => ur.Update\n\tg.DELETE(\"/{user_id}\", ur.Destroy) // DELETE /users/{user_id} => ur.Destroy\n\tg.GET(\"/{user_id}/edit\", ur.Edit) // GET /users/{user_id}/edit => ur.Edit\n*/\nfunc (a *App) Resource(p string, r Resource) *App {\n\tg := a.Group(p)\n\n\tif mw, ok := r.(Middler); ok {\n\t\tg.Use(mw.Use()...)\n\t}\n\n\tp = \"/\"\n\n\trv := reflect.ValueOf(r)\n\tif rv.Kind() == reflect.Ptr {\n\t\trv = rv.Elem()\n\t}\n\n\trt := rv.Type()\n\tresourceName := rt.Name()\n\thandlerName := fmt.Sprintf(\"%s.%s\", rt.PkgPath(), resourceName) + \".%s\"\n\n\tn := strings.TrimSuffix(rt.Name(), \"Resource\")\n\tparamName := name.New(n).ParamID().String()\n\n\ttype paramKeyable interface {\n\t\tParamKey() string\n\t}\n\n\tif pk, ok := r.(paramKeyable); ok {\n\t\tparamName = pk.ParamKey()\n\t}\n\n\tspath := path.Join(p, \"{\"+paramName+\"}\")\n\n\t// This order will become the order of route evaluation too.\n\tsetFuncKey(r.List, fmt.Sprintf(handlerName, \"List\"))\n\tg.GET(p, r.List).ResourceName = resourceName\n\n\tsetFuncKey(r.Create, fmt.Sprintf(handlerName, \"Create\"))\n\tg.POST(p, r.Create).ResourceName = resourceName\n\n\t// NOTE: it makes restriction that resource id cannot be 'new'.\n\tif n, ok := r.(newable); ok {\n\t\tsetFuncKey(n.New, fmt.Sprintf(handlerName, \"New\"))\n\t\tg.GET(path.Join(p, \"new\"), n.New).ResourceName = resourceName\n\t}\n\n\tsetFuncKey(r.Show, fmt.Sprintf(handlerName, \"Show\"))\n\tg.GET(path.Join(spath), r.Show).ResourceName = resourceName\n\n\tsetFuncKey(r.Update, fmt.Sprintf(handlerName, \"Update\"))\n\tg.PUT(path.Join(spath), r.Update).ResourceName = resourceName\n\n\tsetFuncKey(r.Destroy, fmt.Sprintf(handlerName, \"Destroy\"))\n\tg.DELETE(path.Join(spath), r.Destroy).ResourceName = resourceName\n\n\tif n, ok := r.(editable); ok {\n\t\tsetFuncKey(n.Edit, fmt.Sprintf(handlerName, \"Edit\"))\n\t\tg.GET(path.Join(spath, \"edit\"), n.Edit).ResourceName = resourceName\n\t}\n\n\tg.Prefix = path.Join(g.Prefix, spath)\n\tg.prefix = g.Prefix\n\n\treturn g\n}\n\n// ANY accepts a request across any HTTP method for the specified path\n// and routes it to the specified Handler.\nfunc (a *App) ANY(p string, h Handler) {\n\ta.GET(p, h)\n\ta.POST(p, h)\n\ta.PUT(p, h)\n\ta.PATCH(p, h)\n\ta.HEAD(p, h)\n\ta.OPTIONS(p, h)\n\ta.DELETE(p, h)\n}\n\n// Group creates a new `*App` that inherits from it's parent `*App`.\n// This is useful for creating groups of end-points that need to share\n// common functionality, like middleware.\n/*\n\tg := a.Group(\"/api/v1\")\n\tg.Use(AuthorizeAPIMiddleware)\n\tg.GET(\"/users, APIUsersHandler)\n\tg.GET(\"/users/:user_id, APIUserShowHandler)\n*/\nfunc (a *App) Group(groupPath string) *App {\n\t// TODO: move this function to app.go or home.go eventually.\n\tg := New(a.Options)\n\t// keep them for v0 compatibility\n\tg.Prefix = path.Join(a.Prefix, groupPath)\n\tg.Name = g.Prefix\n\n\t// for Home structure\n\tg.prefix = path.Join(a.prefix, groupPath)\n\tg.host = a.host\n\tg.name = g.prefix\n\n\tg.router = a.router\n\tg.RouteNamer = a.RouteNamer\n\tg.Middleware = a.Middleware.clone()\n\tg.ErrorHandlers = a.ErrorHandlers\n\n\tg.app = a.app  // will replace g.root\n\tg.root = g.app // will be deprecated\n\n\t// to be replaced with child Homes. currently, only used in grifts.\n\ta.children = append(a.children, g)\n\treturn g\n}\n\n// VirtualHost creates a new `*App` that inherits from it's parent `*App`.\n// All pre-configured things on the parent App such as middlewares will be\n// applied, and can be modified only for this child App.\n//\n// This is a multi-homing feature similar to the `VirtualHost` in Apache\n// or multiple `server`s in nginx. One important different behavior is that\n// there is no concept of the `default` host in buffalo (at least for now)\n// and the routing decision will be made with the \"first match\" manner.\n// (e.g. if you have already set the route for '/' for the root App before\n// setting up a virualhost, the route of the root App will be picked up\n// even if the client makes a request to the specified domain.)\n/*\n\ta.VirtualHost(\"www.example.com\")\n\ta.VirtualHost(\"{subdomain}.example.com\")\n\ta.VirtualHost(\"{subdomain:[a-z]+}.example.com\")\n*/\nfunc (a *App) VirtualHost(h string) *App {\n\tg := a.Group(\"/\")\n\tg.host = h\n\tg.router = a.router.Host(h).Subrouter()\n\n\treturn g\n}\n\n// RouteHelpers returns a map of BuildPathHelper() for each route available in the app.\nfunc (a *App) RouteHelpers() map[string]RouteHelperFunc {\n\trh := map[string]RouteHelperFunc{}\n\tfor _, route := range a.Routes() {\n\t\tcRoute := route\n\t\trh[cRoute.PathName] = cRoute.BuildPathHelper()\n\t}\n\treturn rh\n}\n\nfunc (e *Home) addRoute(method string, url string, h Handler) *RouteInfo {\n\t// NOTE: lock the root app (not this app). only the root has the affective\n\t// routes list.\n\te.app.moot.Lock()\n\tdefer e.app.moot.Unlock()\n\n\turl = path.Join(e.prefix, url)\n\turl = e.app.normalizePath(url)\n\tname := e.app.RouteNamer.NameRoute(url)\n\n\ths := funcKey(h)\n\tr := &RouteInfo{\n\t\tMethod:      method,\n\t\tPath:        url,\n\t\tHandlerName: hs,\n\t\tHandler:     h,\n\t\tApp:         e.appSelf, // CHKME: to be replaced with Home\n\t\tAliases:     []string{},\n\t}\n\n\tr.MuxRoute = e.router.Handle(url, r).Methods(method)\n\tr.Name(name)\n\n\troutes := e.app.Routes()\n\troutes = append(routes, r)\n\t// NOTE: sorting is fancy but we lose the evaluation order information\n\t// of routing decision. Let's keep the routes as registered order so\n\t// developers can easily evaluate the order with `buffalo routes` and\n\t// can debug any routing priority issue. (just keep the original line\n\t// as history reference)\n\t//sort.Sort(routes)\n\n\te.app.routes = routes\n\n\treturn r\n}\n\nfunc stripAsset(path string, h http.Handler, a *App) http.Handler {\n\tif path == \"\" {\n\t\treturn h\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tup := r.URL.Path\n\t\tup = strings.TrimPrefix(up, path)\n\t\tup = strings.TrimSuffix(up, \"/\")\n\n\t\tu, err := url.Parse(up)\n\t\tif err != nil {\n\t\t\teh := a.ErrorHandlers.Get(http.StatusBadRequest)\n\t\t\teh(http.StatusBadRequest, err, a.newContext(RouteInfo{}, w, r))\n\t\t\treturn\n\t\t}\n\n\t\tr.URL = u\n\t\th.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "route_mappings_test.go",
    "content": "package buffalo\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_App_Routes_without_Root(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tr.Nil(a.root)\n\n\ta.GET(\"/foo\", voidHandler)\n\n\troutes := a.Routes()\n\tr.Len(routes, 1)\n\troute := routes[0]\n\tr.Equal(\"GET\", route.Method)\n\tr.Equal(\"/foo/\", route.Path)\n\tr.NotZero(route.HandlerName)\n}\n\nfunc Test_App_Routes_with_Root(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tr.Nil(a.root)\n\n\tg := a.Group(\"/api/v1\")\n\tg.GET(\"/foo\", voidHandler)\n\n\troutes := a.Routes()\n\tr.Len(routes, 1)\n\troute := routes[0]\n\tr.Equal(\"GET\", route.Method)\n\tr.Equal(\"/api/v1/foo/\", route.Path)\n\tr.NotZero(route.HandlerName)\n\n\tr.Equal(a.Routes(), g.Routes())\n}\n\nfunc Test_App_RouteName(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\n\tcases := map[string]string{\n\t\t\"cool\":                \"coolPath\",\n\t\t\"coolPath\":            \"coolPath\",\n\t\t\"coco_path\":           \"cocoPath\",\n\t\t\"ouch_something_cool\": \"ouchSomethingCoolPath\",\n\t}\n\n\tri := a.GET(\"/something\", voidHandler)\n\tfor k, v := range cases {\n\t\tri.Name(k)\n\t\tr.Equal(ri.PathName, v)\n\t}\n\n}\n\nfunc Test_RouteList_Lookup(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tr.Nil(a.root)\n\n\ta.GET(\"/foo\", voidHandler)\n\ta.GET(\"/test\", voidHandler)\n\n\troutes := a.Routes()\n\tfor _, route := range routes {\n\t\tlRoute, err := routes.Lookup(route.PathName)\n\t\tr.NoError(err)\n\t\tr.Equal(lRoute, route)\n\t}\n\tlRoute, err := routes.Lookup(\"a\")\n\tr.Error(err)\n\tr.Nil(lRoute)\n\n}\n\nfunc Test_App_RouteHelpers(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tr.Nil(a.root)\n\n\ta.GET(\"/foo\", voidHandler)\n\ta.GET(\"/test/{id}\", voidHandler)\n\n\trh := a.RouteHelpers()\n\n\tr.Len(rh, 2)\n\n\tf, ok := rh[\"fooPath\"]\n\tr.True(ok)\n\tx, err := f(map[string]any{})\n\tr.NoError(err)\n\tr.Equal(\"/foo/\", string(x))\n\n\tf, ok = rh[\"testPath\"]\n\tr.True(ok)\n\tx, err = f(map[string]any{\n\t\t\"id\": 1,\n\t})\n\tr.NoError(err)\n\tr.Equal(\"/test/1/\", string(x))\n}\n\ntype resourceHandler struct{}\n\nfunc (r resourceHandler) List(Context) error {\n\treturn nil\n}\n\nfunc (r resourceHandler) Show(Context) error {\n\treturn nil\n}\n\nfunc (r resourceHandler) Create(Context) error {\n\treturn nil\n}\n\nfunc (r resourceHandler) Update(Context) error {\n\treturn nil\n}\n\nfunc (r resourceHandler) Destroy(Context) error {\n\treturn nil\n}\n\nfunc Test_App_Routes_Resource(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tr.Nil(a.root)\n\n\ta.GET(\"/foo\", voidHandler)\n\ta.Resource(\"/r\", resourceHandler{})\n\n\troutes := a.Routes()\n\tr.Len(routes, 6)\n\troute := routes[0]\n\tr.Equal(\"GET\", route.Method)\n\tr.Equal(\"/foo/\", route.Path)\n\tr.NotZero(route.HandlerName)\n\n\tfor k, v := range routes {\n\t\tif k > 0 {\n\t\t\tr.Equal(\"resourceHandler\", v.ResourceName)\n\t\t}\n\t}\n}\n\nfunc Test_App_VirtualHost(t *testing.T) {\n\tr := require.New(t)\n\n\ta1 := New(Options{})\n\tr.Nil(a1.root)\n\n\th1 := a1.VirtualHost(\"www.example.com\")\n\th1.GET(\"/foo\", voidHandler)\n\n\troutes := h1.Routes()\n\tr.Len(routes, 1)\n\n\troute := routes[0]\n\tr.Equal(\"GET\", route.Method)\n\tr.Equal(\"/foo/\", route.Path)\n\tr.NotZero(route.HandlerName)\n\n\t// With Regular Expressions\n\n\ta2 := New(Options{})\n\tr.Nil(a1.root)\n\n\th2 := a2.VirtualHost(\"{subdomain}.example.com\")\n\th2.GET(\"/foo\", voidHandler)\n\th2.GET(\"/foo/{id}\", voidHandler).Name(\"fooID\")\n\n\trh := h2.RouteHelpers()\n\n\troutes = h2.Routes()\n\tr.Len(routes, 2)\n\n\tr.Equal(\"GET\", routes[0].Method)\n\tr.Equal(\"/foo/\", routes[0].Path)\n\tr.NotZero(routes[0].HandlerName)\n\n\tr.Equal(\"GET\", routes[1].Method)\n\tr.Equal(\"/foo/{id}/\", routes[1].Path)\n\tr.NotZero(routes[1].HandlerName)\n\n\tf, ok := rh[\"fooPath\"]\n\tr.True(ok)\n\tx, err := f(map[string]any{\n\t\t\"subdomain\": \"test\",\n\t})\n\tr.NoError(err)\n\tr.Equal(\"http://test.example.com/foo/\", string(x))\n\n\tf, ok = rh[\"fooIDPath\"]\n\tr.True(ok)\n\tx, err = f(map[string]any{\n\t\t\"subdomain\": \"test\",\n\t\t\"id\":        1,\n\t})\n\tr.NoError(err)\n\tr.Equal(\"http://test.example.com/foo/1/\", string(x))\n}\n"
  },
  {
    "path": "routenamer.go",
    "content": "package buffalo\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gobuffalo/flect\"\n\t\"github.com/gobuffalo/flect/name\"\n)\n\n// RouteNamer is in charge of naming a route from the\n// path assigned, this name typically will be used if no\n// name is assined with .Name(...).\ntype RouteNamer interface {\n\t// NameRoute receives the path and returns the name\n\t// for the route.\n\tNameRoute(string) string\n}\n\n// BaseRouteNamer is the default route namer used by apps.\ntype baseRouteNamer struct{}\n\nfunc (drn baseRouteNamer) NameRoute(p string) string {\n\tif p == \"/\" || p == \"\" {\n\t\treturn \"root\"\n\t}\n\n\tresultParts := []string{}\n\tparts := strings.Split(p, \"/\")\n\n\tfor index, part := range parts {\n\n\t\toriginalPart := parts[index]\n\n\t\tvar previousPart string\n\t\tif index > 0 {\n\t\t\tpreviousPart = parts[index-1]\n\t\t}\n\n\t\tvar nextPart string\n\t\tif len(parts) > index+1 {\n\t\t\tnextPart = parts[index+1]\n\t\t}\n\n\t\tisIdentifierPart := strings.Contains(part, \"{\") && (strings.Contains(part, flect.Singularize(previousPart)))\n\t\tisSimplifiedID := part == `{id}`\n\n\t\tif isIdentifierPart || isSimplifiedID || part == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.Contains(nextPart, \"{\") {\n\t\t\tpart = flect.Singularize(part)\n\t\t}\n\n\t\tif originalPart == \"new\" || originalPart == \"edit\" {\n\t\t\tresultParts = append([]string{part}, resultParts...)\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.Contains(previousPart, \"}\") {\n\t\t\tresultParts = append(resultParts, part)\n\t\t\tcontinue\n\t\t}\n\n\t\tresultParts = append(resultParts, part)\n\t}\n\n\tif len(resultParts) == 0 {\n\t\treturn \"unnamed\"\n\t}\n\n\tunderscore := strings.TrimSpace(strings.Join(resultParts, \"_\"))\n\treturn name.VarCase(underscore)\n}\n"
  },
  {
    "path": "router_test.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/fstest\"\n\n\t\"github.com/gobuffalo/buffalo/internal/env\"\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc testApp() *App {\n\ta := New(Options{})\n\ta.Redirect(http.StatusMovedPermanently, \"/foo\", \"/bar\")\n\ta.GET(\"/bar\", func(c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"bar\"))\n\t})\n\n\trt := a.Group(\"/router/tests\")\n\n\th := func(c Context) error {\n\t\tx := c.Request().Method + \"|\"\n\t\tx += strings.TrimSuffix(c.Value(\"current_path\").(string), \"/\")\n\t\treturn c.Render(http.StatusOK, render.String(x))\n\t}\n\n\trt.GET(\"/\", h)\n\trt.POST(\"/\", h)\n\trt.PUT(\"/\", h)\n\trt.DELETE(\"/\", h)\n\trt.OPTIONS(\"/\", h)\n\trt.PATCH(\"/\", h)\n\n\ta.ErrorHandlers[http.StatusMethodNotAllowed] = func(status int, err error, c Context) error {\n\t\tres := c.Response()\n\t\tres.WriteHeader(status)\n\t\tres.Write([]byte(\"my custom 405\"))\n\t\treturn nil\n\t}\n\treturn a\n}\n\nfunc otherTestApp() *App {\n\ta := New(Options{})\n\tf := func(c Context) error {\n\t\treq := c.Request()\n\t\treturn c.Render(http.StatusOK, render.String(req.Method+\" - \"+req.URL.String()))\n\t}\n\ta.GET(\"/foo\", f)\n\ta.POST(\"/bar\", f)\n\ta.DELETE(\"/baz/baz\", f)\n\treturn a\n}\n\nfunc Test_MethodNotFoundError(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.GET(\"/bar\", func(c Context) error {\n\t\treturn c.Render(http.StatusOK, render.String(\"bar\"))\n\t})\n\ta.ErrorHandlers[http.StatusMethodNotAllowed] = func(status int, err error, c Context) error {\n\t\tres := c.Response()\n\t\tres.WriteHeader(status)\n\t\tres.Write([]byte(\"my custom 405\"))\n\t\treturn nil\n\t}\n\tw := httptest.New(a)\n\tres := w.HTML(\"/bar\").Post(nil)\n\tr.Equal(http.StatusMethodNotAllowed, res.Code)\n\tr.Contains(res.Body.String(), \"my custom 405\")\n}\n\nfunc Test_Mount_Buffalo(t *testing.T) {\n\tr := require.New(t)\n\ta := testApp()\n\ta.Mount(\"/admin\", otherTestApp())\n\n\ttable := map[string]string{\n\t\t\"/foo\":     \"GET\",\n\t\t\"/bar\":     \"POST\",\n\t\t\"/baz/baz\": \"DELETE\",\n\t}\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\tfor u, m := range table {\n\t\tp := fmt.Sprintf(\"%s/%s\", ts.URL, path.Join(\"admin\", u))\n\t\treq, err := http.NewRequest(m, p, nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, _ := io.ReadAll(res.Body)\n\t\tr.Equal(fmt.Sprintf(\"%s - %s/\", m, u), string(b))\n\t}\n}\n\nfunc Test_Mount_Buffalo_on_Group(t *testing.T) {\n\tr := require.New(t)\n\ta := testApp()\n\tg := a.Group(\"/users\")\n\tg.Mount(\"/admin\", otherTestApp())\n\n\ttable := map[string]string{\n\t\t\"/foo\":     \"GET\",\n\t\t\"/bar\":     \"POST\",\n\t\t\"/baz/baz\": \"DELETE\",\n\t}\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\tfor u, m := range table {\n\t\tp := fmt.Sprintf(\"%s/%s\", ts.URL, path.Join(\"users\", \"admin\", u))\n\t\treq, err := http.NewRequest(m, p, nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, _ := io.ReadAll(res.Body)\n\t\tr.Equal(fmt.Sprintf(\"%s - %s/\", m, u), string(b))\n\t}\n}\n\nfunc muxer() http.Handler {\n\tf := func(res http.ResponseWriter, req *http.Request) {\n\t\tfmt.Fprintf(res, \"%s - %s\", req.Method, req.URL.String())\n\t}\n\tmux := mux.NewRouter()\n\tmux.HandleFunc(\"/foo/\", f).Methods(\"GET\")\n\tmux.HandleFunc(\"/bar/\", f).Methods(\"POST\")\n\tmux.HandleFunc(\"/baz/baz/\", f).Methods(\"DELETE\")\n\treturn mux\n}\n\nfunc Test_Mount_Handler(t *testing.T) {\n\tr := require.New(t)\n\ta := testApp()\n\ta.Mount(\"/admin\", muxer())\n\n\ttable := map[string]string{\n\t\t\"/foo\":     \"GET\",\n\t\t\"/bar\":     \"POST\",\n\t\t\"/baz/baz\": \"DELETE\",\n\t}\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\tfor u, m := range table {\n\t\tp := fmt.Sprintf(\"%s/%s\", ts.URL, path.Join(\"admin\", u))\n\t\treq, err := http.NewRequest(m, p, nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, _ := io.ReadAll(res.Body)\n\t\tr.Equal(fmt.Sprintf(\"%s - %s/\", m, u), string(b))\n\t}\n}\n\nfunc Test_PreHandlers(t *testing.T) {\n\tr := require.New(t)\n\ta := testApp()\n\tbh := func(c Context) error {\n\t\treq := c.Request()\n\t\treturn c.Render(http.StatusOK, render.String(req.Method+\"-\"+req.URL.String()))\n\t}\n\ta.GET(\"/ph\", bh)\n\ta.POST(\"/ph\", bh)\n\tmh := func(res http.ResponseWriter, req *http.Request) {\n\t\tif req.Method == \"GET\" {\n\t\t\tres.WriteHeader(http.StatusTeapot)\n\t\t\tres.Write([]byte(\"boo\"))\n\t\t}\n\t}\n\ta.PreHandlers = append(a.PreHandlers, http.HandlerFunc(mh))\n\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\ttable := []struct {\n\t\tCode   int\n\t\tMethod string\n\t\tResult string\n\t}{\n\t\t{Code: http.StatusTeapot, Method: \"GET\", Result: \"boo\"},\n\t\t{Code: http.StatusOK, Method: \"POST\", Result: \"POST-/ph/\"},\n\t}\n\n\tfor _, v := range table {\n\t\treq, err := http.NewRequest(v.Method, ts.URL+\"/ph\", nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, err := io.ReadAll(res.Body)\n\t\tr.NoError(err)\n\t\tr.Equal(v.Code, res.StatusCode)\n\t\tr.Equal(v.Result, string(b))\n\t}\n}\n\nfunc Test_PreWares(t *testing.T) {\n\tr := require.New(t)\n\ta := testApp()\n\tbh := func(c Context) error {\n\t\treq := c.Request()\n\t\treturn c.Render(http.StatusOK, render.String(req.Method+\"-\"+req.URL.String()))\n\t}\n\ta.GET(\"/ph\", bh)\n\ta.POST(\"/ph\", bh)\n\n\tmh := func(h http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\t\tif req.Method == \"GET\" {\n\t\t\t\tres.WriteHeader(http.StatusTeapot)\n\t\t\t\tres.Write([]byte(\"boo\"))\n\t\t\t}\n\t\t})\n\t}\n\n\ta.PreWares = append(a.PreWares, mh)\n\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\ttable := []struct {\n\t\tCode   int\n\t\tMethod string\n\t\tResult string\n\t}{\n\t\t{Code: http.StatusTeapot, Method: \"GET\", Result: \"boo\"},\n\t\t{Code: http.StatusOK, Method: \"POST\", Result: \"POST-/ph/\"},\n\t}\n\n\tfor _, v := range table {\n\t\treq, err := http.NewRequest(v.Method, ts.URL+\"/ph\", nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, err := io.ReadAll(res.Body)\n\t\tr.NoError(err)\n\t\tr.Equal(v.Code, res.StatusCode)\n\t\tr.Equal(v.Result, string(b))\n\t}\n}\n\nfunc Test_Router(t *testing.T) {\n\tr := require.New(t)\n\n\ttable := []string{\n\t\t\"GET\",\n\t\t\"POST\",\n\t\t\"PUT\",\n\t\t\"DELETE\",\n\t\t\"OPTIONS\",\n\t\t\"PATCH\",\n\t}\n\n\tts := httptest.NewServer(testApp())\n\tdefer ts.Close()\n\n\tfor _, v := range table {\n\t\treq, err := http.NewRequest(v, fmt.Sprintf(\"%s/router/tests\", ts.URL), nil)\n\t\tr.NoError(err)\n\t\tres, err := http.DefaultClient.Do(req)\n\t\tr.NoError(err)\n\t\tb, _ := io.ReadAll(res.Body)\n\t\tr.Equal(fmt.Sprintf(\"%s|/router/tests\", v), string(b))\n\t}\n}\n\nfunc Test_Router_Group(t *testing.T) {\n\tr := require.New(t)\n\n\ta := testApp()\n\tg := a.Group(\"/api/v1\")\n\tg.GET(\"/users\", func(c Context) error {\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/api/v1/users\").Get()\n\tr.Equal(http.StatusCreated, res.Code)\n}\n\nfunc Test_Router_Group_on_Group(t *testing.T) {\n\tr := require.New(t)\n\n\ta := testApp()\n\tg := a.Group(\"/api/v1\")\n\tg.GET(\"/users\", func(c Context) error {\n\t\treturn c.Render(http.StatusCreated, nil)\n\t})\n\tf := g.Group(\"/foo\")\n\tf.GET(\"/bar\", func(c Context) error {\n\t\treturn c.Render(http.StatusTeapot, nil)\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/api/v1/foo/bar\").Get()\n\tr.Equal(http.StatusTeapot, res.Code)\n}\n\nfunc Test_Router_Group_Middleware(t *testing.T) {\n\tr := require.New(t)\n\n\ta := testApp()\n\ta.Use(func(h Handler) Handler { return h })\n\tr.Len(a.Middleware.stack, 4)\n\n\tg := a.Group(\"/api/v1\")\n\tr.Len(a.Middleware.stack, 4)\n\tr.Len(g.Middleware.stack, 4)\n\n\tg.Use(func(h Handler) Handler { return h })\n\tr.Len(a.Middleware.stack, 4)\n\tr.Len(g.Middleware.stack, 5)\n}\n\nfunc Test_Router_Redirect(t *testing.T) {\n\tr := require.New(t)\n\tw := httptest.New(testApp())\n\tres := w.HTML(\"/foo\").Get()\n\tr.Equal(http.StatusMovedPermanently, res.Code)\n\tr.Equal(\"/bar\", res.Location())\n}\n\nfunc Test_Router_ServeFiles(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"foo.png\": &fstest.MapFile{\n\t\t\tData: []byte(\"foo\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\ta := New(Options{})\n\ta.ServeFiles(\"/assets\", http.FS(rootFS))\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/assets/foo.png\").Get()\n\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Equal(\"foo\", res.Body.String())\n\n\tr.NotEqual(res.Header().Get(\"ETag\"), \"\")\n\tr.Equal(res.Header().Get(\"Cache-Control\"), \"max-age=31536000\")\n\n\tenv.Set(AssetsAgeVarName, \"3600\")\n\tw = httptest.New(a)\n\tres = w.HTML(\"/assets/foo.png\").Get()\n\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Equal(\"foo\", res.Body.String())\n\n\tr.NotEqual(res.Header().Get(\"ETag\"), \"\")\n\tr.Equal(res.Header().Get(\"Cache-Control\"), \"max-age=3600\")\n}\n\nfunc Test_Router_InvalidURL(t *testing.T) {\n\tr := require.New(t)\n\n\trootFS := fstest.MapFS{\n\t\t\"foo.png\": &fstest.MapFile{\n\t\t\tData: []byte(\"foo\"),\n\t\t\tMode: 0644,\n\t\t},\n\t}\n\ta := New(Options{})\n\ta.ServeFiles(\"/\", http.FS(rootFS))\n\n\tw := httptest.New(a)\n\ts := \"/%25%7dn2zq0%3cscript%3ealert(1)%3c\\\\/script%3evea7f\"\n\n\trequest, _ := http.NewRequest(\"GET\", s, nil)\n\tresponse := httptest.NewRecorder()\n\n\tw.ServeHTTP(response, request)\n\tr.Equal(http.StatusBadRequest, response.Code, \"(400) BadRequest response is expected\")\n}\n\ntype WebResource struct {\n\tBaseResource\n}\n\n// Edit default implementation. Returns a 404\nfunc (v WebResource) Edit(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\n// New default implementation. Returns a 404\nfunc (v WebResource) New(c Context) error {\n\treturn c.Error(http.StatusNotFound, fmt.Errorf(\"resource not implemented\"))\n}\n\nfunc Test_App_NamedRoutes(t *testing.T) {\n\n\ttype CarsResource struct {\n\t\tWebResource\n\t}\n\n\ttype ResourcesResource struct {\n\t\tWebResource\n\t}\n\n\tr := require.New(t)\n\ta := New(Options{})\n\n\tvar carsResource Resource = CarsResource{}\n\n\tvar resourcesResource Resource = ResourcesResource{}\n\n\trr := render.New(render.Options{\n\t\tHTMLLayout:  \"application.plush.html\",\n\t\tTemplatesFS: os.DirFS(\"../templates\"),\n\t\tHelpers:     map[string]any{},\n\t})\n\n\tsampleHandler := func(c Context) error {\n\t\tc.Set(\"opts\", map[string]any{})\n\t\treturn c.Render(http.StatusOK, rr.String(`\n\t\t\t1. <%= rootPath() %>\n\t\t\t2. <%= userPath({user_id: 1}) %>\n\t\t\t3. <%= myPeepsPath() %>\n\t\t\t5. <%= carPath({car_id: 1}) %>\n\t\t\t6. <%= newCarPath() %>\n\t\t\t7. <%= editCarPath({car_id: 1}) %>\n\t\t\t8. <%= editCarPath({car_id: 1, other: 12}) %>\n\t\t\t9. <%= rootPath({\"some\":\"variable\",\"other\": 12}) %>\n\t\t\t10. <%= rootPath() %>\n\t\t\t11. <%= rootPath({\"special/\":\"12=ss\"}) %>\n\t\t\t12. <%= resourcePath({resource_id: 1}) %>\n\t\t\t13. <%= editResourcePath({resource_id: 1}) %>\n\t\t\t14. <%= testPath() %>\n\t\t\t15. <%= testNamePath({name: \"myTest\"}) %>\n\t\t\t16. <%= paganoPath({id: 1}) %>\n\t\t`))\n\t}\n\n\ta.GET(\"/\", sampleHandler)\n\ta.GET(\"/users\", sampleHandler)\n\ta.GET(\"/users/{user_id}\", sampleHandler)\n\ta.GET(\"/peeps\", sampleHandler).Name(\"myPeeps\")\n\ta.Resource(\"/car\", carsResource)\n\ta.Resource(\"/resources\", resourcesResource)\n\ta.GET(\"/test\", sampleHandler)\n\ta.GET(\"/test/{name}\", sampleHandler)\n\ta.GET(\"/pagano/{id}\", sampleHandler)\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Contains(res.Body.String(), \"1. /\")\n\tr.Contains(res.Body.String(), \"2. /users/1\")\n\tr.Contains(res.Body.String(), \"3. /peeps\")\n\tr.Contains(res.Body.String(), \"5. /car/1\")\n\tr.Contains(res.Body.String(), \"6. /car/new\")\n\tr.Contains(res.Body.String(), \"7. /car/1/edit\")\n\tr.Contains(res.Body.String(), \"8. /car/1/edit/?other=12\")\n\tr.Contains(res.Body.String(), \"9. /?other=12&some=variable\")\n\tr.Contains(res.Body.String(), \"10. /\")\n\tr.Contains(res.Body.String(), \"11. /?special%2F=12%3Dss\")\n\tr.Contains(res.Body.String(), \"12. /resources/1\")\n\tr.Contains(res.Body.String(), \"13. /resources/1/edit\")\n\tr.Contains(res.Body.String(), \"14. /test\")\n\tr.Contains(res.Body.String(), \"15. /test/myTest\")\n\tr.Contains(res.Body.String(), \"16. /pagano/1\")\n}\n\nfunc Test_App_NamedRoutes_MissingParameter(t *testing.T) {\n\tr := require.New(t)\n\ta := New(Options{})\n\n\trr := render.New(render.Options{\n\t\tHTMLLayout:  \"application.plush.html\",\n\t\tTemplatesFS: os.DirFS(\"../templates\"),\n\t\tHelpers:     map[string]any{},\n\t})\n\n\tsampleHandler := func(c Context) error {\n\t\tc.Set(\"opts\", map[string]any{})\n\t\treturn c.Render(http.StatusOK, rr.String(`\n\t\t\t<%= userPath(opts) %>\n\t\t`))\n\t}\n\n\ta.GET(\"/users/{user_id}\", sampleHandler)\n\tw := httptest.New(a)\n\tres := w.HTML(\"/users/1\").Get()\n\n\tr.Equal(http.StatusInternalServerError, res.Code)\n\tr.Contains(res.Body.String(), \"missing parameters for /users/{user_id}\")\n}\n\nfunc Test_Resource(t *testing.T) {\n\tr := require.New(t)\n\n\ttype trs struct {\n\t\tMethod string\n\t\tPath   string\n\t\tResult string\n\t}\n\n\ttests := []trs{\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"\",\n\t\t\tResult: \"list\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/new\",\n\t\t\tResult: \"new\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/1\",\n\t\t\tResult: \"show 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/1/edit\",\n\t\t\tResult: \"edit 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"POST\",\n\t\t\tPath:   \"\",\n\t\t\tResult: \"create\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"PUT\",\n\t\t\tPath:   \"/1\",\n\t\t\tResult: \"update 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"DELETE\",\n\t\t\tPath:   \"/1\",\n\t\t\tResult: \"destroy 1\",\n\t\t},\n\t}\n\n\ta := New(Options{})\n\ta.Resource(\"/users\", &userResource{})\n\ta.Resource(\"/api/v1/users\", &userResource{})\n\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\tc := http.Client{}\n\tfor _, path := range []string{\"/users\", \"/api/v1/users\"} {\n\t\tfor _, test := range tests {\n\t\t\tu := ts.URL + path + test.Path\n\t\t\treq, err := http.NewRequest(test.Method, u, nil)\n\t\t\tr.NoError(err)\n\t\t\tres, err := c.Do(req)\n\t\t\tr.NoError(err)\n\t\t\tb, err := io.ReadAll(res.Body)\n\t\t\tr.NoError(err)\n\t\t\tr.Equal(test.Result, string(b))\n\t\t}\n\t}\n\n}\n\ntype paramKeyResource struct {\n\t*userResource\n}\n\nfunc (paramKeyResource) ParamKey() string {\n\treturn \"bazKey\"\n}\n\nfunc Test_Resource_ParamKey(t *testing.T) {\n\tr := require.New(t)\n\tfr := &paramKeyResource{&userResource{}}\n\ta := New(Options{})\n\ta.Resource(\"/foo\", fr)\n\trt := a.Routes()\n\tpaths := []string{}\n\tfor _, rr := range rt {\n\t\tpaths = append(paths, rr.Path)\n\t}\n\tr.Contains(paths, \"/foo/{bazKey}/edit/\")\n}\n\ntype mwResource struct {\n\tWebResource\n}\n\nfunc (mwResource) Use() []MiddlewareFunc {\n\tvar mw []MiddlewareFunc\n\n\tmw = append(mw, func(next Handler) Handler {\n\t\treturn func(c Context) error {\n\t\t\tif c.Param(\"good\") == \"\" {\n\t\t\t\treturn fmt.Errorf(\"not good\")\n\t\t\t}\n\t\t\treturn next(c)\n\t\t}\n\t})\n\n\treturn mw\n}\n\nfunc (m mwResource) List(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"southern harmony and the musical companion\"))\n}\n\nfunc Test_Resource_MW(t *testing.T) {\n\tr := require.New(t)\n\tfr := mwResource{}\n\ta := New(Options{})\n\ta.Resource(\"/foo\", fr)\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/foo?good=true\").Get()\n\tr.Equal(http.StatusOK, res.Code)\n\tr.Contains(res.Body.String(), \"southern harmony\")\n\n\tres = w.HTML(\"/foo\").Get()\n\tr.Equal(http.StatusInternalServerError, res.Code)\n\n\tr.NotContains(res.Body.String(), \"southern harmony\")\n}\n\ntype userResource struct{}\n\nfunc (u *userResource) List(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"list\"))\n}\n\nfunc (u *userResource) Show(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(`show <%=params[\"user_id\"] %>`))\n}\n\nfunc (u *userResource) New(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"new\"))\n}\n\nfunc (u *userResource) Create(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(\"create\"))\n}\n\nfunc (u *userResource) Edit(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(`edit <%=params[\"user_id\"] %>`))\n}\n\nfunc (u *userResource) Update(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(`update <%=params[\"user_id\"] %>`))\n}\n\nfunc (u *userResource) Destroy(c Context) error {\n\treturn c.Render(http.StatusOK, render.String(`destroy <%=params[\"user_id\"] %>`))\n}\n\nfunc Test_ResourceOnResource(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\tur := a.Resource(\"/users\", &userResource{})\n\tur.Resource(\"/people\", &userResource{})\n\n\tts := httptest.NewServer(a)\n\tdefer ts.Close()\n\n\ttype trs struct {\n\t\tMethod string\n\t\tPath   string\n\t\tResult string\n\t}\n\ttests := []trs{\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/people\",\n\t\t\tResult: \"list\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/people/new\",\n\t\t\tResult: \"new\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/people/1\",\n\t\t\tResult: \"show 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPath:   \"/people/1/edit\",\n\t\t\tResult: \"edit 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"POST\",\n\t\t\tPath:   \"/people\",\n\t\t\tResult: \"create\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"PUT\",\n\t\t\tPath:   \"/people/1\",\n\t\t\tResult: \"update 1\",\n\t\t},\n\t\t{\n\t\t\tMethod: \"DELETE\",\n\t\t\tPath:   \"/people/1\",\n\t\t\tResult: \"destroy 1\",\n\t\t},\n\t}\n\tc := http.Client{}\n\tfor _, test := range tests {\n\t\tu := ts.URL + path.Join(\"/users/42\", test.Path)\n\t\treq, err := http.NewRequest(test.Method, u, nil)\n\t\tr.NoError(err)\n\t\tres, err := c.Do(req)\n\t\tr.NoError(err)\n\t\tb, err := io.ReadAll(res.Body)\n\t\tr.NoError(err)\n\t\tr.Equal(test.Result, string(b))\n\t}\n\n}\n\nfunc Test_buildRouteName(t *testing.T) {\n\tr := require.New(t)\n\tcases := map[string]string{\n\t\t\"/\":                                    \"root\",\n\t\t\"/users\":                               \"users\",\n\t\t\"/users/new\":                           \"newUsers\",\n\t\t\"/users/{user_id}\":                     \"user\",\n\t\t\"/users/{user_id}/children\":            \"userChildren\",\n\t\t\"/users/{user_id}/children/{child_id}\": \"userChild\",\n\t\t\"/users/{user_id}/children/new\":        \"newUserChildren\",\n\t\t\"/users/{user_id}/children/{child_id}/build\": \"userChildBuild\",\n\t\t\"/admin/planes\":                         \"adminPlanes\",\n\t\t\"/admin/planes/{plane_id}\":              \"adminPlane\",\n\t\t\"/admin/planes/{plane_id}/edit\":         \"editAdminPlane\",\n\t\t\"/test\":                                 \"test\",\n\t\t\"/tests/{name}\":                         \"testName\",\n\t\t\"/tests/{name_id}/cases/{case_id}\":      \"testNameIdCase\",\n\t\t\"/tests/{name_id}/cases/{case_id}/edit\": \"editTestNameIdCase\",\n\t}\n\n\ta := New(Options{})\n\n\tfor input, result := range cases {\n\t\tfResult := a.RouteNamer.NameRoute(input)\n\t\tr.Equal(result, fResult, input)\n\t}\n\n\ta = New(Options{Prefix: \"/test\"})\n\tcases = map[string]string{\n\t\t\"/test\":       \"test\",\n\t\t\"/test/users\": \"testUsers\",\n\t}\n\n\tfor input, result := range cases {\n\t\tfResult := a.RouteNamer.NameRoute(input)\n\t\tr.Equal(result, fResult, input)\n\t}\n}\n\nfunc Test_CatchAll_Route(t *testing.T) {\n\tr := require.New(t)\n\trr := render.New(render.Options{})\n\n\ta := New(Options{})\n\ta.GET(\"/{name:.+}\", func(c Context) error {\n\t\tname := c.Param(\"name\")\n\t\treturn c.Render(http.StatusOK, rr.String(name))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/john\").Get()\n\n\tr.Contains(res.Body.String(), \"john\")\n}\n\nfunc Test_Router_Matches_Trailing_Slash(t *testing.T) {\n\ttable := []struct {\n\t\tmapped   string\n\t\tbrowser  string\n\t\texpected string\n\t}{\n\t\t{\"/foo\", \"/foo\", \"/foo/\"},\n\t\t{\"/foo\", \"/foo/\", \"/foo/\"},\n\t\t{\"/foo/\", \"/foo\", \"/foo/\"},\n\t\t{\"/foo/\", \"/foo/\", \"/foo/\"},\n\t\t{\"/index.html\", \"/index.html\", \"/index.html/\"},\n\t\t{\"/foo.gif\", \"/foo.gif\", \"/foo.gif/\"},\n\t\t{\"/{img}\", \"/foo.png\", \"/foo.png/\"},\n\t}\n\n\tfor _, tt := range table {\n\t\tt.Run(tt.mapped+\"|\"+tt.browser, func(st *testing.T) {\n\t\t\tr := require.New(st)\n\n\t\t\tapp := New(Options{\n\t\t\t\tPreWares: []PreWare{\n\t\t\t\t\tfunc(h http.Handler) http.Handler {\n\t\t\t\t\t\tvar f http.HandlerFunc = func(res http.ResponseWriter, req *http.Request) {\n\t\t\t\t\t\t\tpath := req.URL.Path\n\t\t\t\t\t\t\treq.URL.Path = strings.TrimSuffix(path, \"/\")\n\t\t\t\t\t\t\tr.False(strings.HasSuffix(req.URL.Path, \"/\"))\n\t\t\t\t\t\t\th.ServeHTTP(res, req)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn f\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\tapp.GET(tt.mapped, func(c Context) error {\n\t\t\t\treturn c.Render(http.StatusOK, render.String(c.Request().URL.Path))\n\t\t\t})\n\n\t\t\tw := httptest.New(app)\n\t\t\tres := w.HTML(\"%s\", tt.browser).Get()\n\n\t\t\tr.Equal(http.StatusOK, res.Code)\n\t\t\tr.Equal(tt.expected, res.Body.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "runtime/build.go",
    "content": "// Deprecated: This package is deprecated and will be removed in a future version.\n// Use runtime/debug.ReadBuildInfo() directly instead.\n//\n// This package previously provided build information for buffalo applications.\n// Starting with Go 1.18, the standard library's runtime/debug package provides\n// equivalent functionality through ReadBuildInfo(), which includes VCS information\n// (commit hash, time) and module version.\n//\n// Migration example:\n//\n//\timport \"runtime/debug\"\n//\n//\tinfo, ok := debug.ReadBuildInfo()\n//\tif ok {\n//\t    // Use info.Main.Version for version\n//\t    // Use info.Settings for VCS info (vcs.revision, vcs.time)\n//\t}\n//\n// For applications built with \"buffalo build\", build information is now\n// automatically embedded by Go's build system and can be accessed via\n// runtime/debug.ReadBuildInfo().\npackage runtime\n\nimport (\n\t\"runtime/debug\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Version is the current version of the buffalo binary.\n// Deprecated: Use runtime/debug.ReadBuildInfo().Main.Version instead.\nvar Version = \"dev\"\n\n// BuildInfo holds information about the build.\n// Deprecated: Use runtime/debug.BuildInfo instead.\ntype BuildInfo struct {\n\tVersion string    `json:\"version\"`\n\tTime    time.Time `json:\"-\"`\n}\n\n// String implements fmt.Stringer\nfunc (b BuildInfo) String() string {\n\tif b.Time.IsZero() {\n\t\treturn b.Version\n\t}\n\treturn b.Version + \" (\" + b.Time.Format(time.RFC3339) + \")\"\n}\n\nvar (\n\tbuild     BuildInfo\n\tbuildOnce sync.Once\n)\n\n// Build returns the information about the current build of the application.\n// In development mode this will almost always return zero values for BuildInfo.\n//\n// Deprecated: Use runtime/debug.ReadBuildInfo() instead. This function now\n// returns information derived from the standard library's build info.\n//\n// For backward compatibility, this function caches the build info after first call.\nfunc Build() BuildInfo {\n\tbuildOnce.Do(func() {\n\t\tbuild = loadBuildInfo()\n\t})\n\treturn build\n}\n\n// loadBuildInfo reads build information from runtime/debug and converts it\n// to the legacy BuildInfo format for backward compatibility.\nfunc loadBuildInfo() BuildInfo {\n\tinfo, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn BuildInfo{\n\t\t\tVersion: \"dev\",\n\t\t\tTime:    time.Time{},\n\t\t}\n\t}\n\n\tbi := BuildInfo{\n\t\tVersion: info.Main.Version,\n\t\tTime:    time.Time{},\n\t}\n\n\t// Handle development builds\n\tif bi.Version == \"\" || bi.Version == \"(devel)\" {\n\t\tbi.Version = \"dev\"\n\t}\n\n\t// Try to extract build time from VCS info\n\tfor _, setting := range info.Settings {\n\t\tif setting.Key == \"vcs.time\" {\n\t\t\tif t, err := time.Parse(time.RFC3339, setting.Value); err == nil {\n\t\t\t\tbi.Time = t\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn bi\n}\n\nvar so sync.Once\n\n// SetBuild allows the setting of build information only once.\n// This is typically managed by the binary built by `buffalo build`.\n//\n// Deprecated: This function is no longer necessary. Build information is now\n// automatically embedded by the Go toolchain and can be accessed via\n// runtime/debug.ReadBuildInfo(). This function is kept for backward compatibility\n// but has no effect when called after Build() has been called.\nfunc SetBuild(b BuildInfo) {\n\tso.Do(func() {\n\t\tbuild = b\n\t})\n}\n"
  },
  {
    "path": "server.go",
    "content": "package buffalo\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/servers\"\n\t\"github.com/gobuffalo/events\"\n\t\"github.com/gobuffalo/refresh/refresh/web\"\n)\n\n// Serve the application at the specified address/port and listen for OS\n// interrupt and kill signals and will attempt to stop the application\n// gracefully. This will also start the Worker process, unless WorkerOff is enabled.\nfunc (a *App) Serve(srvs ...servers.Server) error {\n\tvar wg sync.WaitGroup\n\n\ta.Logger.Debug(\"starting application\")\n\n\tpayload := events.Payload{\n\t\t\"app\": a,\n\t}\n\tif err := events.EmitPayload(EvtAppStart, payload); err != nil {\n\t\t// just to make sure if events work properly?\n\t\ta.Logger.Error(\"unable to emit event. something went wrong internally\")\n\t\treturn err\n\t}\n\n\tif len(srvs) == 0 {\n\t\tif strings.HasPrefix(a.Options.Addr, \"unix:\") {\n\t\t\ttcp, err := servers.UnixSocket(a.Options.Addr[5:])\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsrvs = append(srvs, tcp)\n\t\t} else {\n\t\t\tsrvs = append(srvs, servers.New())\n\t\t}\n\t}\n\n\tctx, cancel := signal.NotifyContext(a.Context, syscall.SIGTERM, os.Interrupt)\n\tdefer cancel()\n\n\twg.Go(func() {\n\t\t// gracefully shut down the application when the context is cancelled\n\t\t// channel waiter should not be called any other place\n\t\t<-ctx.Done()\n\n\t\ta.Logger.Info(\"shutting down application\")\n\n\t\t// shutting down listeners first, to make sure no more new request\n\t\ta.Logger.Info(\"shutting down servers\")\n\t\tfor _, s := range srvs {\n\t\t\ttimeout := time.Duration(a.Options.TimeoutSecondShutdown) * time.Second\n\t\t\tctx, cfn := context.WithTimeout(context.Background(), timeout)\n\t\t\tdefer cfn()\n\t\t\tevents.EmitPayload(EvtServerStop, payload)\n\t\t\tif err := s.Shutdown(ctx); err != nil {\n\t\t\t\tevents.EmitError(EvtServerStopErr, err, payload)\n\t\t\t\ta.Logger.Error(\"shutting down server: \", err)\n\t\t\t}\n\t\t\tcfn()\n\t\t}\n\n\t\tif !a.WorkerOff {\n\t\t\ta.Logger.Info(\"shutting down worker\")\n\t\t\tevents.EmitPayload(EvtWorkerStop, payload)\n\t\t\tif err := a.Worker.Stop(); err != nil {\n\t\t\t\tevents.EmitError(EvtWorkerStopErr, err, payload)\n\t\t\t\ta.Logger.Error(\"error while shutting down worker: \", err)\n\t\t\t}\n\t\t}\n\t})\n\n\t// if configured to do so, start the workers\n\tif !a.WorkerOff {\n\t\twg.Go(func() {\n\t\t\tevents.EmitPayload(EvtWorkerStart, payload)\n\t\t\tif err := a.Worker.Start(ctx); err != nil {\n\t\t\t\tevents.EmitError(EvtWorkerStartErr, err, payload)\n\t\t\t\ta.Stop(err)\n\t\t\t}\n\t\t})\n\t}\n\n\tfor _, s := range srvs {\n\t\ts.SetAddr(a.Addr)\n\t\ta.Logger.Infof(\"starting %s\", s)\n\t\tserver := s // capture loop variable\n\t\twg.Go(func() {\n\t\t\tevents.EmitPayload(EvtServerStart, payload)\n\t\t\t// server.Start always returns non-nil error\n\t\t\ta.Stop(server.Start(ctx, a))\n\t\t})\n\t}\n\n\twg.Wait()\n\ta.Logger.Info(\"shutdown completed\")\n\n\terr := ctx.Err()\n\tif errors.Is(err, context.Canceled) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\n// Stop the application and attempt to gracefully shutdown\nfunc (a *App) Stop(err error) error {\n\tevents.EmitError(EvtAppStop, err, events.Payload{\"app\": a})\n\n\tce := a.Context.Err()\n\tif ce != nil {\n\t\ta.Logger.Warn(\"application context has already been canceled: \", ce)\n\t\treturn errors.New(\"application has already been canceled\")\n\t}\n\n\ta.Logger.Warn(\"stopping application: \", err)\n\ta.cancel()\n\treturn nil\n}\n\n// ServeHTTP implements http.Handler\nfunc (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tws := &Response{\n\t\tResponseWriter: w,\n\t}\n\tif a.MethodOverride != nil {\n\t\ta.MethodOverride(w, r)\n\t}\n\tif ok := a.processPreHandlers(ws, r); !ok {\n\t\treturn\n\t}\n\n\tr.URL.Path = a.normalizePath(r.URL.Path)\n\n\tvar h http.Handler = a.router\n\tif a.Env == \"development\" {\n\t\th = web.ErrorChecker(h)\n\t}\n\th.ServeHTTP(ws, r)\n}\n\nfunc (a *App) processPreHandlers(res http.ResponseWriter, req *http.Request) bool {\n\tsh := func(h http.Handler) bool {\n\t\th.ServeHTTP(res, req)\n\t\tif br, ok := res.(*Response); ok {\n\t\t\tif br.Status > 0 || br.Size > 0 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tfor _, ph := range a.PreHandlers {\n\t\tif ok := sh(ph); !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tlast := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {}))\n\tfor _, ph := range a.PreWares {\n\t\tlast = ph(last)\n\t\tif ok := sh(last); !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (a *App) normalizePath(path string) string {\n\tif strings.HasSuffix(path, \"/\") {\n\t\treturn path\n\t}\n\tfor _, p := range a.filepaths {\n\t\tif p == \"/\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(path, p) {\n\t\t\treturn path\n\t\t}\n\t}\n\treturn path + \"/\"\n}\n"
  },
  {
    "path": "server_test.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/buffalo/worker\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// All tests in this file requires certain amount of waiting and they are\n// timing sensitive. Adjust this timing values if they are failing due to\n// timing issue.\nconst (\n\twaitStart   = 2\n\twaitRun     = 2\n\tconsumerRun = 8\n)\n\n// startApp starts given buffalo app and check its exit status.\n// The go routine emulates a buffalo app process.\nfunc startApp(app *App, wg *sync.WaitGroup, r *require.Assertions) {\n\twg.Go(func() {\n\t\terr := app.Serve()\n\t\tr.NoError(err)\n\t})\n\t// wait until the server started.\n\t// could be improved with connection test but that's too much...\n\ttime.Sleep(waitStart * time.Second)\n}\n\nfunc Test_Server_Simple(t *testing.T) {\n\t// This testcase explains the minimum/basic workflow of buffalo app.\n\t// Setup and execute the app, wait until startup, then stop it.\n\t// The other testcases use this structure with additional actions.\n\tr := require.New(t)\n\tvar wg sync.WaitGroup\n\n\t// Setup a new buffalo.App to be used as a testing buffalo app.\n\tapp := New(Options{})\n\n\tstartApp(app, &wg, r) // starts buffalo app routine.\n\n\tapp.cancel()\n\twg.Wait()\n}\n\nvar handlerDone = false\n\n// timeConsumer consumes about 10 minutes for processing its request\nfunc timeConsumer(c Context) error {\n\tfor range consumerRun {\n\t\tfmt.Println(\"#\")\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\thandlerDone = true\n\treturn c.Render(http.StatusOK, render.String(\"Hey!\"))\n}\n\nfunc Test_Server_GracefulShutdownOngoingRequest(t *testing.T) {\n\t// This test case explain the minimum/basic workflow of buffalo app.\n\tr := require.New(t)\n\tvar wg sync.WaitGroup\n\n\t// Setup a new buffalo.App with a simple time consuming handler.\n\tapp := New(Options{})\n\tapp.GET(\"/\", timeConsumer)\n\n\tstartApp(app, &wg, r) // starts buffalo app routine.\n\n\tfirstQuery := false\n\tsecondQuery := false\n\t// This routine is the 1st client that GETs before Stop it\n\t// The result should be successful even though the server shutting down.\n\twg.Go(func() {\n\t\tresp, err := http.Get(\"http://127.0.0.1:3000\")\n\t\tr.NoError(err)\n\t\tdefer resp.Body.Close()\n\t\tr.Equal(http.StatusOK, resp.StatusCode)\n\t\tfmt.Println(\"the first query should be OK:\", resp.Status)\n\t\tfirstQuery = true\n\t})\n\t// make sure the request sent\n\ttime.Sleep(waitRun * time.Second)\n\n\tapp.cancel()\n\ttime.Sleep(1 * time.Second) // make sure the server started shutdown.\n\n\t// This routine is the 2nd client that GETs after Stop it\n\t// The result should be connection refused even though app is still on.\n\twg.Go(func() {\n\t\t_, err := http.Get(\"http://127.0.0.1:3000\")\n\t\tr.Contains(err.Error(), \"refused\")\n\t\tfmt.Println(\"the second query should be refused:\", err)\n\t\tsecondQuery = true\n\t})\n\n\twg.Wait()\n\tr.Equal(true, handlerDone)\n\tr.Equal(true, firstQuery)\n\tr.Equal(true, secondQuery)\n}\n\nvar timerDone = false\n\nfunc timerWorker(args worker.Args) error {\n\tfor range consumerRun {\n\t\tfmt.Println(\"%\")\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\ttimerDone = true\n\treturn nil\n}\n\nfunc Test_Server_GracefulShutdownOngoingWorker(t *testing.T) {\n\t// This test case explain the minimum/basic workflow of buffalo app.\n\tr := require.New(t)\n\tvar wg sync.WaitGroup\n\n\t// Setup a new buffalo.App with a simple time consuming handler.\n\tapp := New(Options{})\n\tapp.Worker.Register(\"timer\", timerWorker)\n\tapp.Worker.PerformIn(worker.Job{\n\t\tHandler: \"timer\",\n\t}, 1*time.Second)\n\n\tstartApp(app, &wg, r) // starts buffalo app routine.\n\n\ttime.Sleep(1 * time.Second) // make sure just 1 second\n\n\tapp.cancel()\n\ttime.Sleep(1 * time.Second) // make sure the server started shutdown.\n\n\t// This routine is the 2nd client that GETs after Stop it\n\t// The result should be connection refused even though app is still on.\n\twg.Go(func() {\n\t\t_, err := http.Get(\"http://127.0.0.1:3000\")\n\t\tr.Contains(err.Error(), \"refused\")\n\t})\n\n\twg.Wait()\n\tr.Equal(true, timerDone)\n}\n"
  },
  {
    "path": "servers/listener.go",
    "content": "package servers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// Listener server for using a pre-defined net.Listener\ntype Listener struct {\n\t*http.Server\n\tListener net.Listener\n}\n\n// SetAddr sets the servers address, if it hasn't already been set\nfunc (s *Listener) SetAddr(addr string) {\n\tif s.Server.Addr == \"\" {\n\t\ts.Server.Addr = addr\n\t}\n}\n\n// String returns a string representation of a Listener\nfunc (s *Listener) String() string {\n\treturn fmt.Sprintf(\"listener on %s\", s.Server.Addr)\n}\n\n// Start the server\nfunc (s *Listener) Start(c context.Context, h http.Handler) error {\n\ts.Handler = h\n\treturn s.Serve(s.Listener)\n}\n\n// UnixSocket returns a new Listener on that address\nfunc UnixSocket(addr string) (*Listener, error) {\n\tlistener, err := net.Listen(\"unix\", addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Listener{\n\t\tServer:   &http.Server{},\n\t\tListener: listener,\n\t}, nil\n}\n"
  },
  {
    "path": "servers/servers.go",
    "content": "package servers\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n)\n\n// Server allows for custom server implementations\ntype Server interface {\n\tShutdown(context.Context) error\n\tStart(context.Context, http.Handler) error\n\tSetAddr(string)\n}\n\n// Wrap converts a standard *http.Server to a buffalo.Server\nfunc Wrap(s *http.Server) Server {\n\treturn &Simple{Server: s}\n}\n\n// WrapTLS Server converts a standard *http.Server to a buffalo.Server\n// but makes sure it is run with TLS.\nfunc WrapTLS(s *http.Server, certFile string, keyFile string) Server {\n\treturn &TLS{\n\t\tServer:   s,\n\t\tCertFile: certFile,\n\t\tKeyFile:  keyFile,\n\t}\n}\n\n// WrapListener wraps an *http.Server and a net.Listener\nfunc WrapListener(s *http.Server, l net.Listener) Server {\n\treturn &Listener{\n\t\tServer:   s,\n\t\tListener: l,\n\t}\n}\n"
  },
  {
    "path": "servers/simple.go",
    "content": "package servers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Simple server\ntype Simple struct {\n\t*http.Server\n}\n\n// SetAddr sets the servers address, if it hasn't already been set\nfunc (s *Simple) SetAddr(addr string) {\n\tif s.Server.Addr == \"\" {\n\t\ts.Server.Addr = addr\n\t}\n}\n\n// String returns a string representation of a Simple server\nfunc (s *Simple) String() string {\n\treturn fmt.Sprintf(\"simple server on %s\", s.Server.Addr)\n}\n\n// Start the server\nfunc (s *Simple) Start(c context.Context, h http.Handler) error {\n\ts.Handler = h\n\treturn s.ListenAndServe()\n}\n\n// New Simple server\nfunc New() *Simple {\n\treturn &Simple{\n\t\tServer: &http.Server{},\n\t}\n}\n"
  },
  {
    "path": "servers/tls.go",
    "content": "package servers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// TLS server\ntype TLS struct {\n\t*http.Server\n\tCertFile string\n\tKeyFile  string\n}\n\n// SetAddr sets the servers address, if it hasn't already been set\nfunc (s *TLS) SetAddr(addr string) {\n\tif s.Server.Addr == \"\" {\n\t\ts.Server.Addr = addr\n\t}\n}\n\n// String returns a string representation of a Listener\nfunc (s *TLS) String() string {\n\treturn fmt.Sprintf(\"TLS server on %s\", s.Server.Addr)\n}\n\n// Start the server\nfunc (s *TLS) Start(c context.Context, h http.Handler) error {\n\ts.Handler = h\n\treturn s.ListenAndServeTLS(s.CertFile, s.KeyFile)\n}\n"
  },
  {
    "path": "session.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/sessions\"\n)\n\n// Session wraps the \"github.com/gorilla/sessions\" API\n// in something a little cleaner and a bit more useable.\ntype Session struct {\n\tSession *sessions.Session\n\treq     *http.Request\n\tres     http.ResponseWriter\n}\n\n// Save the current session.\nfunc (s *Session) Save() error {\n\treturn s.Session.Save(s.req, s.res)\n}\n\n// Get a value from the current session.\nfunc (s *Session) Get(name any) any {\n\treturn s.Session.Values[name]\n}\n\n// GetOnce gets a value from the current session and then deletes it.\nfunc (s *Session) GetOnce(name any) any {\n\tif x, ok := s.Session.Values[name]; ok {\n\t\ts.Delete(name)\n\t\treturn x\n\t}\n\treturn nil\n}\n\n// Set a value onto the current session. If a value with that name\n// already exists it will be overridden with the new value.\nfunc (s *Session) Set(name, value any) {\n\ts.Session.Values[name] = value\n}\n\n// Delete a value from the current session.\nfunc (s *Session) Delete(name any) {\n\tdelete(s.Session.Values, name)\n}\n\n// Clear the current session\nfunc (s *Session) Clear() {\n\tfor k := range s.Session.Values {\n\t\ts.Delete(k)\n\t}\n}\n\n// Get a session using a request and response.\nfunc (a *App) getSession(r *http.Request, w http.ResponseWriter) *Session {\n\tif a.root != nil {\n\t\treturn a.root.getSession(r, w)\n\t}\n\tsession, _ := a.SessionStore.Get(r, a.SessionName)\n\treturn &Session{\n\t\tSession: session,\n\t\treq:     r,\n\t\tres:     w,\n\t}\n}\n"
  },
  {
    "path": "session_test.go",
    "content": "package buffalo\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Session_SingleCookie(t *testing.T) {\n\tr := require.New(t)\n\n\tsessionName := \"_test_session\"\n\ta := New(Options{SessionName: sessionName})\n\trr := render.New(render.Options{})\n\n\ta.GET(\"/\", func(c Context) error {\n\t\treturn c.Render(http.StatusCreated, rr.String(\"\"))\n\t})\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/\").Get()\n\n\tvar sessionCookies []string\n\tfor _, c := range res.Header().Values(\"Set-Cookie\") {\n\t\tif strings.HasPrefix(c, sessionName) {\n\t\t\tsessionCookies = append(sessionCookies, c)\n\t\t}\n\t}\n\n\tr.Equal(1, len(sessionCookies))\n}\n\nfunc Test_Session_CustomValue(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\trr := render.New(render.Options{})\n\n\t// Root path sets a custom session value\n\ta.GET(\"/\", func(c Context) error {\n\t\tc.Session().Set(\"example\", \"test\")\n\t\treturn c.Render(http.StatusCreated, rr.String(\"\"))\n\t})\n\t// /session path prints custom session value as response\n\ta.GET(\"/session\", func(c Context) error {\n\t\tsessionValue := c.Session().Get(\"example\")\n\t\treturn c.Render(http.StatusCreated, rr.String(fmt.Sprintf(\"%s\", sessionValue)))\n\t})\n\n\tw := httptest.New(a)\n\t_ = w.HTML(\"/\").Get()\n\n\t// Create second request that should contain the cookie from the first response\n\treqGetSession := w.HTML(\"/session\")\n\tresGetSession := reqGetSession.Get()\n\n\tr.Equal(resGetSession.Body.String(), \"test\")\n}\n"
  },
  {
    "path": "worker/job.go",
    "content": "package worker\n\nimport \"encoding/json\"\n\n// Args are the arguments passed into a job\ntype Args map[string]any\n\nfunc (a Args) String() string {\n\tb, _ := json.Marshal(a)\n\treturn string(b)\n}\n\n// Job to be processed by a Worker\ntype Job struct {\n\t// Queue the job should be placed into\n\tQueue string\n\t// Args that will be passed to the Handler when run\n\tArgs Args\n\t// Handler that will be run by the worker\n\tHandler string\n}\n\nfunc (j Job) String() string {\n\tb, _ := json.Marshal(j)\n\treturn string(b)\n}\n"
  },
  {
    "path": "worker/simple.go",
    "content": "package worker\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar _ Worker = &Simple{}\n\n// NewSimple creates a basic implementation of the Worker interface\n// that is backed using just the standard library and goroutines.\nfunc NewSimple() *Simple {\n\t// TODO(sio4): #road-to-v1 - how to check if the worker is ready to work\n\t// when worker should be initialized? how to check if worker is ready?\n\t// and purpose of the context\n\treturn NewSimpleWithContext(context.Background())\n}\n\n// NewSimpleWithContext creates a basic implementation of the Worker interface\n// that is backed using just the standard library and goroutines.\nfunc NewSimpleWithContext(ctx context.Context) *Simple {\n\tctx, cancel := context.WithCancel(ctx)\n\n\tl := logrus.New()\n\tl.Level = logrus.InfoLevel\n\tl.Formatter = &logrus.TextFormatter{}\n\n\treturn &Simple{\n\t\tLogger:   l,\n\t\tctx:      ctx,\n\t\tcancel:   cancel,\n\t\thandlers: map[string]Handler{},\n\t\tmoot:     &sync.Mutex{},\n\t\tstarted:  false,\n\t}\n}\n\n// Simple is a basic implementation of the Worker interface\n// that is backed using just the standard library and goroutines.\ntype Simple struct {\n\tLogger   SimpleLogger\n\tctx      context.Context\n\tcancel   context.CancelFunc\n\thandlers map[string]Handler\n\tmoot     *sync.Mutex\n\twg       sync.WaitGroup\n\tstarted  bool\n}\n\n// Register Handler with the worker\nfunc (w *Simple) Register(name string, h Handler) error {\n\tif name == \"\" || h == nil {\n\t\treturn fmt.Errorf(\"name or handler cannot be empty/nil\")\n\t}\n\n\tw.moot.Lock()\n\tdefer w.moot.Unlock()\n\tif _, ok := w.handlers[name]; ok {\n\t\treturn fmt.Errorf(\"handler already mapped for name %s\", name)\n\t}\n\tw.handlers[name] = h\n\treturn nil\n}\n\n// Start the worker\nfunc (w *Simple) Start(ctx context.Context) error {\n\t// TODO(sio4): #road-to-v1 - define the purpose of Start clearly\n\tw.Logger.Info(\"starting Simple background worker\")\n\n\tw.moot.Lock()\n\tdefer w.moot.Unlock()\n\n\tw.ctx, w.cancel = context.WithCancel(ctx)\n\tw.started = true\n\treturn nil\n}\n\n// Stop the worker\nfunc (w *Simple) Stop() error {\n\t// prevent job submission when stopping\n\tw.moot.Lock()\n\tdefer w.moot.Unlock()\n\n\tw.Logger.Info(\"stopping Simple background worker\")\n\n\tw.cancel()\n\n\tw.wg.Wait()\n\tw.Logger.Info(\"all background jobs stopped completely\")\n\treturn nil\n}\n\n// Perform a job as soon as possibly using a goroutine.\nfunc (w *Simple) Perform(job Job) error {\n\tw.moot.Lock()\n\tdefer w.moot.Unlock()\n\n\tif !w.started {\n\t\treturn fmt.Errorf(\"worker is not yet started\")\n\t}\n\n\t// Perform should not allow a job submission if the worker is not running\n\tif err := w.ctx.Err(); err != nil {\n\t\treturn fmt.Errorf(\"worker is not ready to perform a job: %v\", err)\n\t}\n\n\tw.Logger.Debugf(\"performing job %s\", job)\n\n\tif job.Handler == \"\" {\n\t\terr := fmt.Errorf(\"no handler name given: %s\", job)\n\t\tw.Logger.Error(err)\n\t\treturn err\n\t}\n\n\tif h, ok := w.handlers[job.Handler]; ok {\n\t\t// TODO(sio4): #road-to-v1 - consider timeout and/or cancellation\n\t\tw.wg.Go(func() {\n\t\t\terr := safeRun(func() error {\n\t\t\t\treturn h(job.Args)\n\t\t\t})\n\n\t\t\tif err != nil {\n\t\t\t\tw.Logger.Error(err)\n\t\t\t}\n\t\t\tw.Logger.Debugf(\"completed job %s\", job)\n\t\t})\n\t\treturn nil\n\t}\n\n\terr := fmt.Errorf(\"no handler mapped for name %s\", job.Handler)\n\tw.Logger.Error(err)\n\treturn err\n}\n\n// safeRun the function safely knowing that if it panics\n// the panic will be caught and returned as an error\nfunc safeRun(fn func() error) (err error) {\n\tdefer func() {\n\t\tif ex := recover(); ex != nil {\n\t\t\tif e, ok := ex.(error); ok {\n\t\t\t\terr = e\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = errors.New(fmt.Sprint(ex))\n\t\t}\n\t}()\n\n\treturn fn()\n}\n\n// PerformAt performs a job at a particular time using a goroutine.\nfunc (w *Simple) PerformAt(job Job, t time.Time) error {\n\treturn w.PerformIn(job, time.Until(t))\n}\n\n// PerformIn performs a job after waiting for a specified amount\n// using a goroutine.\nfunc (w *Simple) PerformIn(job Job, d time.Duration) error {\n\t// Perform should not allow a job submission if the worker is not running\n\tif err := w.ctx.Err(); err != nil {\n\t\treturn fmt.Errorf(\"worker is not ready to perform a job: %v\", err)\n\t}\n\n\tw.wg.Go(func() { // waiting job also should be counted\n\t\tfor {\n\t\t\tw.moot.Lock()\n\t\t\tif w.started {\n\t\t\t\tw.moot.Unlock()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tw.moot.Unlock()\n\n\t\t\twaiting := 100 * time.Millisecond\n\t\t\ttime.Sleep(waiting)\n\t\t\td = d - waiting\n\t\t}\n\n\t\tselect {\n\t\tcase <-time.After(d):\n\t\t\tw.Perform(job)\n\t\tcase <-w.ctx.Done():\n\t\t\t// TODO(sio4): #road-to-v1 - it should be guaranteed to be performed\n\t\t\tw.cancel()\n\t\t}\n\t})\n\treturn nil\n}\n\n// SimpleLogger is used by the Simple worker to write logs\ntype SimpleLogger interface {\n\tDebugf(string, ...any)\n\tInfof(string, ...any)\n\tErrorf(string, ...any)\n\tDebug(...any)\n\tInfo(...any)\n\tError(...any)\n}\n"
  },
  {
    "path": "worker/simple_test.go",
    "content": "package worker\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc sampleHandler(Args) error {\n\treturn nil\n}\n\nfunc Test_Simple_RegisterEmpty(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\terr := w.Register(\"\", sampleHandler)\n\tr.Error(err)\n}\n\nfunc Test_Simple_RegisterNil(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\terr := w.Register(\"sample\", nil)\n\tr.Error(err)\n}\n\nfunc Test_Simple_RegisterEmptyNil(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\terr := w.Register(\"\", nil)\n\tr.Error(err)\n}\n\nfunc Test_Simple_RegisterExisting(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\terr := w.Register(\"sample\", sampleHandler)\n\tr.NoError(err)\n\n\terr = w.Register(\"sample\", sampleHandler)\n\tr.Error(err)\n}\n\nfunc Test_Simple_StartStop(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tctx := context.Background()\n\terr := w.Start(ctx)\n\tr.NoError(err)\n\tr.NotNil(w.ctx)\n\tr.Nil(w.ctx.Err())\n\n\terr = w.Stop()\n\tr.NoError(err)\n\tr.NotNil(w.ctx)\n\tr.NotNil(w.ctx.Err())\n}\n\nfunc Test_Simple_Perform(t *testing.T) {\n\tr := require.New(t)\n\n\tvar hit bool\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\n\tw.Register(\"x\", func(Args) error {\n\t\thit = true\n\t\treturn nil\n\t})\n\tw.Perform(Job{\n\t\tHandler: \"x\",\n\t})\n\n\t// the worker should guarantee the job is finished before the worker stopped\n\tr.NoError(w.Stop())\n\tr.True(hit)\n}\n\nfunc Test_Simple_PerformBroken(t *testing.T) {\n\tr := require.New(t)\n\n\tvar hit bool\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\n\tw.Register(\"x\", func(Args) error {\n\t\thit = true\n\n\t\t//Index out of bounds on purpose\n\t\tprintln([]string{}[0])\n\n\t\treturn nil\n\t})\n\tw.Perform(Job{\n\t\tHandler: \"x\",\n\t})\n\n\tr.NoError(w.Stop())\n\tr.True(hit)\n}\n\nfunc Test_Simple_PerformWithEmptyJob(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\tdefer w.Stop()\n\n\terr := w.Perform(Job{})\n\tr.Error(err)\n}\n\nfunc Test_Simple_PerformWithUnknownJob(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\tdefer w.Stop()\n\n\terr := w.Perform(Job{Handler: \"unknown\"})\n\tr.Error(err)\n}\n\n/* TODO(sio4): #road-to-v1 - define the purpose of Start clearly\n   consider to make Perform to work only when the worker is started.\nfunc Test_Simple_PerformBeforeStart(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Register(\"sample\", sampleHandler))\n\n\terr := w.Perform(Job{Handler: \"sample\"})\n\tr.Error(err)\n}\n*/\n\nfunc Test_Simple_PerformAfterStop(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Register(\"sample\", sampleHandler))\n\tr.NoError(w.Start(context.Background()))\n\tr.NoError(w.Stop())\n\n\terr := w.Perform(Job{Handler: \"sample\"})\n\tr.Error(err)\n}\n\nfunc Test_Simple_PerformAt(t *testing.T) {\n\tr := require.New(t)\n\n\tvar hit bool\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\n\tw.Register(\"x\", func(Args) error {\n\t\thit = true\n\t\twg.Done()\n\t\treturn nil\n\t})\n\tw.PerformAt(Job{\n\t\tHandler: \"x\",\n\t}, time.Now().Add(5*time.Millisecond))\n\n\t// how long does the handler take for assignment? hmm,\n\ttime.Sleep(100 * time.Millisecond)\n\twg.Wait()\n\tr.True(hit)\n\n\tr.NoError(w.Stop())\n}\n\nfunc Test_Simple_PerformIn(t *testing.T) {\n\tr := require.New(t)\n\n\tvar hit bool\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\n\tw.Register(\"x\", func(Args) error {\n\t\thit = true\n\t\twg.Done()\n\t\treturn nil\n\t})\n\tw.PerformIn(Job{\n\t\tHandler: \"x\",\n\t}, 5*time.Millisecond)\n\n\t// how long does the handler take for assignment? hmm,\n\ttime.Sleep(100 * time.Millisecond)\n\twg.Wait()\n\tr.True(hit)\n\n\tr.NoError(w.Stop())\n}\n\n/* TODO(sio4): #road-to-v1 - define the purpose of Start clearly\n   consider to make Perform to work only when the worker is started.\nfunc Test_Simple_PerformInBeforeStart(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Register(\"sample\", sampleHandler))\n\n\terr := w.PerformIn(Job{Handler: \"sample\"}, 5*time.Millisecond)\n\tr.Error(err)\n}\n*/\n\nfunc Test_Simple_PerformInAfterStop(t *testing.T) {\n\tr := require.New(t)\n\n\tw := NewSimple()\n\tr.NoError(w.Register(\"sample\", sampleHandler))\n\tr.NoError(w.Start(context.Background()))\n\tr.NoError(w.Stop())\n\n\terr := w.PerformIn(Job{Handler: \"sample\"}, 5*time.Millisecond)\n\tr.Error(err)\n}\n\n/* TODO(sio4): #road-to-v1 - it should be guaranteed to be performed\n   consider to make PerformIn to guarantee the job execution\nfunc Test_Simple_PerformInFollowedByStop(t *testing.T) {\n\tr := require.New(t)\n\n\tvar hit bool\n\tw := NewSimple()\n\tr.NoError(w.Start(context.Background()))\n\n\tw.Register(\"x\", func(Args) error {\n\t\thit = true\n\t\treturn nil\n\t})\n\terr := w.PerformIn(Job{\n\t\tHandler: \"x\",\n\t}, 5*time.Millisecond)\n\tr.NoError(err)\n\n\t// stop the worker immediately after PerformIn\n\tr.NoError(w.Stop())\n\n\tr.True(hit)\n}\n*/\n"
  },
  {
    "path": "worker/worker.go",
    "content": "package worker\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\n// Handler function that will be run by the worker and given\n// a slice of arguments\ntype Handler func(Args) error\n\n// Worker interface that needs to be implemented to be considered\n// a \"worker\"\ntype Worker interface {\n\t// Start the worker with the given context\n\tStart(context.Context) error\n\t// Stop the worker\n\tStop() error\n\t// Perform a job as soon as possibly\n\tPerform(Job) error\n\t// PerformAt performs a job at a particular time\n\tPerformAt(Job, time.Time) error\n\t// PerformIn performs a job after waiting for a specified amount of time\n\tPerformIn(Job, time.Duration) error\n\t// Register a Handler\n\tRegister(string, Handler) error\n}\n\n/* TODO(sio4): #road-to-v1 - redefine Worker interface clearer\n1. The Start() functions of current implementations including Simple,\n   Gocraft Work Adapter do not block and immediately return the error.\n   However, App.Serve() calls them within a go routine.\n2. The Perform() family of functions can be called before the worker\n   was started once the worker configured. Could be fine but there should\n   be some guidiance for its usage.\n3. The Perform() function could be interpreted as \"Do it\" by its name but\n   their actual job is \"Enqueue it\" even though Simple worker has no clear\n   boundary between them. It could make confusion.\n*/\n"
  },
  {
    "path": "wrappers.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/gobuffalo/buffalo/internal/httpx\"\n\t\"github.com/gorilla/mux\"\n)\n\n// WrapHandler wraps a standard http.Handler and transforms it\n// into a buffalo.Handler.\nfunc WrapHandler(h http.Handler) Handler {\n\treturn func(c Context) error {\n\t\th.ServeHTTP(c.Response(), c.Request())\n\t\treturn nil\n\t}\n}\n\n// WrapHandlerFunc wraps a standard http.HandlerFunc and\n// transforms it into a buffalo.Handler.\nfunc WrapHandlerFunc(h http.HandlerFunc) Handler {\n\treturn WrapHandler(h)\n}\n\n// WrapBuffaloHandler wraps a buffalo.Handler to a standard http.Handler\n//\n// NOTE: A buffalo Handler expects a buffalo Context. WrapBuffaloHandler uses\n// the same logic as DefaultContext where possible, but some functionality\n// (e.g. sessions and logging) WILL NOT work with this unwrap function. If\n// those features are needed a custom UnwrapHandlerFunc needs to be\n// implemented that provides a Context implementing those features.\nfunc WrapBuffaloHandler(h Handler) http.Handler {\n\treturn WrapBuffaloHandlerFunc(h)\n}\n\n// WrapBuffaloHandlerFunc wraps a buffalo.Handler to a standard http.HandlerFunc\n//\n// NOTE: A buffalo Handler expects a buffalo Context. WrapBuffaloHandlerFunc uses\n// the same logic as DefaultContext where possible, but some functionality\n// (e.g. sessions and logging) WILL NOT work with this unwrap function. If\n// those features are needed a custom WrapBuffaloHandlerFunc needs to be\n// implemented that provides a Context implementing those features.\nfunc WrapBuffaloHandlerFunc(h Handler) http.HandlerFunc {\n\treturn func(res http.ResponseWriter, req *http.Request) {\n\t\tif ws, ok := res.(*Response); ok {\n\t\t\tres = ws\n\t\t}\n\n\t\t// Parse URL Params\n\t\tparams := url.Values{}\n\t\tvars := mux.Vars(req)\n\t\tfor k, v := range vars {\n\t\t\tparams.Add(k, v)\n\t\t}\n\n\t\t// Parse URL Query String Params\n\t\t// For POST, PUT, and PATCH requests, it also parse the request body as a form.\n\t\t// Request body parameters take precedence over URL query string values in params\n\t\tif err := req.ParseForm(); err == nil {\n\t\t\tfor k, v := range req.Form {\n\t\t\t\tfor _, vv := range v {\n\t\t\t\t\tparams.Add(k, vv)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tct := httpx.ContentType(req)\n\n\t\tdata := newRequestData()\n\t\tdata.d = map[string]any{\n\t\t\t\"current_path\": req.URL.Path,\n\t\t\t\"contentType\":  ct,\n\t\t\t\"method\":       req.Method,\n\t\t}\n\t\tc := &DefaultContext{\n\t\t\tContext:     req.Context(),\n\t\t\tcontentType: ct,\n\t\t\tresponse:    res,\n\t\t\trequest:     req,\n\t\t\tparams:      params,\n\t\t\tflash:       &Flash{data: map[string][]string{}},\n\t\t\tdata:        data,\n\t\t}\n\t\th(c)\n\t}\n}\n"
  },
  {
    "path": "wrappers_test.go",
    "content": "package buffalo\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/buffalo/render\"\n\t\"github.com/gobuffalo/httptest\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_WrapHandlerFunc(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.GET(\"/foo\", WrapHandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tres.Write([]byte(\"hello\"))\n\t}))\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/foo\").Get()\n\n\tr.Equal(\"hello\", res.Body.String())\n}\n\nfunc Test_WrapHandler(t *testing.T) {\n\tr := require.New(t)\n\n\ta := New(Options{})\n\ta.GET(\"/foo\", WrapHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {\n\t\tres.Write([]byte(\"hello\"))\n\t})))\n\n\tw := httptest.New(a)\n\tres := w.HTML(\"/foo\").Get()\n\n\tr.Equal(\"hello\", res.Body.String())\n}\n\nfunc Test_WrapBuffaloHandler(t *testing.T) {\n\tr := require.New(t)\n\n\ttt := []struct {\n\t\tverb   string\n\t\tpath   string\n\t\tstatus int\n\t}{\n\t\t{\"GET\", \"/\", 200},\n\t\t{\"GET\", \"/foo\", 201},\n\t\t{\"POST\", \"/\", 300},\n\t\t{\"POST\", \"/foo\", 400},\n\t}\n\tfor _, x := range tt {\n\t\tbf := func(c Context) error {\n\t\t\treq := c.Request()\n\t\t\treturn c.Render(x.status, render.String(req.Method+req.URL.Path))\n\t\t}\n\n\t\th := WrapBuffaloHandler(bf)\n\t\tr.NotNil(h)\n\n\t\treq := httptest.NewRequest(x.verb, x.path, nil)\n\t\tres := httptest.NewRecorder()\n\n\t\th.ServeHTTP(res, req)\n\n\t\tr.Equal(x.status, res.Code)\n\t\tr.Contains(res.Body.String(), x.verb+x.path)\n\t}\n}\n\nfunc Test_WrapBuffaloHandlerFunc(t *testing.T) {\n\tr := require.New(t)\n\n\ttt := []struct {\n\t\tverb   string\n\t\tpath   string\n\t\tstatus int\n\t}{\n\t\t{\"GET\", \"/\", 200},\n\t\t{\"GET\", \"/foo\", 201},\n\t\t{\"POST\", \"/\", 300},\n\t\t{\"POST\", \"/foo\", 400},\n\t}\n\tfor _, x := range tt {\n\t\tbf := func(c Context) error {\n\t\t\treq := c.Request()\n\t\t\treturn c.Render(x.status, render.String(req.Method+req.URL.Path))\n\t\t}\n\n\t\th := WrapBuffaloHandlerFunc(bf)\n\t\tr.NotNil(h)\n\n\t\treq := httptest.NewRequest(x.verb, x.path, nil)\n\t\tres := httptest.NewRecorder()\n\n\t\th(res, req)\n\n\t\tr.Equal(x.status, res.Code)\n\t\tr.Contains(res.Body.String(), x.verb+x.path)\n\t}\n}\n\nfunc Benchmark_WrapBuffaloHandler(b *testing.B) {\n\tr := require.New(b)\n\n\tstatus := http.StatusOK\n\n\tbf := func(c Context) error {\n\t\treturn c.Render(status, render.String(http.StatusText(status)))\n\t}\n\n\treq := httptest.NewRequest(http.MethodGet, \"/foo\", nil)\n\tres := httptest.NewRecorder()\n\n\tb.StartTimer()\n\tfor b.Loop() {\n\t\th := WrapBuffaloHandler(bf)\n\t\tr.NotNil(h)\n\n\t\th.ServeHTTP(res, req)\n\n\t\tr.Equal(status, res.Code)\n\t\tr.Contains(res.Body.String(), http.StatusText(status))\n\t}\n\tb.StopTimer()\n}\n"
  }
]