Full Code of syntaqx/serve for AI

main ad0ff656bad8 cached
42 files
48.0 KB
15.7k tokens
41 symbols
1 requests
Download .txt
Repository: syntaqx/serve
Branch: main
Commit: ad0ff656bad8
Files: 42
Total size: 48.0 KB

Directory structure:
gitextract_g0z6iav5/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── codeql.yml
│       ├── dependabot.yml
│       ├── docker.yml
│       ├── go.yml
│       ├── golangci-lint.yml
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── serve/
│       └── main.go
├── compose.yml
├── examples/
│   └── basic/
│       └── main.go
├── fixtures/
│   ├── cert.pem
│   └── key.pem
├── go.mod
├── go.sum
├── internal/
│   ├── commands/
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── version.go
│   │   └── version_test.go
│   ├── config/
│   │   ├── flags.go
│   │   └── flags_test.go
│   └── middleware/
│       ├── auth.go
│       ├── auth_test.go
│       ├── cors.go
│       ├── cors_test.go
│       ├── logger.go
│       ├── logger_test.go
│       ├── recover.go
│       ├── recover_test.go
│       └── statuswriter.go
├── mock/
│   └── http.go
├── serve.go
├── serve_test.go
└── static/
    └── index.html

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

================================================
FILE: .dockerignore
================================================
*.yml
*.yaml
*.json
*.md

.git*
bin
dist
build
docs
examples
tmp
vendor

.editorconfig
Dockerfile
CODEOWNERS
LICENSE
Makefile
.env
compose.yml
compose.override.yml
README.md


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
max_line_length = 120
insert_final_newline = true
trim_trailing_whitespace = true

[*.{json,yaml,yml}]
indent_style = space
indent_size = 2

[*.{sh,bash,envrc}]
indent_style = space
indent_size = 4

[*.go]
indent_style = tab
indent_size = 4

[{Makefile,makefile,GNUmakefile}]
indent_style = tab
indent_size = 4

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .gitattributes
================================================
* text=auto


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: github-actions
  directory: "/"
  schedule:
    interval: weekly
- package-ecosystem: gomod
  directory: "/"
  schedule:
    interval: weekly


================================================
FILE: .github/workflows/codeql.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [ "main" ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ "main" ]
  schedule:
    - cron: '37 6 * * 0'

jobs:
  analyze:
    name: Analyze
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'go' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Use only 'java' to analyze code written in Java, Kotlin or both
        # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v6

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v4
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality


    # Autobuild attempts to build any compiled languages  (C/C++, C#, Go, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v4

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines.
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #     echo "Run, Build Application using script"
    #     ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v4
      with:
        category: "/language:${{matrix.language}}"


================================================
FILE: .github/workflows/dependabot.yml
================================================
name: Dependabot Approve & Auto-Merge
on:
  pull_request:
    branches: [ main ]

permissions:
  contents: write
  pull-requests: write

jobs:

  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      -
        name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v3
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      -
        name: Output Metadata
        run: |
          echo "${{ steps.metadata.outputs.dependency-names }}"
          echo "${{ steps.metadata.outputs.dependency-type }}"
          echo "${{ steps.metadata.outputs.update-type }}"
      -
        name: Approve a PR
        run: gh pr review --approve "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      -
        name: Enable auto-merge for Dependabot PRs
        # if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }}
        run: gh pr merge --auto --merge --delete-branch "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker

on:
  workflow_call:
    inputs:
      push:
        description: "Push the image to the registry"
        type: boolean
        default: false
        required: false
    outputs:
      image:
        description: "Output Image"
        value: ${{ github.repository }}:sha-${{ jobs.build.outputs.version }}

jobs:

  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.vars.outputs.sha_short }}
    steps:
      -
        name: Checkout
        uses: actions/checkout@v6
      - name: Set output vars
        id: vars
        run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
      -
        name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ github.repository }}
          tags: |
            type=schedule
            type=raw,value=latest,enable={{is_default_branch}}
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=sha
      -
        name: Set up QEMU
        uses: docker/setup-qemu-action@v4
      -
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4
      -
        name: Login to DockerHub
        uses: docker/login-action@v4
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      -
        name: Build and push
        uses: docker/build-push-action@v7
        with:
          context: .
          push: ${{ inputs.push }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=${{ github.repository }}:buildcache
          cache-to: type=registry,ref=${{ github.repository }}:buildcache,mode=max
      -
        if: ${{ github.event_name != 'pull_request' }}
        name: Update Docker Hub Description
        uses: peter-evans/dockerhub-description@v5
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          repository: ${{ github.repository }}


================================================
FILE: .github/workflows/go.yml
================================================
name: Go

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      -
        name: Set up Go
        uses: actions/setup-go@v6
        with:
          cache: true
      -
        name: Build
        run: go build -v ./...
      -
        name: Test
        run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
      -
        name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v6


================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
  push:
    tags:
      - v*
    branches:
      - main
  pull_request:

permissions:
  contents: read
  # Optional: allow read access to pull request. Use with `only-new-issues` option.
  # pull-requests: read
jobs:

  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-go@v6
        with:
          cache: false
      - uses: actions/checkout@v6
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
          version: latest

          # Optional: working directory, useful for monorepos
          # working-directory: somedir

          # Optional: golangci-lint command line arguments.
          # args: --issues-exit-code=0

          # Optional: show only new issues if it's a pull request. The default value is `false`.
          # only-new-issues: true

          # Optional: if set to true then the all caching functionality will be complete disabled,
          #           takes precedence over all other caching options.
          # skip-cache: true

          # Optional: if set to true then the action don't cache or restore ~/go/pkg.
          # skip-pkg-cache: true

          # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
          # skip-build-cache: true


================================================
FILE: .github/workflows/release.yml
================================================
name: cd

on:
  push:
    tags:
      - v*
  workflow_dispatch:

jobs:

  docker:
    uses: ./.github/workflows/docker.yml
    with:
      push: true
    secrets: inherit


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# SQLite Databases
*.db

# Log files
*.log

# Output of the go coverage tool, specifically when used with LiteIDE
coverage*
*.out

# Dependency directories
vendor/
_vendor-*/

# Build artifacts
bin/
dist/

# Local configurations
.env*
compose.override.yml


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

[semver]: http://semver.org/

When contributing to this repository, please first discuss the change you wish
to make via issue, email, or any other method with the owners of this repository
before making a change.

Please note we have a code of conduct, please follow it in all your interactions
with the project.

## Pull Request Process

1. Ensure any install or build dependencies are removed before the end of the
   layer when performing a build.
2. Update the `README.md` or `docs` with details of change to the project, this
   includes new flags, environment variables, exposed ports, useful file
   locations and container parameters.
3. Specify how your change should affect our versioning scheme when merged. For
   more information on how we implement versioning, check out the [semver][]
   documentation. PRs will be grouped into logical version groups so that we
   aren't incrementing the version on every merge.
4. You may merge the Pull Request in once you have the sign-off of other
   developers, or if you do not have permission to do that, you may request a
   reviewer to merge it for you.

## Code of Conduct

### Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

### Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

### Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

### Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

### Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project maintainer at syntaqx [at] gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

### Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: Dockerfile
================================================
FROM golang:1.20-alpine AS builder

ARG VERSION="0.0.0-docker"

RUN apk add --update --no-cache \
  ca-certificates tzdata openssh git mercurial && update-ca-certificates \
  && rm -rf /var/cache/apk/*

WORKDIR /src

COPY go.mod* go.sum* ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
  CGO_ENABLED=0 go install -ldflags "-X main.version=$VERSION" ./cmd/...

FROM alpine

RUN adduser -S -D -H -h /app appuser
USER appuser

COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/bin/* /bin/

ENV PORT=8080
EXPOSE $PORT

VOLUME ["/var/www"]

CMD ["serve", "--dir", "/var/www"]


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) Chase Pierce <syntaqx@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Makefile
================================================
VERSION=`git --no-pager describe --tags --always`

LDFLAGS+=
LDFLAGS+=-X main.version=${VERSION}

build:
	go build -ldflags "${LDFLAGS}" -o bin/serve ./cmd/serve

install:
	go install -ldflags "${LDFLAGS}" ./cmd/serve


================================================
FILE: README.md
================================================
# <img src="https://raw.githubusercontent.com/syntaqx/serve/main/docs/logo.svg?sanitize=true" width="250">

`serve` is a static http server anywhere you need one.

[homebrew]:   https://brew.sh/
[git]:        https://git-scm.com/
[golang]:     https://golang.org/
[releases]:   https://github.com/syntaqx/serve/releases
[modules]:    https://github.com/golang/go/wiki/Modules
[docker-hub]: https://hub.docker.com/r/syntaqx/serve

[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)

[![codecov](https://codecov.io/gh/syntaqx/serve/branch/main/graph/badge.svg?token=FGkU1ntp8z)](https://codecov.io/gh/syntaqx/serve)
[![Go Report Card](https://goreportcard.com/badge/github.com/syntaqx/serve)](https://goreportcard.com/report/github.com/syntaqx/serve)
[![Go Reference](https://pkg.go.dev/badge/github.com/syntaqx/serve.svg)](https://pkg.go.dev/github.com/syntaqx/serve)

[![GitHub Release](https://img.shields.io/github/release-pre/syntaqx/serve.svg)][releases]
[![Docker Pulls](https://img.shields.io/docker/pulls/syntaqx/serve.svg)][docker-hub]

> 🚨 The `main` branch is currently in active R&D for the next release of `serve`.
> To use `serve`, please be sure to download a previous [release](https://github.com/syntaqx/serve/releases) as no stability guarantees
> are being made further progress has been made towards a release candidate.

## TL;DR

> It's basically `python -m SimpleHTTPServer 8080` written in Go, because who
> can remember that many letters?

### Features

* HTTPS (TLS)
* CORS support
* Request logging
* `net/http` compatible
* Support for [BasicAuth](https://en.wikipedia.org/wiki/Basic_access_authentication) via `users.json`

## Installation

`serve` can be installed in a handful of ways:

### Homebrew on macOS

If you are using [Homebrew][] on macOS, you can install `serve` with the
following command:

```sh
brew install syntaqx/tap/serve
```

### Docker

The official [syntaqx/serve][docker-hub] image is available on Docker Hub.

To get started, try hosting a directory from your docker host:

```sh
docker run -v .:/var/www:ro -d syntaqx/serve
```

Alternatively, a simple `Dockerfile` can be used to generate a new image that
includes the necessary content:

```dockerfile
FROM syntaqx/serve
COPY . /var/www
```

Place this in the same directory as your content, then `build` and `run` the
container:

```sh
docker build -t some-content-serve .
docker run --name some-serve -d some-content-serve
```

#### Exposing an external port

```sh
docker run --name some-serve -d -p 8080:8080 some-content-serve
```

Then you can navigate to http://localhost:8080/ or http://host-ip:8080/ in your
browser.

#### Using environment variables for configuration

[12-factor-config]: https://12factor.net/config

Currently, `serve` only supports using the `PORT` environment variable for
setting the listening port. All other configurations are available as CLI flags.

> In future releases, most configurations will be settable from both the CLI
> flag as well as a compatible environment variable, aligning with the
> expectations of a [12factor app][12-factor-config]. But, that will require a
> fair amount of work before the functionality is made available.

Here's an example using `compose.yml` to configure `serve` to use HTTPS:

```yaml
version: '3'
services:
  web:
    image: syntaqx/serve
    volumes:
      - ./static:/var/www
      - ./fixtures:/etc/ssl
    environment:
      - PORT=1234
    ports:
      - 1234
    command: serve -ssl -cert=/etc/ssl/cert.pem -key=/etc/ssl/key.pem -dir=/var/www
```

The project repository provides an example [compose](./compose.yml) that
implements a variety of common use-cases for `serve`. Feel free to use those to
help you get started.

### Download the binary

Quickly download install the latest release:

```sh
curl -sfL https://install.goreleaser.com/github.com/syntaqx/serve.sh | sh
```

Or manually download the [latest release][releases] binary for your system and
architecture and install it into your `$PATH`.

### From source

To build from source, check out the instructions on getting started with
[development](#development).

## Usage

```sh
serve [options] [path]
```

> `[path]` defaults to `.` (relative path to the current directory)

Then simply open your browser to http://localhost:8080 to view your server.

### Options

The following configuration options are available:

* `--host` host address to bind to (defaults to `0.0.0.0`)
* `--port` listening port (defaults to `8080`)
* `--ssl` enable https (defaults to `false`)
* `--cert` path to the ssl cert file (defaults to `cert.pem`)
* `--key` path to the ssl key file (defaults to `key.pem`)
* `--dir` directory path to serve (defaults to `.`, also configurable by `arg[0]`)
* `--users` path to users file (defaults to `users.dat`); file should contain lines of username:password in plain text

## Development

To develop `serve` or interact with its source code in any meaningful way, be
sure you have the following installed:

### Prerequisites

* [Git][git]
* [Go][golang]

### Install

You can download and install the project from GitHub by simply running:

```sh
git clone git@github.com:syntaqx/serve.git && cd $(basename $_ .git)
make install
```

This will install `serve` into your `$GOPATH/bin` directory, which assuming is
properly appended to your `$PATH`, can now be used:

```sh
$ serve version
serve version v0.0.6-8-g5074d63 windows/amd64
```

## Using `serve` manually

Besides running `serve` using the provided binary, you can also embed a
`serve.FileServer` into your own Go program:

```go
package main

import (
    "log"
    "net/http"

    "github.com/syntaqx/serve"
)

func main() {
    fs := serve.NewFileServer()
    log.Fatal(http.ListenAndServe(":8080", fs))
}
```

## License

[MIT]: https://opensource.org/licenses/MIT

`serve` is open source software released under the [MIT license][MIT].

As with all Docker images, these likely also contain other software which may be
under other licenses (such as Bash, etc from the base distribution, along with
any direct or indirect dependencies of the primary software being contained).

As for any pre-built image usage, it is the image user's responsibility to
ensure that any use of this image complies with any relevant licenses for all
software contained within.


================================================
FILE: cmd/serve/main.go
================================================
// Package main implements the runtime for the serve binary.
package main

import (
	"flag"
	"log"
	"os"

	"github.com/syntaqx/serve/internal/commands"
	"github.com/syntaqx/serve/internal/config"
)

var version = "0.0.0-develop"

func main() {
	var opt config.Flags
	flag.BoolVar(&opt.Debug, "debug", false, "enable debug output")
	flag.StringVar(&opt.Host, "host", "", "host address to bind to")
	flag.StringVar(&opt.Port, "port", "8080", "listening port")
	flag.BoolVar(&opt.EnableSSL, "ssl", false, "enable https")
	flag.StringVar(&opt.CertFile, "cert", "cert.pem", "path to the ssl cert file")
	flag.StringVar(&opt.KeyFile, "key", "key.pem", "path to the ssl key file")
	flag.StringVar(&opt.Directory, "dir", "", "directory path to serve")
	flag.StringVar(&opt.UsersFile, "users", "users.dat", "path to users file")
	flag.Parse()

	log := log.New(os.Stderr, "[serve] ", log.LstdFlags)

	// Allow port to be configured via the environment variable PORT.
	// This is both better for configuration, and required for Heroku.
	if port, ok := os.LookupEnv("PORT"); ok {
		opt.Port = port
	}

	cmd := flag.Arg(0)

	dir, err := config.SanitizeDir(opt.Directory, cmd)
	if err != nil {
		log.Printf("sanitize directory: %v", err)
		os.Exit(1)
	}

	switch cmd {
	case "version":
		err = commands.Version(version, os.Stderr)
	default:
		err = commands.Server(log, opt, dir)
	}

	if err != nil {
		log.Printf("cmd.%s: %v", cmd, err)
		os.Exit(1)
	}
}


================================================
FILE: compose.yml
================================================
services:

  # Note: You probably will want to remove the `build: .` lines if you copy
  # these into your project. That is used to be able to rebuild the image
  # directly in the project repsitory.

  basic:
    build: .
    image: syntaqx/serve
    volumes:
      - ./static:/var/www
    ports:
      - 8080:8080

  basic_ssl:
    build: .
    image: syntaqx/serve
    volumes:
      - ./static:/var/www
      - ./fixtures:/etc/ssl
    ports:
      - 8888:8080
    command: serve -ssl -cert=/etc/ssl/cert.pem -key=/etc/ssl/key.pem -dir=/var/www


================================================
FILE: examples/basic/main.go
================================================
package main

import (
	"log"
	"net/http"

	"github.com/syntaqx/serve"
)

func main() {
	fs := serve.NewFileServer(serve.Options{
		Directory: "../../static",
	})

	log.Print("serve started at http://localhost:8080/")
	log.Fatal(http.ListenAndServe(":8080", fs))
}


================================================
FILE: fixtures/cert.pem
================================================
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIUG4x9A3w/n65jwz3y7Wo8MDrU6QEwDQYJKoZIhvcNAQEL
BQAweTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y
azEVMBMGA1UECgwMRXhhbXBsZSwgTExDMRIwEAYDVQQDDAlzaXRlLnRlc3QxHzAd
BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTkwMTE3MTA0NDM0WhcN
MjAwMTE3MTA0NDM0WjB5MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxETAPBgNV
BAcMCE5ldyBZb3JrMRUwEwYDVQQKDAxFeGFtcGxlLCBMTEMxEjAQBgNVBAMMCXNp
dGUudGVzdDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALLlVETDAxfpbMrL9vlTKu2y+G8y7qNv
KIdp5FllHAtZVPMis1xV9U4xvpy7baKTKKPtKEYZGcy/gW4fEN9KlvHZSUqrLj7T
X0ySTNkwGItZy+gm1gbwvbQGtL4atgu0jPsJB662DIzq4dLL1OAFMV6VfmY9r2Hs
ARhe0XjGtXKlX+Fyqnbxsot02C01CtFDcEftHR5KUZeUHkoIHmO+5ZtRAgAIfhV/
DQfyn+GfXOfM7PWGfy7RdyyLMrD+SwdfJFpkeeqQTi7p3PIIuHmieGOBjIOUhRv2
IEA7PbMNwoernE3Ey6iwErPjshWhSdLFG4NfAPs/KxDKe0qByRLOfZECAwEAAaOB
nzCBnDAdBgNVHQ4EFgQUWlS44ZoMP/8IkJhHwxzJcfZ7IuIwHwYDVR0jBBgwFoAU
WlS44ZoMP/8IkJhHwxzJcfZ7IuIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwFAYD
VR0RBA0wC4IJc2l0ZS50ZXN0MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy
YXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEASQ/wPIrRSsIEewDg
t6dehznWR+iBMGWGMpDEVw/IpRSN1zxLJp3i/4Yjcr98bEIP4tW27OODSJSKz11R
6/Kb/B04g3s7N4iSAehpeQXPGktNlgGojZSXi7u2y5ON6QBAle5csFxIkuOWDVwH
qM/lsVlNHGyM0BGVMm5VLi2OWSqspz6Lr6yguT7U/AJ/hPe+YjSU5Kc+OnCZ4IH0
NcdVG5aPpDFeZ7c9v1uHa7b725lyXUYO8xfWR3QV6CsTLgRFWhwYBXF51sZbBBsr
fu78txegVWnYau4uh/nytqPoOnjoP4BAMKlynPfIpJ9TLWxosWeXro2xY5zvdFkp
XH/+0g==
-----END CERTIFICATE-----


================================================
FILE: fixtures/key.pem
================================================
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy5VREwwMX6WzK
y/b5UyrtsvhvMu6jbyiHaeRZZRwLWVTzIrNcVfVOMb6cu22ikyij7ShGGRnMv4Fu
HxDfSpbx2UlKqy4+019MkkzZMBiLWcvoJtYG8L20BrS+GrYLtIz7CQeutgyM6uHS
y9TgBTFelX5mPa9h7AEYXtF4xrVypV/hcqp28bKLdNgtNQrRQ3BH7R0eSlGXlB5K
CB5jvuWbUQIACH4Vfw0H8p/hn1znzOz1hn8u0XcsizKw/ksHXyRaZHnqkE4u6dzy
CLh5onhjgYyDlIUb9iBAOz2zDcKHq5xNxMuosBKz47IVoUnSxRuDXwD7PysQyntK
gckSzn2RAgMBAAECggEBAIJ5/q80KHJtPnrermAER6AcU1QPKrwq271//xswQncI
jYvTeEvVKdgBMgvwK7NSb2a4FxKhRg7ucgEWSWECbvsvxmPeXBlYYv5fCguyJ4Sj
VrQYdyuStFm0Nmkc5D+/TL/fQyoq/xZcTZ5IKhfF0c8xa4I4ZU0fK2FR7qePDlHx
kAjInhIAPxCh7vhKk35duhr8r7IDQ33jVyPQ7DgsEIKRh85CVxkcwrtV1sY3LM/O
xmrYWxHzpke06qZBJROjAFKv1kV7NT3eKzgKg16yDkFqYdh38RnFsTB6/zgZ+rko
Jj23tynefYRx3e3feAvhnDQzY32HwKCA4fNm0brJrf0CgYEA7cdXzN0QLwvhvjem
t0gNdcfk0f9pM0wcYh0n7ESANsKAkjAOBqlvJ6tRV1LaqeIX+y1yeBnUIVH+dNfA
tM2nTiilvaasR1Er40c3eeyIhWJ8nC+wBGexxDg3Ys4B0azzcakCYkG6BuVdsAWD
aYdqWf6Tl80l7HwonCVFsu8nX+cCgYEAwJrX3agdZWTuAcFcdGIXWK1m8+4yGv6t
fvwh9X/rkDQHJ5HXDsHmTc8yh/Qa35OzcZJxBooW5azmzVpEbgE/HjnBpNDjp0VT
Xk5k+bZkWgp6wN8BFrh2Me8hliRs93vsUZ+fnFJWgxMTPMpOvhcw9YjucG6lGpwk
ynGkJ0/bZ8cCgYAs8hVioBbDDdfqANL+qhwBO3vBRio4jBaBZUl6m6gwsatj9rlw
AO8F7Jg/jWXP3vDxhbGxihBTDBCxPWcrxgPt/jj2FF9US7+kAn42CcP0kp1DWLBI
5ODxWj796jrly29o+K1+rTXgv9Jpx2EDvZkY0cpMU3brsLxsZ485N4OV2QKBgQCV
G0rinrOjO2/GjBs3Pnk0fYmmblD79Q37sNXZaR7ElIK1b4I+On5A3pcQCTqEu6O/
2M8HcQAo7qH/eFJhlzV2AOCY595WMKVJ7QbfCwTFcDd3+Syumj9miOpHgguZzKY2
yoyWSGgRMUNDXJt5LhsI+ukcwYuv/hG9aBzdEkWZIQKBgGLj5nwaJZWPJ381adJX
JhwQcnS7cZIKrAifCay1oOaOcdQq/07QdBEjR6YT/X7oZCPtiDOdat9vzWKLNEY/
nYY+XFijSz2CKvT+CScjJSxmrsCtiNBQRtaTSKWAcgCpSqN5S+mocWmInZBVtZev
1OueDMUyPAsCabIR4HiTgAIs
-----END PRIVATE KEY-----


================================================
FILE: go.mod
================================================
module github.com/syntaqx/serve

go 1.20

require github.com/stretchr/testify v1.11.1

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: internal/commands/server.go
================================================
package commands

import (
	"bufio"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/syntaqx/serve"
	"github.com/syntaqx/serve/internal/config"
	"github.com/syntaqx/serve/internal/middleware"
)

var getHTTPServerFunc = GetStdHTTPServer

// HTTPServer defines a returnable interface type for http.Server
type HTTPServer interface {
	ListenAndServe() error
	ListenAndServeTLS(certFile, keyFile string) error
}

// GetStdHTTPServer returns a standard net/http.Server configured for a given
// address and handler, and other sane defaults.
func GetStdHTTPServer(addr string, h http.Handler) HTTPServer {
	return &http.Server{
		Addr:              addr,
		Handler:           h,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      15 * time.Second,
		ReadHeaderTimeout: 60 * time.Second,
	}
}

// GetAuthUsers returns a map of users from a given io.Reader
func GetAuthUsers(r io.Reader) map[string]string {
	users := make(map[string]string)

	if r != nil {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			if line := strings.Split(scanner.Text(), ":"); len(line) == 2 { // use only if correct format
				users[line[0]] = line[1]
			}
		}

		if err := scanner.Err(); err != nil {
			log.Fatalf("error occurred during reading users file")
		}
	}

	return users
}

// Server implements the static http server command.
func Server(log *log.Logger, opt config.Flags, dir string) error {
	fs := serve.NewFileServer(serve.Options{
		Directory: dir,
	})

	// Authorization
	var f io.Reader
	if _, err := os.Stat(opt.UsersFile); !os.IsNotExist(err) {
		// Config file exists, load data
		f, err = os.Open(opt.UsersFile)
		if err != nil {
			log.Fatalf("unable to open users file %s", opt.UsersFile)
		}
	} else if opt.Debug {
		log.Printf("%s does not exist, authentication skipped", opt.UsersFile)
	}

	fs.Use(
		middleware.Logger(log),
		middleware.Recover(),
		middleware.CORS(),
		middleware.Auth(GetAuthUsers(f)),
	)

	addr := net.JoinHostPort(opt.Host, opt.Port)
	server := getHTTPServerFunc(addr, fs)

	if opt.EnableSSL {
		log.Printf("https server listening at %s", addr)
		return server.ListenAndServeTLS(opt.CertFile, opt.KeyFile)
	}

	log.Printf("http server listening at %s", addr)
	return server.ListenAndServe()
}


================================================
FILE: internal/commands/server_test.go
================================================
package commands

import (
	"bytes"
	"log"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/syntaqx/serve/internal/config"
	"github.com/syntaqx/serve/mock"
)

func getMockHTTPServerFunc(shouldError bool) func(addr string, h http.Handler) HTTPServer {
	return func(addr string, h http.Handler) HTTPServer {
		return &mock.HTTPServer{ShouldError: shouldError}
	}
}

func TestGetStdHTTPServer(t *testing.T) {
	_, ok := GetStdHTTPServer("", http.DefaultServeMux).(*http.Server)
	assert.True(t, ok)
}

func TestServer(t *testing.T) {
	getHTTPServerFunc = getMockHTTPServerFunc(false)

	assert := assert.New(t)

	var b bytes.Buffer
	log := log.New(&b, "[test] ", 0)
	opt := config.Flags{}

	assert.NoError(Server(log, opt, "."))
	assert.Contains(b.String(), "http server listening at")

	getHTTPServerFunc = GetStdHTTPServer
}

func TestServerErr(t *testing.T) {
	getHTTPServerFunc = getMockHTTPServerFunc(true)

	assert := assert.New(t)

	var b bytes.Buffer
	log := log.New(&b, "[test] ", 0)
	opt := config.Flags{}

	time.Sleep(200 * time.Millisecond)

	assert.Error(Server(log, opt, "."))
	time.Sleep(200 * time.Millisecond)

	getHTTPServerFunc = GetStdHTTPServer
}

func TestServerHTTPS(t *testing.T) {
	getHTTPServerFunc = getMockHTTPServerFunc(false)

	assert := assert.New(t)

	var b bytes.Buffer
	log := log.New(&b, "[test] ", 0)

	opt := config.Flags{
		EnableSSL: true,
		CertFile:  "../../fixtures/cert.pem",
		KeyFile:   "../../fixtures/key.pem",
	}

	assert.NoError(Server(log, opt, "."))
	assert.Contains(b.String(), "https server listening at")

	getHTTPServerFunc = GetStdHTTPServer
}

func TestGetAuthUsers(t *testing.T) {
	tests := []struct {
		input  string
		output map[string]string
	}{
		{ // Single user
			"user1:pass1", map[string]string{
				"user1": "pass1",
			},
		},
		{ // Multiple users
			"user1:pass1\nuser2:pass2", map[string]string{
				"user1": "pass1",
				"user2": "pass2",
			},
		},
		{ // Empty file
			"", map[string]string{},
		},
		{ // Incorrect structure
			"user1:pass1:field1", map[string]string{},
		},
	}

	for _, test := range tests {
		mockFile := strings.NewReader(test.input)
		assert.Equal(t, GetAuthUsers(mockFile), test.output)
	}

}


================================================
FILE: internal/commands/version.go
================================================
package commands

import (
	"fmt"
	"io"
	"runtime"
)

// Version implements the command `version` which outputs the current binary
// release version, if any.
func Version(version string, w io.Writer) error {
	fmt.Fprintf(w, "serve version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
	return nil
}


================================================
FILE: internal/commands/version_test.go
================================================
package commands

import (
	"bytes"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestVersion(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	var b bytes.Buffer
	err := Version("mock", &b)

	assert.NoError(err)
	assert.Contains(b.String(), "version mock")
}


================================================
FILE: internal/config/flags.go
================================================
package config

import (
	"fmt"
	"os"
)

var getwd = os.Getwd

// Flags are the expose configuration flags available to the serve binary.
type Flags struct {
	Debug     bool
	Host      string
	Port      string
	EnableSSL bool
	CertFile  string
	KeyFile   string
	Directory string
	UsersFile string
}

// SanitizeDir allows a directory source to be set from multiple values. If any
// value is defined, that value is used. If none are defined, the current
// working directory is retrieved.
func SanitizeDir(dirs ...string) (string, error) {
	for _, dir := range dirs {
		if len(dir) > 0 {
			return dir, nil
		}
	}

	cwd, err := getwd()
	if err != nil {
		return "", fmt.Errorf("cannot determine cwd: %v", err)
	}

	return cwd, nil
}


================================================
FILE: internal/config/flags_test.go
================================================
package config

import (
	"errors"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSanitizeDir(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	cwd, err := os.Getwd()
	assert.NoError(err)

	var tests = []struct {
		dirs     []string
		expected string
	}{
		{[]string{"foo", "bar"}, "foo"},
		{[]string{"", "bar"}, "bar"},
		{[]string{"", ""}, cwd},
	}

	for _, tt := range tests {
		tt := tt
		t.Run("", func(t *testing.T) {
			t.Parallel()
			dir, err := SanitizeDir(tt.dirs...)
			assert.Equal(tt.expected, dir)
			assert.NoError(err)
		})
	}
}

func TestSanitizeDirCwdErr(t *testing.T) {
	assert := assert.New(t)

	getwd = func() (string, error) {
		return "", errors.New("mock")
	}

	dir, err := SanitizeDir()
	assert.Empty(dir)
	assert.Error(err)

	getwd = os.Getwd
}


================================================
FILE: internal/middleware/auth.go
================================================
package middleware

import "net/http"

// Auth sets basic HTTP authorization
func Auth(users map[string]string) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			// Only require auth if we have any users
			if len(users) > 0 {
				authUser, authPass, ok := r.BasicAuth()
				if !ok {
					// No username/password received
					w.Header().Set("WWW-Authenticate", "Basic realm=Authenticate")
					w.WriteHeader(http.StatusUnauthorized)
				} else {
					if pass, ok := users[authUser]; ok {
						// User exists
						if pass == authPass {
							// Authentication successful
							next.ServeHTTP(w, r)
						} else {
							http.Error(w, "Incorrect login details", http.StatusUnauthorized)
							return
						}
					} else {
						http.Error(w, "Incorrect login details", http.StatusUnauthorized)
						return
					}
				}
			} else {
				next.ServeHTTP(w, r)
			}
		}

		return http.HandlerFunc(fn)
	}
}


================================================
FILE: internal/middleware/auth_test.go
================================================
package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestAuth(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	req, err := http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res := httptest.NewRecorder()

	// No users
	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
	Auth(nil)(testHandler).ServeHTTP(res, req)
	assert.Equal("", res.Header().Get("WWW-Authenticate"))

	// Some users
	testUsers := map[string]string{
		"user1": "pass1",
		"user2": "pass2",
	}
	Auth(testUsers)(testHandler).ServeHTTP(res, req)
	assert.Equal("Basic realm=Authenticate", res.Header().Get("WWW-Authenticate"))
	assert.Equal(http.StatusUnauthorized, res.Result().StatusCode)

	// Correct password
	// Recreate new environment
	testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
	req, err = http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res = httptest.NewRecorder()

	req.SetBasicAuth("user1", "pass1")
	Auth(testUsers)(testHandler).ServeHTTP(res, req)
	assert.Equal("", res.Header().Get("WWW-Authenticate"))
	assert.Equal(http.StatusOK, res.Result().StatusCode)

	// Incorrect password
	// Recreate new environment
	testHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
	req, err = http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res = httptest.NewRecorder()

	req.SetBasicAuth("user1", "pass2")
	Auth(testUsers)(testHandler).ServeHTTP(res, req)
	assert.Equal(http.StatusUnauthorized, res.Result().StatusCode)
}


================================================
FILE: internal/middleware/cors.go
================================================
package middleware

import (
	"net/http"
	"strings"
)

// CORS sets permissive cross-origin resource sharing rules.
func CORS() func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Access-Control-Allow-Methods", strings.Join([]string{
				http.MethodHead,
				http.MethodOptions,
				http.MethodGet,
				http.MethodPost,
				http.MethodPut,
				http.MethodPatch,
				http.MethodDelete,
			}, ", "))
			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}
}


================================================
FILE: internal/middleware/cors_test.go
================================================
package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestCORS(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	req, err := http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
	CORS()(testHandler).ServeHTTP(res, req)

	assert.Equal("*", res.Header().Get("Access-Control-ALlow-Origin"))
	assert.Contains(res.Header().Get("Access-Control-Allow-Methods"), http.MethodGet)
}


================================================
FILE: internal/middleware/logger.go
================================================
package middleware

import (
	"log"
	"net/http"
)

// Logger is a middleware that logs each request, along with some useful data
// about what was requested, and what the response was.
func Logger(log *log.Logger) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			sw := statusWriter{ResponseWriter: w}

			defer func() {
				log.Println(r.Method, r.URL.Path, sw.status, r.RemoteAddr, r.UserAgent())
			}()
			next.ServeHTTP(&sw, r)
		}
		return http.HandlerFunc(fn)
	}
}


================================================
FILE: internal/middleware/logger_test.go
================================================
package middleware

import (
	"bytes"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

var logTests = []struct {
	in  func(w http.ResponseWriter, r *http.Request)
	out string
}{
	{
		in: func(w http.ResponseWriter, _ *http.Request) {
			_, err := w.Write([]byte{})
			if err != nil {
				panic(err)
			}
		},
		out: "[test] GET / 200",
	},
	{
		in: func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusNotFound)
		},
		out: "[test] GET / 404",
	},
}

func TestLogger(t *testing.T) {
	t.Parallel()

	for _, tt := range logTests {
		assert := assert.New(t)

		var b bytes.Buffer
		log := log.New(&b, "[test] ", 0)

		req, err := http.NewRequest(http.MethodGet, "/", nil)
		assert.NoError(err)
		res := httptest.NewRecorder()

		testHandler := http.HandlerFunc(tt.in)
		Logger(log)(testHandler).ServeHTTP(res, req)

		assert.Equal(tt.out, strings.TrimSpace(b.String()))
	}
}


================================================
FILE: internal/middleware/recover.go
================================================
package middleware

import (
	"fmt"
	"net/http"
)

// Recover is a middleware that recovers from panics that occur for a request.
func Recover() func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			defer func() {
				if err := recover(); err != nil {
					http.Error(w, fmt.Sprintf("[PANIC RECOVERED] %v", err), http.StatusInternalServerError)
				}
			}()
			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}
}


================================================
FILE: internal/middleware/recover_test.go
================================================
package middleware

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestRecover(t *testing.T) {

	t.Parallel()
	assert := assert.New(t)

	req, err := http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res := httptest.NewRecorder()

	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		panic("test")
	})

	Recover()(testHandler).ServeHTTP(res, req)

	assert.Equal(http.StatusInternalServerError, res.Code)
	assert.Equal("[PANIC RECOVERED] test", strings.TrimSpace(res.Body.String()))
}


================================================
FILE: internal/middleware/statuswriter.go
================================================
package middleware

import "net/http"

type statusWriter struct {
	http.ResponseWriter
	status int
}

func (w *statusWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *statusWriter) Write(b []byte) (int, error) {
	if w.status == 0 {
		w.status = http.StatusOK
	}
	return w.ResponseWriter.Write(b)
}


================================================
FILE: mock/http.go
================================================
package mock

import "errors"

// ErrMock is a mock error
var ErrMock = errors.New("mock error")

// HTTPServer is a mock http server
type HTTPServer struct {
	ShouldError bool
}

// ListenAndServe is a mock http server method
func (s *HTTPServer) ListenAndServe() error {
	if s.ShouldError {
		return ErrMock
	}
	return nil
}

// ListenAndServeTLS is a mock http server method
func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error {
	if s.ShouldError {
		return ErrMock
	}
	return nil
}


================================================
FILE: serve.go
================================================
// Package serve provides a static http server anywhere you need one.
package serve

import "net/http"

// Options is a struct for specifying configuration options for a FileServer.
type Options struct {
	// Directory is the root directory from which to serve files.
	Directory string

	// Prefix is a filepath prefix that should be ignored by the FileServer.
	Prefix string
}

// FileServer wraps an http.FileServer.
type FileServer struct {
	opt     Options
	handler http.Handler
}

// NewFileServer initializes a FileServer.
func NewFileServer(options ...Options) *FileServer {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}

	fs := &FileServer{
		opt: opt,
	}

	fs.handler = http.StripPrefix(opt.Prefix, http.FileServer(http.Dir(opt.Directory)))
	return fs
}

// Use wraps the Handler with middleware(s).
func (fs *FileServer) Use(mws ...func(http.Handler) http.Handler) {
	for _, h := range mws {
		fs.handler = h(fs.handler)
	}
}

// ServeHTTP implements the net/http.Handler interface.
func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fs.handler.ServeHTTP(w, r)
}


================================================
FILE: serve_test.go
================================================
package serve

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFileServerDefaults(t *testing.T) {
	fs := NewFileServer()
	_ = fs
}

func TestFileServerOptions(t *testing.T) {
	fs := NewFileServer(Options{Directory: "test"})
	_ = fs
}

func TestFileServerUse(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	req, err := http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res := httptest.NewRecorder()

	testMiddleware1 := func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			_, _ = w.Write([]byte("start\n"))
			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}

	testMiddleware2 := func(next http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, _ *http.Request) {
			_, _ = w.Write([]byte("end\n"))
		}
		return http.HandlerFunc(fn)
	}

	fs := &FileServer{
		handler: http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
			t.Fail()
		}),
	}

	fs.Use(testMiddleware2, testMiddleware1)

	fs.ServeHTTP(res, req)

	assert.Equal("start\nend\n", res.Body.String())
}

func TestFileServerServeHTTP(t *testing.T) {
	t.Parallel()
	assert := assert.New(t)

	req, err := http.NewRequest(http.MethodGet, "/", nil)
	assert.NoError(err)
	res := httptest.NewRecorder()

	fs := &FileServer{
		handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			_, _ = w.Write([]byte("expected"))
		}),
	}

	fs.ServeHTTP(res, req)

	assert.Equal("expected", res.Body.String())
}


================================================
FILE: static/index.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Welcome to your server!</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Mukta">

  <style>
    *, ::after, ::before {
      box-sizing: border-box;
    }

    html, body {
      background-color: #eee;
      margin: 0;
      height: 100vh;
    }

    body {
      font-family: 'Mukta', sans-serif;
      font-size: 1rem;
      font-weight: 400;
      line-height: 1.5;
      color: #656f79;
    }

    a {
      text-decoration: none;
      color: #5ba7ce;
    }

    a:hover {
      text-decoration: underline;
    }

    h1, h2, h3, h4, h5, h6 {
      margin-top: 0;
      margin-bottom: .5rem;
      line-height: 1.2;
    }

    h1 {
      font-size: 2.5rem;
    }

    h2 {
      font-size: 1.8rem;
    }

    h3 {
      font-size: 1.75rem;
    }

    .vh-100 {
      height: 100vh;
    }

    .mb-2 {
      margin-bottom: 2rem;
    }

    .flex-center {
      align-items: center;
      justify-content: center;
      display: flex;
    }

    .position-rel {
      position: relative;
    }

    .text-center {
      text-align: center;
    }

    .title {
      margin-top: 2rem;
      letter-spacing: -.2rem;
      font-weight: 100;
      font-size: 4rem;
      color: #363a3f;
    }

    .links {
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .links li {
      margin: 0 .5rem 0 0;
      padding: 0;
      display: inline-block;
    }

  </style>
</head>
<body>

  <div class="flex-center position-rel vh-100">
    <div class="text-center">
      <header class="mb-2">
        <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTYuMSA4LjgiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudCk7fTwvc3R5bGU+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHkxPSI4LjgiIHgyPSIxNCIgeTI9IjguOCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLCAwLCAwLCAtMSwgMCwgMTMuMikiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwMDAwOTIiIC8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmYwMGYzIiAvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0aXRsZT5Bc3NldCAyPC90aXRsZT48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iTGF5ZXJfMS0yIiBkYXRhLW5hbWU9IkxheWVyIDEiPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTgsMEExLjU0LDEuNTQsMCwwLDAsNi41LDEuNWExLjM1LDEuMzUsMCwwLDAsMCwuMTgsNi41LDYuNSwwLDAsMC01LDYuMTJILjZhLjU2LjU2LDAsMCwwLS42LjUuNTYuNTYsMCwwLDAsLjYuNUgxNS40Yy4zLDAsLjctLjIuNy0uNWEuNTYuNTYsMCwwLDAtLjYtLjVoLTFhNi41LDYuNSwwLDAsMC01LTYuMTIsMSwxLDAsMCwwLDAtLjE4QTEuNTQsMS41NCwwLDAsMCw4LDBaTTgsMWEuNDcuNDcsMCwwLDEsLjUuNXYwYTQuMTgsNC4xOCwwLDAsMC0xLDB2MEEuNDcuNDcsMCwwLDEsOCwxWk04LDIuNWE1LjUxLDUuNTEsMCwwLDEsNS40OSw1LjNoLTExQTUuNTEsNS41MSwwLDAsMSw4LDIuNVptLS44OS43OGEuMzkuMzksMCwwLDAtLjIxLDBBNC44OCw0Ljg4LDAsMCwwLDMuMyw2LjlhLjU1LjU1LDAsMCwwLC40LjZoLjFhLjQzLjQzLDAsMCwwLC40LS40LDQuMTksNC4xOSwwLDAsMSwzLTIuOS40OS40OSwwLDAsMCwuMy0uNkEuNTMuNTMsMCwwLDAsNy4xMSwzLjI4WiIgLz48L2c+PC9nPjwvc3ZnPg==" width="250">
        <h1 class="title">serve</h1>
        <h2>Your web server is working correctly!</h2>
      </header>
      <p>You can edit this file at <code>/var/www/index.html</code></p>
      <ul class="links">
        <li><a href="https://github.com/syntaqx/serve/tree/main/examples">Examples</a></li>
        <li><a href="https://github.com/syntaqx/serve">GitHub</a></li>
        <li><a href="https://hub.docker.com/r/syntaqx/serve">Docker Hub</a></li>
      </ul>
    </div>
  </div>

</body>
</html>
Download .txt
gitextract_g0z6iav5/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── codeql.yml
│       ├── dependabot.yml
│       ├── docker.yml
│       ├── go.yml
│       ├── golangci-lint.yml
│       └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── serve/
│       └── main.go
├── compose.yml
├── examples/
│   └── basic/
│       └── main.go
├── fixtures/
│   ├── cert.pem
│   └── key.pem
├── go.mod
├── go.sum
├── internal/
│   ├── commands/
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── version.go
│   │   └── version_test.go
│   ├── config/
│   │   ├── flags.go
│   │   └── flags_test.go
│   └── middleware/
│       ├── auth.go
│       ├── auth_test.go
│       ├── cors.go
│       ├── cors_test.go
│       ├── logger.go
│       ├── logger_test.go
│       ├── recover.go
│       ├── recover_test.go
│       └── statuswriter.go
├── mock/
│   └── http.go
├── serve.go
├── serve_test.go
└── static/
    └── index.html
Download .txt
SYMBOL INDEX (41 symbols across 20 files)

FILE: cmd/serve/main.go
  function main (line 15) | func main() {

FILE: examples/basic/main.go
  function main (line 10) | func main() {

FILE: internal/commands/server.go
  type HTTPServer (line 21) | type HTTPServer interface
  function GetStdHTTPServer (line 28) | func GetStdHTTPServer(addr string, h http.Handler) HTTPServer {
  function GetAuthUsers (line 39) | func GetAuthUsers(r io.Reader) map[string]string {
  function Server (line 59) | func Server(log *log.Logger, opt config.Flags, dir string) error {

FILE: internal/commands/server_test.go
  function getMockHTTPServerFunc (line 16) | func getMockHTTPServerFunc(shouldError bool) func(addr string, h http.Ha...
  function TestGetStdHTTPServer (line 22) | func TestGetStdHTTPServer(t *testing.T) {
  function TestServer (line 27) | func TestServer(t *testing.T) {
  function TestServerErr (line 42) | func TestServerErr(t *testing.T) {
  function TestServerHTTPS (line 59) | func TestServerHTTPS(t *testing.T) {
  function TestGetAuthUsers (line 79) | func TestGetAuthUsers(t *testing.T) {

FILE: internal/commands/version.go
  function Version (line 11) | func Version(version string, w io.Writer) error {

FILE: internal/commands/version_test.go
  function TestVersion (line 10) | func TestVersion(t *testing.T) {

FILE: internal/config/flags.go
  type Flags (line 11) | type Flags struct
  function SanitizeDir (line 25) | func SanitizeDir(dirs ...string) (string, error) {

FILE: internal/config/flags_test.go
  function TestSanitizeDir (line 11) | func TestSanitizeDir(t *testing.T) {
  function TestSanitizeDirCwdErr (line 38) | func TestSanitizeDirCwdErr(t *testing.T) {

FILE: internal/middleware/auth.go
  function Auth (line 6) | func Auth(users map[string]string) func(next http.Handler) http.Handler {

FILE: internal/middleware/auth_test.go
  function TestAuth (line 11) | func TestAuth(t *testing.T) {

FILE: internal/middleware/cors.go
  function CORS (line 9) | func CORS() func(next http.Handler) http.Handler {

FILE: internal/middleware/cors_test.go
  function TestCORS (line 11) | func TestCORS(t *testing.T) {

FILE: internal/middleware/logger.go
  function Logger (line 10) | func Logger(log *log.Logger) func(next http.Handler) http.Handler {

FILE: internal/middleware/logger_test.go
  function TestLogger (line 35) | func TestLogger(t *testing.T) {

FILE: internal/middleware/recover.go
  function Recover (line 9) | func Recover() func(next http.Handler) http.Handler {

FILE: internal/middleware/recover_test.go
  function TestRecover (line 12) | func TestRecover(t *testing.T) {

FILE: internal/middleware/statuswriter.go
  type statusWriter (line 5) | type statusWriter struct
    method WriteHeader (line 10) | func (w *statusWriter) WriteHeader(status int) {
    method Write (line 15) | func (w *statusWriter) Write(b []byte) (int, error) {

FILE: mock/http.go
  type HTTPServer (line 9) | type HTTPServer struct
    method ListenAndServe (line 14) | func (s *HTTPServer) ListenAndServe() error {
    method ListenAndServeTLS (line 22) | func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error {

FILE: serve.go
  type Options (line 7) | type Options struct
  type FileServer (line 16) | type FileServer struct
    method Use (line 37) | func (fs *FileServer) Use(mws ...func(http.Handler) http.Handler) {
    method ServeHTTP (line 44) | func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  function NewFileServer (line 22) | func NewFileServer(options ...Options) *FileServer {

FILE: serve_test.go
  function TestFileServerDefaults (line 11) | func TestFileServerDefaults(t *testing.T) {
  function TestFileServerOptions (line 16) | func TestFileServerOptions(t *testing.T) {
  function TestFileServerUse (line 21) | func TestFileServerUse(t *testing.T) {
  function TestFileServerServeHTTP (line 57) | func TestFileServerServeHTTP(t *testing.T) {
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (54K chars).
[
  {
    "path": ".dockerignore",
    "chars": 174,
    "preview": "*.yml\n*.yaml\n*.json\n*.md\n\n.git*\nbin\ndist\nbuild\ndocs\nexamples\ntmp\nvendor\n\n.editorconfig\nDockerfile\nCODEOWNERS\nLICENSE\nMak"
  },
  {
    "path": ".editorconfig",
    "chars": 423,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_style = space\nmax_line_length = 120\ninsert_final_newline = true"
  },
  {
    "path": ".gitattributes",
    "chars": 12,
    "preview": "* text=auto\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 183,
    "preview": "version: 2\nupdates:\n- package-ecosystem: github-actions\n  directory: \"/\"\n  schedule:\n    interval: weekly\n- package-ecos"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 3002,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/dependabot.yml",
    "chars": 1203,
    "preview": "name: Dependabot Approve & Auto-Merge\non:\n  pull_request:\n    branches: [ main ]\n\npermissions:\n  contents: write\n  pull-"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 2200,
    "preview": "name: Docker\n\non:\n  workflow_call:\n    inputs:\n      push:\n        description: \"Push the image to the registry\"\n       "
  },
  {
    "path": ".github/workflows/go.yml",
    "chars": 535,
    "preview": "name: Go\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n\n  test:\n    runs-on: ubuntu-"
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "chars": 1430,
    "preview": "name: golangci-lint\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - main\n  pull_request:\n\npermissions:\n  contents"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 171,
    "preview": "name: cd\n\non:\n  push:\n    tags:\n      - v*\n  workflow_dispatch:\n\njobs:\n\n  docker:\n    uses: ./.github/workflows/docker.y"
  },
  {
    "path": ".gitignore",
    "chars": 365,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# SQLite Da"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 4354,
    "preview": "# Contributing\n\n[semver]: http://semver.org/\n\nWhen contributing to this repository, please first discuss the change you "
  },
  {
    "path": "Dockerfile",
    "chars": 797,
    "preview": "FROM golang:1.20-alpine AS builder\n\nARG VERSION=\"0.0.0-docker\"\n\nRUN apk add --update --no-cache \\\n  ca-certificates tzda"
  },
  {
    "path": "LICENSE",
    "chars": 1094,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Chase Pierce <syntaqx@gmail.com>\n\nPermission is hereby granted, free of charge, to "
  },
  {
    "path": "Makefile",
    "chars": 218,
    "preview": "VERSION=`git --no-pager describe --tags --always`\n\nLDFLAGS+=\nLDFLAGS+=-X main.version=${VERSION}\n\nbuild:\n\tgo build -ldfl"
  },
  {
    "path": "README.md",
    "chars": 6351,
    "preview": "# <img src=\"https://raw.githubusercontent.com/syntaqx/serve/main/docs/logo.svg?sanitize=true\" width=\"250\">\n\n`serve` is a"
  },
  {
    "path": "cmd/serve/main.go",
    "chars": 1442,
    "preview": "// Package main implements the runtime for the serve binary.\npackage main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/s"
  },
  {
    "path": "compose.yml",
    "chars": 548,
    "preview": "services:\n\n  # Note: You probably will want to remove the `build: .` lines if you copy\n  # these into your project. That"
  },
  {
    "path": "examples/basic/main.go",
    "chars": 265,
    "preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/syntaqx/serve\"\n)\n\nfunc main() {\n\tfs := serve.NewFileServer(serve"
  },
  {
    "path": "fixtures/cert.pem",
    "chars": 1493,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIEIDCCAwigAwIBAgIUG4x9A3w/n65jwz3y7Wo8MDrU6QEwDQYJKoZIhvcNAQEL\nBQAweTELMAkGA1UEBhMCVVMxCzA"
  },
  {
    "path": "fixtures/key.pem",
    "chars": 1704,
    "preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy5VREwwMX6WzK\ny/b5UyrtsvhvMu6jbyiHaeRZZRw"
  },
  {
    "path": "go.mod",
    "chars": 233,
    "preview": "module github.com/syntaqx/serve\n\ngo 1.20\n\nrequire github.com/stretchr/testify v1.11.1\n\nrequire (\n\tgithub.com/davecgh/go-"
  },
  {
    "path": "go.sum",
    "chars": 883,
    "preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
  },
  {
    "path": "internal/commands/server.go",
    "chars": 2253,
    "preview": "package commands\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/syntaqx/serve"
  },
  {
    "path": "internal/commands/server_test.go",
    "chars": 2230,
    "preview": "package commands\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/asse"
  },
  {
    "path": "internal/commands/version.go",
    "chars": 306,
    "preview": "package commands\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n)\n\n// Version implements the command `version` which outputs the curr"
  },
  {
    "path": "internal/commands/version_test.go",
    "chars": 279,
    "preview": "package commands\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestVersion(t *testing.T) "
  },
  {
    "path": "internal/config/flags.go",
    "chars": 734,
    "preview": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\nvar getwd = os.Getwd\n\n// Flags are the expose configuration flags available to "
  },
  {
    "path": "internal/config/flags_test.go",
    "chars": 801,
    "preview": "package config\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSanitizeDir(t *te"
  },
  {
    "path": "internal/middleware/auth.go",
    "chars": 1000,
    "preview": "package middleware\n\nimport \"net/http\"\n\n// Auth sets basic HTTP authorization\nfunc Auth(users map[string]string) func(nex"
  },
  {
    "path": "internal/middleware/auth_test.go",
    "chars": 1600,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc "
  },
  {
    "path": "internal/middleware/cors.go",
    "chars": 623,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// CORS sets permissive cross-origin resource sharing rules.\nfunc"
  },
  {
    "path": "internal/middleware/cors_test.go",
    "chars": 570,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc "
  },
  {
    "path": "internal/middleware/logger.go",
    "chars": 565,
    "preview": "package middleware\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\n// Logger is a middleware that logs each request, along with some use"
  },
  {
    "path": "internal/middleware/logger_test.go",
    "chars": 949,
    "preview": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretc"
  },
  {
    "path": "internal/middleware/recover.go",
    "chars": 511,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// Recover is a middleware that recovers from panics that occur for a"
  },
  {
    "path": "internal/middleware/recover_test.go",
    "chars": 590,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/asser"
  },
  {
    "path": "internal/middleware/statuswriter.go",
    "chars": 351,
    "preview": "package middleware\n\nimport \"net/http\"\n\ntype statusWriter struct {\n\thttp.ResponseWriter\n\tstatus int\n}\n\nfunc (w *statusWri"
  },
  {
    "path": "mock/http.go",
    "chars": 505,
    "preview": "package mock\n\nimport \"errors\"\n\n// ErrMock is a mock error\nvar ErrMock = errors.New(\"mock error\")\n\n// HTTPServer is a moc"
  },
  {
    "path": "serve.go",
    "chars": 1112,
    "preview": "// Package serve provides a static http server anywhere you need one.\npackage serve\n\nimport \"net/http\"\n\n// Options is a "
  },
  {
    "path": "serve_test.go",
    "chars": 1523,
    "preview": "package serve\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestF"
  },
  {
    "path": "static/index.html",
    "chars": 3616,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, in"
  }
]

About this extraction

This page contains the full source code of the syntaqx/serve GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (48.0 KB), approximately 15.7k tokens, and a symbol index with 41 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

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

Copied to clipboard!