16 Commits

Author SHA1 Message Date
f8f0302564 Make linter happy and update dependencies.
Some checks failed
continuous-integration/drone/push Build is failing
2022-06-26 22:26:39 +05:00
7b6a425908 Update Drone configuration. 2022-06-26 22:10:53 +05:00
591c24bab7 Update Drone configuration. 2022-06-26 22:08:23 +05:00
d85c9cb53c Update Drone configuration. 2022-06-26 22:04:03 +05:00
2b44a60ee7 The Great Linting Fixes, Drone configuration fix (again) and flatfile changes.
Great linting fixes has been applied, thanks to golangci-lint for
extensive reporting.

Fixed Drone configuration to use array for when-branch statement in
Docker plugin.

Flatfile storage from now will write files with 0600 permission for
greater security.
2021-11-20 22:19:58 +05:00
218e0bf667 Remove -mod=vendor from golangci-lint GOFLAGS. 2021-11-20 22:00:52 +05:00
006eb6e72a Use newer golang with Drone and update badge in README. 2021-11-20 22:00:02 +05:00
5eae5595e9 Drone fixes. 2021-11-20 02:23:47 +05:00
0f47434f60 Move from fileb0x to embed.FS. Fixes #22.
Also removed unused fontawesome JS.
2021-06-16 06:22:33 +05:00
383233202e Fix registry logging in by CI when uploading Docker image. 2021-06-15 00:16:44 +05:00
968d945205 More Greater Linting. 2021-06-15 00:11:58 +05:00
6ea6e2e144 The Great Sources Linting. 2021-06-14 23:48:34 +05:00
3265c5a4b2 Proper image-alias for DIND service in CI. 2021-06-14 23:10:35 +05:00
1210ecb510 Fix CI config. 2021-06-14 23:08:54 +05:00
dec022e460 Update CI configuration. 2021-06-14 23:05:59 +05:00
79791ef228 Update golang versions everywhere, use alpine 3.13 for image, remove vendor dir. 2021-06-14 22:59:21 +05:00
969 changed files with 721 additions and 380395 deletions

View File

@@ -1,69 +1,52 @@
---
kind: pipeline
type: docker
name: build
name: lint and test
steps:
- name: notify-start
image: pztrn/discordrone
settings:
webhook_id:
from_secret: discord_webhook_id
webhook_token:
from_secret: discord_webhook_secret
message: 'Starting building **{{repo.name}}#{{build.number}}@{{commit.sha}}** @ {{datetime build.started "02-Jan-2006 15:04:05 MST" "Asia/Yekaterinburg"}} (See {{build.link}} for logs).'
- name: lint
image: golangci/golangci-lint:latest
image: golangci/golangci-lint:v1.46.2
environment:
GOFLAGS: -mod=vendor
CGO_ENABLED: 0
commands:
- golangci-lint run
depends_on:
- notify-start
- name: test
image: golang:1.13.5-alpine
image: golang:1.18.3-alpine
environment:
GOFLAGS: -mod=vendor
CGO_ENABLED: 0
commands:
- go test ./...
depends_on:
- notify-start
- name: docker
---
kind: pipeline
type: docker
name: build docker images
depends_on:
- "lint and test"
steps:
- name: build master image
image: plugins/docker
when:
branch: master
branch: ["master"]
settings:
username:
from_secret: dockerhub_user
registry: code.pztrn.name
username: drone
password:
from_secret: dockerhub_password
repo: pztrn/fastpastebin
from_secret: drone_secret
repo: code.pztrn.name/pztrn/fastpastebin
auto_tag: true
depends_on:
- lint
- test
- name: notify-end
- name: build tagged image
image: plugins/docker
when:
status:
- success
- failure
image: pztrn/discordrone
event: ["tag"]
settings:
webhook_id:
from_secret: discord_webhook_id
webhook_token:
from_secret: discord_webhook_secret
message: "
{{#success build.status}}
**{{repo.name}}#{{build.number}}@{{commit.sha}}** built in {{since build.startedint}} and pushed to hub.docker.com.
{{ else }}
**{{repo.name}}#{{build.number}}@{{commit.sha}}** failed. See {{build.link}}.
{{/success}}"
depends_on:
- docker
registry: code.pztrn.name
username: drone
password:
from_secret: drone_secret
repo: code.pztrn.name/pztrn/fastpastebin
auto_tag: true

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
examples/fastpastebin.yaml
dist/
data/
vendor/
.idea
.vscode
*DS_Store*

View File

@@ -1,28 +1,35 @@
image: docker:dind
variables:
HOST: 0.0.0.0
PORT: 2375
DOCKER_HOST: tcp://docker:2375/
DOCKER_DRIVER: overlay2
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
GIT_STRATEGY: clone
DOCKER_TCP_PORT: 2375
DOCKER_TLS_CERTDIR: ""
CONTAINER_NAME: registry.gitlab.pztrn.name/fastpastebin/fastpastebin
DIND_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:dind
GOLANGCILINT_IMAGE: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.40.1-alpine
services:
- docker:dind
- name: ${DIND_IMAGE}
alias: docker
stages:
- test
- build
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build:
stage: build
lint:
stage: test
image: ${GOLANGCILINT_IMAGE}
tags:
- docker
script:
- golangci-lint run ./...
build:
stage: build
image: $DIND_IMAGE
tags:
- docker
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- source docker/set_docker_tag.sh
- docker build -t $CONTAINER_NAME:$DOCKER_TAG .
- docker push $CONTAINER_NAME:$DOCKER_TAG

View File

@@ -12,6 +12,11 @@ linters:
- funlen
# Magic numbers everywhere and we can't get rid of them.
- gomnd
# This linter MIGHT BE good, but who decided that I want keepFor in
# JSON instead of keep_for for KeepFor field?
- tagliatelle
# Deprecated.
- exhaustivestruct
linters-settings:
lll:
line-length: 420
@@ -19,3 +24,12 @@ linters-settings:
min-complexity: 50
gocyclo:
min-complexity: 40
cyclop:
max-complexity: 40
issues:
exclude-rules:
# There will be some ToDos.
- linters:
- godox
text: "TODO"

View File

@@ -1,13 +1,13 @@
FROM golang:1.13.1-alpine AS build
FROM golang:1.16.5-alpine AS build
WORKDIR /fastpastebin
COPY . .
WORKDIR /fastpastebin/cmd/fastpastebin
RUN GOFLAGS="-mod=vendor" go build
RUN CGO_ENABLED=0 go build -tags netgo
FROM alpine:3.10
FROM alpine:3.13
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
COPY --from=build /fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin

View File

@@ -1,6 +1,6 @@
# Fast Pastebin
[![Drone (self-hosted)](https://img.shields.io/drone/build/fastpastebin/fastpastebin?server=https%3A%2F%2Fci.dev.pztrn.name)](https://ci.dev.pztrn.name/fastpastebin/fastpastebin/) [![Discord](https://img.shields.io/discord/632359730089689128)](https://discord.gg/qHN6KsD) ![Keybase XLM](https://img.shields.io/keybase/xlm/pztrn)
[![Build Status](https://github-ci.pztrn.name/api/badges/pztrn/fastpastebin/status.svg)](https://github-ci.pztrn.name/pztrn/fastpastebin) ![Keybase XLM](https://img.shields.io/keybase/xlm/pztrn)
Easy-to-use-and-install pastebin software written in Go. No bells or whistles, no websockets and even NO JAVASCRIPT!

7
assets/assets.go Normal file
View File

@@ -0,0 +1,7 @@
package assets
import "embed"
// Data is an embedded assets data.
//go:embed *
var Data embed.FS

File diff suppressed because one or more lines are too long

View File

@@ -1,183 +0,0 @@
// Code generated by fileb0x at "2021-01-09 06:15:34.328599857 +0500 +05 m=+0.033228523" from config file "fileb0x.yml" DO NOT EDIT.
// modification hash(a501d12d9fe3316e4b2134554bdc730c.238872c2653234e79053054d1ba776af)
package static
import (
"bytes"
"context"
"io"
"net/http"
"os"
"path"
"golang.org/x/net/webdav"
)
var (
// CTX is a context for webdav vfs
CTX = context.Background()
// FS is a virtual memory file system
FS = webdav.NewMemFS()
// Handler is used to server files through a http handler
Handler *webdav.Handler
// HTTP is the http file system
HTTP http.FileSystem = new(HTTPFS)
)
// HTTPFS implements http.FileSystem
type HTTPFS struct {
// Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
Prefix string
}
func init() {
err := CTX.Err()
if err != nil {
panic(err)
}
err = FS.Mkdir(CTX, "static/", 0777)
if err != nil && err != os.ErrExist {
panic(err)
}
err = FS.Mkdir(CTX, "static/css/", 0777)
if err != nil && err != os.ErrExist {
panic(err)
}
err = FS.Mkdir(CTX, "static/js/", 0777)
if err != nil && err != os.ErrExist {
panic(err)
}
Handler = &webdav.Handler{
FileSystem: FS,
LockSystem: webdav.NewMemLS(),
}
}
// Open a file
func (hfs *HTTPFS) Open(path string) (http.File, error) {
path = hfs.Prefix + path
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
if err != nil {
return nil, err
}
return f, nil
}
// ReadFile is adapTed from ioutil
func ReadFile(path string) ([]byte, error) {
f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
// If the buffer overflows, we will get bytes.ErrTooLarge.
// Return that as an error. Any other panic remains.
defer func() {
e := recover()
if e == nil {
return
}
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
_, err = buf.ReadFrom(f)
return buf.Bytes(), err
}
// WriteFile is adapTed from ioutil
func WriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := FS.OpenFile(CTX, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
// WalkDirs looks for files in the given dir and returns a list of files in it
// usage for all files in the b0x: WalkDirs("", false)
func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) {
f, err := FS.OpenFile(CTX, name, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
fileInfos, err := f.Readdir(0)
if err != nil {
return nil, err
}
err = f.Close()
if err != nil {
return nil, err
}
for _, info := range fileInfos {
filename := path.Join(name, info.Name())
if includeDirsInList || !info.IsDir() {
files = append(files, filename)
}
if info.IsDir() {
files, err = WalkDirs(filename, includeDirsInList, files...)
if err != nil {
return nil, err
}
}
}
return files, nil
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.380518788 +0500 +05 m=+0.085147493" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
// original path: assets/database_not_available.html
package static
import (
"os"
)
// FileDatabaseNotAvailableHTML is "/database_not_available.html"
var FileDatabaseNotAvailableHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x6f\x74\x69\x66\x69\x63\x61\x74\x69\x6f\x6e\x20\x69\x73\x2d\x64\x61\x6e\x67\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x68\x33\x3e\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x44\x61\x74\x61\x62\x61\x73\x65\x20\x6e\x6f\x74\x20\x61\x76\x61\x69\x6c\x61\x62\x6c\x65\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x3c\x2f\x68\x33\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x53\x6f\x6d\x65\x74\x68\x69\x6e\x67\x20\x77\x65\x6e\x74\x20\x77\x72\x6f\x6e\x67\x20\x77\x68\x69\x6c\x65\x20\x74\x72\x79\x69\x6e\x67\x20\x74\x6f\x20\x63\x6f\x6e\x6e\x65\x63\x74\x20\x74\x6f\x20\x64\x61\x74\x61\x62\x61\x73\x65\x2e\x20\x43\x68\x65\x63\x6b\x20\x6c\x6f\x67\x73\x20\x66\x6f\x72\x20\x64\x65\x74\x61\x69\x6c\x73\x2e\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/database_not_available.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileDatabaseNotAvailableHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.366001707 +0500 +05 m=+0.070630336" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
// original path: assets/error.html
package static
import (
"os"
)
// FileErrorHTML is "/error.html"
var FileErrorHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x62\x6f\x78\x20\x68\x61\x73\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x77\x61\x72\x6e\x69\x6e\x67\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x7b\x65\x72\x72\x6f\x72\x7d\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/error.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileErrorHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.379885693 +0500 +05 m=+0.084514363" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-09 06:13:46.102341215 +0500 +05)
// original path: assets/footer.html
package static
import (
"os"
)
// FileFooterHTML is "/footer.html"
var FileFooterHTML = []byte("\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x61\x69\x6e\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x20\x68\x61\x73\x2d\x74\x65\x78\x74\x2d\x63\x65\x6e\x74\x65\x72\x65\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x46\x61\x73\x74\x20\x70\x61\x73\x74\x65\x20\x62\x69\x6e\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x20\x76\x65\x72\x73\x69\x6f\x6e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x73\x74\x72\x6f\x6e\x67\x3e\x7b\x76\x65\x72\x73\x69\x6f\x6e\x7d\x3c\x2f\x73\x74\x72\x6f\x6e\x67\x3e\x20\x62\x79\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x70\x7a\x74\x72\x6e\x2e\x6e\x61\x6d\x65\x22\x3e\x53\x74\x61\x6e\x69\x73\x6c\x61\x76\x20\x4e\x2e\x20\x61\x6b\x61\x20\x70\x7a\x74\x72\x6e\x3c\x2f\x61\x3e\x2e\x20\x54\x68\x65\x20\x73\x6f\x75\x72\x63\x65\x20\x63\x6f\x64\x65\x20\x69\x73\x20\x6c\x69\x63\x65\x6e\x73\x65\x64\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x6f\x70\x65\x6e\x73\x6f\x75\x72\x63\x65\x2e\x6f\x72\x67\x2f\x6c\x69\x63\x65\x6e\x73\x65\x73\x2f\x6d\x69\x74\x2d\x6c\x69\x63\x65\x6e\x73\x65\x2e\x70\x68\x70\x22\x3e\x4d\x49\x54\x3c\x2f\x61\x3e\x2e\x20\x47\x65\x74\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x69\x74\x6c\x61\x62\x2e\x70\x7a\x74\x72\x6e\x2e\x6e\x61\x6d\x65\x2f\x66\x61\x73\x74\x70\x61\x73\x74\x65\x62\x69\x6e\x2f\x66\x61\x73\x74\x70\x61\x73\x74\x65\x62\x69\x6e\x22\x3e\x73\x6f\x75\x72\x63\x65\x20\x6f\x72\x20\x62\x69\x6e\x61\x72\x79\x20\x72\x65\x6c\x65\x61\x73\x65\x73\x20\x68\x65\x72\x65\x3c\x2f\x61\x3e\x21\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x64\x69\x76\x3e\x0a")
func init() {
f, err := FS.OpenFile(CTX, "/footer.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileFooterHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.333846038 +0500 +05 m=+0.038474816" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/main.html
package static
import (
"os"
)
// FileMainHTML is "/main.html"
var FileMainHTML = []byte("\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x68\x74\x6d\x6c\x3e\x0a\x3c\x68\x74\x6d\x6c\x3e\x0a\x0a\x3c\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x3c\x6d\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\x3d\x22\x75\x74\x66\x2d\x38\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6d\x65\x74\x61\x20\x6e\x61\x6d\x65\x3d\x22\x76\x69\x65\x77\x70\x6f\x72\x74\x22\x20\x63\x6f\x6e\x74\x65\x6e\x74\x3d\x22\x77\x69\x64\x74\x68\x3d\x64\x65\x76\x69\x63\x65\x2d\x77\x69\x64\x74\x68\x2c\x20\x69\x6e\x69\x74\x69\x61\x6c\x2d\x73\x63\x61\x6c\x65\x3d\x31\x22\x3e\x0a\x20\x20\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x46\x61\x73\x74\x20\x50\x61\x73\x74\x65\x20\x42\x69\x6e\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x62\x75\x6c\x6d\x61\x2d\x30\x2e\x37\x2e\x35\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x62\x75\x6c\x6d\x61\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2d\x33\x2e\x30\x2e\x30\x2e\x6d\x69\x6e\x2e\x63\x73\x73\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6c\x69\x6e\x6b\x20\x72\x65\x6c\x3d\x22\x73\x74\x79\x6c\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x73\x74\x61\x74\x69\x63\x2f\x63\x73\x73\x2f\x73\x74\x79\x6c\x65\x2e\x63\x73\x73\x22\x3e\x0a\x3c\x2f\x68\x65\x61\x64\x3e\x0a\x0a\x3c\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x7b\x6e\x61\x76\x69\x67\x61\x74\x69\x6f\x6e\x7d\x20\x7b\x64\x6f\x63\x75\x6d\x65\x6e\x74\x42\x6f\x64\x79\x7d\x0a\x3c\x2f\x62\x6f\x64\x79\x3e\x0a\x3c\x66\x6f\x6f\x74\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x6f\x6f\x74\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x7b\x66\x6f\x6f\x74\x65\x72\x7d\x0a\x3c\x2f\x66\x6f\x6f\x74\x65\x72\x3e\x0a\x0a\x3c\x2f\x68\x74\x6d\x6c\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/main.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileMainHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.381029312 +0500 +05 m=+0.085658023" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/navigation.html
package static
import (
"os"
)
// FileNavigationHTML is "/navigation.html"
var FileNavigationHTML = []byte("\x3c\x6e\x61\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x20\x69\x73\x2d\x64\x61\x72\x6b\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x62\x72\x61\x6e\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x69\x74\x65\x6d\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x22\x3e\x46\x61\x73\x74\x20\x50\x61\x73\x74\x65\x20\x42\x69\x6e\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x69\x74\x65\x6d\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x73\x2f\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x50\x61\x73\x74\x65\x73\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x69\x64\x3d\x22\x6e\x61\x76\x62\x61\x72\x49\x74\x65\x6d\x73\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x6d\x65\x6e\x75\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x73\x74\x61\x72\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x6e\x61\x76\x62\x61\x72\x2d\x65\x6e\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x6e\x61\x76\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/navigation.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileNavigationHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.373359597 +0500 +05 m=+0.077988268" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pagination.html
package static
import (
"os"
)
// FilePaginationHTML is "/pagination.html"
var FilePaginationHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x6e\x61\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x20\x69\x73\x2d\x63\x65\x6e\x74\x65\x72\x65\x64\x22\x20\x72\x6f\x6c\x65\x3d\x22\x6e\x61\x76\x69\x67\x61\x74\x69\x6f\x6e\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x70\x72\x65\x76\x69\x6f\x75\x73\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x70\x72\x65\x76\x69\x6f\x75\x73\x50\x61\x67\x65\x4c\x69\x6e\x6b\x7d\x22\x3e\x50\x72\x65\x76\x69\x6f\x75\x73\x20\x70\x61\x67\x65\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x75\x6c\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x73\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x4c\x69\x6e\x6b\x73\x7d\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x75\x6c\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6e\x65\x78\x74\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x6e\x65\x78\x74\x50\x61\x67\x65\x4c\x69\x6e\x6b\x7d\x22\x3e\x4e\x65\x78\x74\x20\x70\x61\x67\x65\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x6e\x61\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pagination.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePaginationHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.372861545 +0500 +05 m=+0.077490249" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pagination_ellipsis.html
package static
import (
"os"
)
// FilePaginationEllipsisHTML is "/pagination_ellipsis.html"
var FilePaginationEllipsisHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x73\x70\x61\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x65\x6c\x6c\x69\x70\x73\x69\x73\x22\x3e\x26\x68\x65\x6c\x6c\x69\x70\x3b\x3c\x2f\x73\x70\x61\x6e\x3e\x0a\x3c\x2f\x6c\x69\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pagination_ellipsis.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePaginationEllipsisHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.334873281 +0500 +05 m=+0.039502074" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pagination_link.html
package static
import (
"os"
)
// FilePaginationLinkHTML is "/pagination_link.html"
var FilePaginationLinkHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x6e\x6b\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x47\x6f\x20\x74\x6f\x20\x70\x61\x67\x65\x20\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x22\x20\x68\x72\x65\x66\x3d\x22\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x4c\x69\x6e\x6b\x7d\x22\x3e\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x3c\x2f\x61\x3e\x0a\x3c\x2f\x6c\x69\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pagination_link.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePaginationLinkHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.366696762 +0500 +05 m=+0.071325415" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pagination_link_current.html
package static
import (
"os"
)
// FilePaginationLinkCurrentHTML is "/pagination_link_current.html"
var FilePaginationLinkCurrentHTML = []byte("\x3c\x6c\x69\x3e\x0a\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x2d\x6c\x69\x6e\x6b\x20\x69\x73\x2d\x63\x75\x72\x72\x65\x6e\x74\x22\x20\x61\x72\x69\x61\x2d\x6c\x61\x62\x65\x6c\x3d\x22\x50\x61\x67\x65\x20\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x22\x20\x61\x72\x69\x61\x2d\x63\x75\x72\x72\x65\x6e\x74\x3d\x22\x70\x61\x67\x65\x22\x3e\x7b\x70\x61\x67\x65\x4e\x75\x6d\x7d\x3c\x2f\x61\x3e\x0a\x3c\x2f\x6c\x69\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pagination_link_current.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePaginationLinkCurrentHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.36723926 +0500 +05 m=+0.071867934" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/paste.html
package static
import (
"os"
)
// FilePasteHTML is "/paste.html"
var FilePasteHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x61\x62\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x74\x61\x62\x6c\x65\x20\x69\x73\x2d\x62\x6f\x72\x64\x65\x72\x65\x64\x20\x69\x73\x2d\x73\x74\x72\x69\x70\x65\x64\x20\x69\x73\x2d\x6e\x61\x72\x72\x6f\x77\x20\x69\x73\x2d\x68\x6f\x76\x65\x72\x61\x62\x6c\x65\x20\x69\x73\x2d\x66\x75\x6c\x6c\x77\x69\x64\x74\x68\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x23\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x54\x69\x74\x6c\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x4c\x61\x6e\x67\x75\x61\x67\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x50\x61\x73\x74\x65\x64\x20\x6f\x6e\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x57\x69\x6c\x6c\x20\x65\x78\x70\x69\x72\x65\x20\x6f\x6e\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x68\x3e\x50\x61\x73\x74\x65\x20\x74\x79\x70\x65\x3c\x2f\x74\x68\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x54\x69\x74\x6c\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x4c\x61\x6e\x67\x75\x61\x67\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x45\x78\x70\x69\x72\x61\x74\x69\x6f\x6e\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x3e\x7b\x70\x61\x73\x74\x65\x54\x79\x70\x65\x7d\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x74\x64\x20\x63\x6f\x6c\x73\x70\x61\x6e\x3d\x22\x36\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x63\x6c\x61\x73\x73\x3d\x22\x62\x75\x74\x74\x6f\x6e\x22\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x2f\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x2f\x7b\x70\x61\x73\x74\x65\x54\x73\x7d\x72\x61\x77\x22\x3e\x56\x69\x65\x77\x20\x72\x61\x77\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x64\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x62\x6f\x64\x79\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x74\x61\x62\x6c\x65\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e\x0a\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x70\x61\x73\x74\x65\x2d\x64\x61\x74\x61\x22\x3e\x0a\x20\x20\x20\x20\x7b\x70\x61\x73\x74\x65\x64\x61\x74\x61\x7d\x0a\x3c\x2f\x64\x69\x76\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/paste.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePasteHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.335668233 +0500 +05 m=+0.040296968" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pastelist_list.html
package static
import (
"os"
)
// FilePastelistListHTML is "/pastelist_list.html"
var FilePastelistListHTML = []byte("\x3c\x73\x65\x63\x74\x69\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x73\x65\x63\x74\x69\x6f\x6e\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x73\x74\x65\x73\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x7b\x70\x61\x67\x69\x6e\x61\x74\x69\x6f\x6e\x7d\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x73\x65\x63\x74\x69\x6f\x6e\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pastelist_list.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePastelistListHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.336449591 +0500 +05 m=+0.041078378" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.815743367 +0500 +05)
// original path: assets/pastelist_paste.html
package static
import (
"os"
)
// FilePastelistPasteHTML is "/pastelist_paste.html"
var FilePastelistPasteHTML = []byte("\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x68\x65\x61\x64\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x68\x65\x61\x64\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x68\x65\x61\x64\x65\x72\x2d\x74\x69\x74\x6c\x65\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x50\x61\x73\x74\x65\x20\x23\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x2c\x20\x70\x6f\x73\x74\x65\x64\x20\x6f\x6e\x20\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x65\x7d\x20\x61\x6e\x64\x20\x74\x69\x74\x6c\x65\x64\x20\x61\x73\x20\x22\x7b\x70\x61\x73\x74\x65\x54\x69\x74\x6c\x65\x7d\x22\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x70\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x68\x65\x61\x64\x65\x72\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x69\x76\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x6f\x6e\x74\x65\x6e\x74\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x72\x65\x3e\x7b\x70\x61\x73\x74\x65\x44\x61\x74\x61\x7d\x3c\x2f\x70\x72\x65\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x66\x6f\x6f\x74\x65\x72\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x66\x6f\x6f\x74\x65\x72\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x61\x20\x68\x72\x65\x66\x3d\x22\x2f\x70\x61\x73\x74\x65\x2f\x7b\x70\x61\x73\x74\x65\x49\x44\x7d\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x63\x61\x72\x64\x2d\x66\x6f\x6f\x74\x65\x72\x2d\x69\x74\x65\x6d\x20\x62\x75\x74\x74\x6f\x6e\x20\x69\x73\x2d\x73\x75\x63\x63\x65\x73\x73\x20\x69\x73\x2d\x72\x61\x64\x69\x75\x73\x6c\x65\x73\x73\x22\x3e\x56\x69\x65\x77\x3c\x2f\x61\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x66\x6f\x6f\x74\x65\x72\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x64\x69\x76\x3e")
func init() {
f, err := FS.OpenFile(CTX, "/pastelist_paste.html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FilePastelistPasteHTML)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,35 +0,0 @@
// Code generaTed by fileb0x at "2021-01-09 06:15:34.37218288 +0500 +05 m=+0.076811562" from config file "fileb0x.yml" DO NOT EDIT.
// modified(2021-01-08 11:51:03.805743275 +0500 +05)
// original path: assets/css/style.css
package static
import (
"os"
)
// FileStaticCSSStyleCSS is "static/css/style.css"
var FileStaticCSSStyleCSS = []byte("\x23\x70\x61\x73\x74\x65\x2d\x63\x6f\x6e\x74\x65\x6e\x74\x73\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x66\x61\x6d\x69\x6c\x79\x3a\x20\x6d\x6f\x6e\x6f\x73\x70\x61\x63\x65\x3b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\x30\x2e\x39\x72\x65\x6d\x3b\x0a\x20\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3a\x20\x39\x30\x76\x68\x3b\x0a\x7d\x0a\x0a\x2e\x70\x61\x73\x74\x65\x2d\x64\x61\x74\x61\x20\x7b\x0a\x20\x20\x20\x20\x66\x6f\x6e\x74\x2d\x73\x69\x7a\x65\x3a\x20\x30\x2e\x39\x72\x65\x6d\x3b\x0a\x7d\x0a\x0a\x2f\x2a\x20\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x73\x70\x65\x63\x69\x61\x6c\x20\x63\x61\x73\x65\x20\x66\x6f\x72\x20\x6d\x75\x6c\x74\x69\x6c\x69\x6e\x65\x20\x74\x6f\x6f\x6c\x74\x69\x70\x73\x2e\x20\x53\x65\x65\x20\x68\x74\x74\x70\x73\x3a\x2f\x2f\x67\x69\x74\x68\x75\x62\x2e\x63\x6f\x6d\x2f\x57\x69\x6b\x69\x6b\x69\x2f\x62\x75\x6c\x6d\x61\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2f\x69\x73\x73\x75\x65\x73\x2f\x33\x34\x20\x2a\x2f\x0a\x2e\x74\x6f\x6f\x6c\x74\x69\x70\x2e\x69\x73\x2d\x74\x6f\x6f\x6c\x74\x69\x70\x2d\x6d\x75\x6c\x74\x69\x6c\x69\x6e\x65\x3a\x3a\x62\x65\x66\x6f\x72\x65\x20\x7b\x0a\x20\x20\x20\x20\x77\x68\x69\x74\x65\x2d\x73\x70\x61\x63\x65\x3a\x70\x72\x65\x2d\x6c\x69\x6e\x65\x0a\x7d")
func init() {
f, err := FS.OpenFile(CTX, "static/css/style.css", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
panic(err)
}
_, err = f.Write(FileStaticCSSStyleCSS)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -25,12 +25,10 @@
package main
import (
// stdlib
"os"
"os/signal"
"syscall"
// local
"go.dev.pztrn.name/fastpastebin/domains/dbnotavailable"
"go.dev.pztrn.name/fastpastebin/domains/indexpage"
"go.dev.pztrn.name/fastpastebin/domains/pastes"
@@ -41,29 +39,29 @@ import (
)
func main() {
c := context.New()
c.Initialize()
appCtx := context.New()
appCtx.Initialize()
c.Logger.Info().Msg("Starting Fast Pastebin...")
appCtx.Logger.Info().Msg("Starting Fast Pastebin...")
// Here goes initial initialization for packages that want CLI flags
// to be added.
// Parse flags.
c.Flagger.Parse()
appCtx.Flagger.Parse()
// Continue loading.
c.LoadConfiguration()
c.InitializePost()
database.New(c)
c.Database.Initialize()
templater.Initialize(c)
appCtx.LoadConfiguration()
appCtx.InitializePost()
database.New(appCtx)
appCtx.Database.Initialize()
templater.Initialize(appCtx)
captcha.New(c)
captcha.New(appCtx)
dbnotavailable.New(c)
indexpage.New(c)
pastes.New(c)
dbnotavailable.New(appCtx)
indexpage.New(appCtx)
pastes.New(appCtx)
// CTRL+C handler.
signalHandler := make(chan os.Signal, 1)
@@ -73,7 +71,7 @@ func main() {
go func() {
<-signalHandler
c.Shutdown()
appCtx.Shutdown()
shutdownDone <- true
}()

View File

@@ -5,7 +5,7 @@ volumes:
services:
database:
image: mariadb:10.3
image: mariadb:10.5
container_name: database
restart: always
volumes:

View File

@@ -25,23 +25,21 @@
package dbnotavailable
import (
// stdlib
"net/http"
// local
"go.dev.pztrn.name/fastpastebin/internal/templater"
// other
"github.com/labstack/echo"
"go.dev.pztrn.name/fastpastebin/internal/templater"
)
// Database not available error page
// Database not available error page.
func dbNotAvailableGet(ec echo.Context) error {
htmlData := templater.GetTemplate(ec, "database_not_available.html", nil)
// nolint:wrapcheck
return ec.HTML(http.StatusInternalServerError, htmlData)
}
func dbNotAvailableRawGet(ec echo.Context) error {
// nolint:wrapcheck
return ec.String(http.StatusInternalServerError, "Database not available\nSomething went wrong while trying to connect to database. Check logs for details.")
}

View File

@@ -25,19 +25,16 @@
package dbnotavailable
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
)
var ctx *context.Context
// New initializes pastes package and adds necessary HTTP and API
// endpoints.
func New(cc *context.Context) {
c = cc
ctx = cc
c.Echo.GET("/database_not_available", dbNotAvailableGet)
c.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
ctx.Echo.GET("/database_not_available", dbNotAvailableGet)
ctx.Echo.GET("/database_not_available/raw", dbNotAvailableRawGet)
}

View File

@@ -25,18 +25,15 @@
package indexpage
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
)
var ctx *context.Context
// New initializes pastes package and adds necessary HTTP and API
// endpoints.
func New(cc *context.Context) {
c = cc
ctx = cc
c.Echo.GET("/", indexGet)
ctx.Echo.GET("/", indexGet)
}

View File

@@ -25,30 +25,28 @@
package indexpage
import (
// stdlib
"net/http"
// local
"go.dev.pztrn.name/fastpastebin/internal/captcha"
"go.dev.pztrn.name/fastpastebin/internal/templater"
// other
"github.com/alecthomas/chroma/lexers"
"github.com/labstack/echo"
"go.dev.pztrn.name/fastpastebin/internal/captcha"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
"go.dev.pztrn.name/fastpastebin/internal/templater"
)
// Index of this site.
func indexGet(ec echo.Context) error {
func indexGet(ectx echo.Context) error {
// We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection()
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available")
dbConn := ctx.Database.GetDatabaseConnection()
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/database_not_available")
}
// Generate list of available languages to highlight.
availableLexers := lexers.Names(false)
var availableLexersSelectOpts = "<option value='text'>Text</option><option value='autodetect'>Auto detect</option><option disabled>-----</option>"
availableLexersSelectOpts := "<option value='text'>Text</option><option value='autodetect'>Auto detect</option><option disabled>-----</option>"
for i := range availableLexers {
availableLexersSelectOpts += "<option value='" + availableLexers[i] + "'>" + availableLexers[i] + "</option>"
}
@@ -56,7 +54,8 @@ func indexGet(ec echo.Context) error {
// Captcha.
captchaString := captcha.NewCaptcha()
htmlData := templater.GetTemplate(ec, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
htmlData := templater.GetTemplate(ectx, "index.html", map[string]string{"lexers": availableLexersSelectOpts, "captchaString": captchaString})
return ec.HTML(http.StatusOK, htmlData)
// nolint:wrapcheck
return ectx.HTML(http.StatusOK, htmlData)
}

View File

@@ -25,43 +25,37 @@
package pastes
import (
// stdlib
"regexp"
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
regexInts = regexp.MustCompile("[0-9]+")
)
var regexInts = regexp.MustCompile("[0-9]+")
var (
c *context.Context
)
var ctx *context.Context
// New initializes pastes package and adds necessary HTTP and API
// endpoints.
func New(cc *context.Context) {
c = cc
ctx = cc
////////////////////////////////////////////////////////////
// HTTP endpoints.
////////////////////////////////////////////////////////////
// New paste.
c.Echo.POST("/paste/", pastePOSTWebInterface)
ctx.Echo.POST("/paste/", pastePOSTWebInterface)
// Show public paste.
c.Echo.GET("/paste/:id", pasteGETWebInterface)
ctx.Echo.GET("/paste/:id", pasteGETWebInterface)
// Show RAW representation of public paste.
c.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
ctx.Echo.GET("/paste/:id/raw", pasteRawGETWebInterface)
// Show private paste.
c.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
ctx.Echo.GET("/paste/:id/:timestamp", pasteGETWebInterface)
// Show RAW representation of private paste.
c.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
ctx.Echo.GET("/paste/:id/:timestamp/raw", pasteRawGETWebInterface)
// Verify access to passworded paste.
c.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
c.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
ctx.Echo.GET("/paste/:id/:timestamp/verify", pastePasswordedVerifyGet)
ctx.Echo.POST("/paste/:id/:timestamp/verify", pastePasswordedVerifyPost)
// Pastes list.
c.Echo.GET("/pastes/", pastesGET)
c.Echo.GET("/pastes/:page", pastesGET)
ctx.Echo.GET("/pastes/", pastesGET)
ctx.Echo.GET("/pastes/:page", pastesGET)
}

View File

@@ -1,23 +1,19 @@
package pastes
import (
// stdlib
"bytes"
"net/http"
"strconv"
"time"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
"go.dev.pztrn.name/fastpastebin/internal/templater"
// other
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters"
htmlfmt "github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"github.com/labstack/echo"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
"go.dev.pztrn.name/fastpastebin/internal/structs"
"go.dev.pztrn.name/fastpastebin/internal/templater"
)
const (
@@ -35,23 +31,26 @@ const (
// value (they both will be ignored), but private will.
func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) {
// Get paste.
paste, err1 := c.Database.GetPaste(pasteID)
paste, err1 := ctx.Database.GetPaste(pasteID)
if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
return nil, pasteNotFound
}
// Check if paste is expired.
if paste.IsExpired() {
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
return nil, pasteExpired
}
// Check if we have a private paste and it's parameters are correct.
if paste.Private {
pasteTs := paste.CreatedAt.Unix()
if timestamp != pasteTs {
c.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTs).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
pasteTS := paste.CreatedAt.Unix()
if timestamp != pasteTS {
ctx.Logger.Error().Int("paste ID", pasteID).Int64("paste timestamp", pasteTS).Int64("provided timestamp", timestamp).Msg("Incorrect timestamp provided for private paste")
return nil, pasteTimestampInvalid
}
}
@@ -73,28 +72,29 @@ func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Pa
// GET for "/paste/PASTE_ID" and "/paste/PASTE_ID/TIMESTAMP" (private pastes).
// Web interface version.
func pasteGETWebInterface(ec echo.Context) error {
pasteIDRaw := ec.Param("id")
func pasteGETWebInterface(ectx echo.Context) error {
pasteIDRaw := ectx.Param("id")
// We already get numbers from string, so we will not check strconv.Atoi()
// error.
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
pasteIDStr := strconv.Itoa(pasteID)
c.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Trying to get paste data")
// Check if we have timestamp passed.
// If passed timestamp is invalid (isn't a real UNIX timestamp) we
// will show 404 Not Found error and spam about that in logs.
var timestamp int64
tsProvidedStr := ec.Param("timestamp")
tsProvidedStr := ectx.Param("timestamp")
if tsProvidedStr != "" {
tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64)
if err != nil {
c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
ctx.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found")
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDStr+" not found")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
timestamp = tsProvided
@@ -104,24 +104,28 @@ func pasteGETWebInterface(ec echo.Context) error {
// for private pastes.
var cookieValue string
cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr)
cookie, err1 := ectx.Cookie("PASTE-" + pasteIDStr)
if err1 == nil {
cookieValue = cookie.Value
}
paste, error := pasteGetData(pasteID, timestamp, cookieValue)
paste, err := pasteGetData(pasteID, timestamp, cookieValue)
// For these cases we should return 404 Not Found page.
if error == pasteExpired || error == pasteNotFound || error == pasteTimestampInvalid {
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
return ec.HTML(http.StatusNotFound, errtpl)
if err == pasteExpired || err == pasteNotFound || err == pasteTimestampInvalid {
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
// nolint:wrapcheck
return ectx.HTML(http.StatusNotFound, errtpl)
}
// If passed cookie value was invalid - go to paste authorization
// page.
if error == pasteCookieInvalid {
c.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page")
return ec.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ec.Param("timestamp")+"/verify")
if err == pasteCookieInvalid {
ctx.Logger.Info().Int("paste ID", pasteID).Msg("Invalid cookie, redirecting to auth page")
// nolint:wrapcheck
return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDStr+"/"+ectx.Param("timestamp")+"/verify")
}
// Format paste data map.
@@ -155,7 +159,7 @@ func pasteGETWebInterface(ec echo.Context) error {
// Tokenize paste data.
lexered, err3 := lexer.Tokenise(nil, paste.Data)
if err3 != nil {
c.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
ctx.Logger.Error().Err(err3).Msg("Failed to tokenize paste data")
}
// Get style for HTML output.
style := styles.Get("monokai")
@@ -164,58 +168,60 @@ func pasteGETWebInterface(ec echo.Context) error {
}
// Get HTML formatter.
formatter := chroma.Formatter(htmlfmt.New(htmlfmt.WithLineNumbers(true), htmlfmt.LineNumbersInTable(true), htmlfmt.LinkableLineNumbers(true, "L")))
if formatter == nil {
formatter = formatters.Fallback
}
// Create buffer and format into it.
buf := new(bytes.Buffer)
err4 := formatter.Format(buf, style, lexered)
if err4 != nil {
c.Logger.Error().Err(err4).Msg("Failed to format paste data")
ctx.Logger.Error().Err(err4).Msg("Failed to format paste data")
}
pasteData["pastedata"] = buf.String()
// Get template and format it.
pasteHTML := templater.GetTemplate(ec, "paste.html", pasteData)
pasteHTML := templater.GetTemplate(ectx, "paste.html", pasteData)
return ec.HTML(http.StatusOK, pasteHTML)
// nolint:wrapcheck
return ectx.HTML(http.StatusOK, pasteHTML)
}
// GET for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
func pastePasswordedVerifyGet(ec echo.Context) error {
pasteIDRaw := ec.Param("id")
timestampRaw := ec.Param("timestamp")
func pastePasswordedVerifyGet(ectx echo.Context) error {
pasteIDRaw := ectx.Param("id")
timestampRaw := ectx.Param("timestamp")
// We already get numbers from string, so we will not check strconv.Atoi()
// error.
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
// Get paste.
paste, err1 := c.Database.GetPaste(pasteID)
paste, err1 := ctx.Database.GetPaste(pasteID)
if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+pasteIDRaw+" not found")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
// Check for auth cookie. If present - redirect to paste.
cookie, err := ec.Cookie("PASTE-" + strconv.Itoa(pasteID))
cookie, err := ectx.Cookie("PASTE-" + strconv.Itoa(pasteID))
if err == nil {
// No cookie, redirect to auth page.
c.Logger.Debug().Msg("Paste cookie found, checking it...")
ctx.Logger.Debug().Msg("Paste cookie found, checking it...")
// Generate cookie value to check.
cookieValue := paste.GenerateCryptedCookieValue()
if cookieValue == cookie.Value {
c.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
return ec.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ec.Param("timestamp"))
ctx.Logger.Info().Msg("Valid cookie, redirecting to paste page...")
// nolint:wrapcheck
return ectx.Redirect(http.StatusMovedPermanently, "/paste/"+pasteIDRaw+"/"+ectx.Param("timestamp"))
}
c.Logger.Debug().Msg("Invalid cookie, showing auth page")
ctx.Logger.Debug().Msg("Invalid cookie, showing auth page")
}
// HTML data.
@@ -223,43 +229,47 @@ func pastePasswordedVerifyGet(ec echo.Context) error {
htmlData["pasteID"] = strconv.Itoa(pasteID)
htmlData["pasteTimestamp"] = timestampRaw
verifyHTML := templater.GetTemplate(ec, "passworded_paste_verify.html", htmlData)
verifyHTML := templater.GetTemplate(ectx, "passworded_paste_verify.html", htmlData)
return ec.HTML(http.StatusOK, verifyHTML)
// nolint:wrapcheck
return ectx.HTML(http.StatusOK, verifyHTML)
}
// POST for "/paste/PASTE_ID/TIMESTAMP/verify" - a password verify page.
func pastePasswordedVerifyPost(ec echo.Context) error {
func pastePasswordedVerifyPost(ectx echo.Context) error {
// We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection()
dbConn := ctx.Database.GetDatabaseConnection()
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available")
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/database_not_available")
}
pasteIDRaw := ec.Param("id")
timestampRaw := ec.Param("timestamp")
pasteIDRaw := ectx.Param("id")
timestampRaw := ectx.Param("timestamp")
// We already get numbers from string, so we will not check strconv.Atoi()
// error.
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste")
// Get paste.
paste, err1 := c.Database.GetPaste(pasteID)
paste, err1 := ctx.Database.GetPaste(pasteID)
if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
params, err2 := ec.FormParams()
params, err2 := ectx.FormParams()
if err2 != nil {
c.Logger.Debug().Msg("No form parameters passed")
ctx.Logger.Debug().Msg("No form parameters passed")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
errtpl := templater.GetErrorTemplate(ectx, "Paste #"+strconv.Itoa(pasteID)+" not found")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
if paste.VerifyPassword(params["paste-password"][0]) {
@@ -269,59 +279,68 @@ func pastePasswordedVerifyPost(ec echo.Context) error {
cookie.Name = "PASTE-" + strconv.Itoa(pasteID)
cookie.Value = paste.GenerateCryptedCookieValue()
cookie.Expires = time.Now().Add(24 * time.Hour)
ec.SetCookie(cookie)
ectx.SetCookie(cookie)
return ec.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/paste/"+strconv.Itoa(pasteID)+"/"+timestampRaw)
}
errtpl := templater.GetErrorTemplate(ec, "Invalid password. Please, try again.")
errtpl := templater.GetErrorTemplate(ectx, "Invalid password. Please, try again.")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
// GET for "/pastes/:id/raw", raw paste output.
// Web interface version.
func pasteRawGETWebInterface(ec echo.Context) error {
func pasteRawGETWebInterface(ectx echo.Context) error {
// We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection()
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available/raw")
dbConn := ctx.Database.GetDatabaseConnection()
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/database_not_available/raw")
}
pasteIDRaw := ec.Param("id")
pasteIDRaw := ectx.Param("id")
// We already get numbers from string, so we will not check strconv.Atoi()
// error.
pasteID, _ := strconv.Atoi(regexInts.FindAllString(pasteIDRaw, 1)[0])
c.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
ctx.Logger.Debug().Int("paste ID", pasteID).Msg("Requesting paste data")
// Get paste.
paste, err1 := c.Database.GetPaste(pasteID)
paste, err1 := ctx.Database.GetPaste(pasteID)
if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database")
return ec.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
ctx.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste from database")
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
}
if paste.IsExpired() {
c.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
return ec.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Paste is expired")
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, "Paste #"+pasteIDRaw+" does not exist.")
}
// Check if we have a private paste and it's parameters are correct.
if paste.Private {
tsProvidedStr := ec.Param("timestamp")
tsProvidedStr := ectx.Param("timestamp")
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
if err2 != nil {
c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
ctx.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste")
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
// nolint:wrapcheck
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
}
pasteTs := paste.CreatedAt.Unix()
if tsProvided != pasteTs {
c.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTs).Msg("Incorrect timestamp provided for private paste")
pasteTS := paste.CreatedAt.Unix()
if tsProvided != pasteTS {
ctx.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTS).Msg("Incorrect timestamp provided for private paste")
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
// nolint:wrapcheck
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
}
}
@@ -329,9 +348,10 @@ func pasteRawGETWebInterface(ec echo.Context) error {
// ToDo: figure out how to handle passworded pastes here.
// Return error for now.
if paste.Password != "" {
c.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
ctx.Logger.Error().Int("paste ID", pasteID).Msg("Cannot render paste as raw: passworded paste. Patches welcome!")
return ectx.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
}
return ec.String(http.StatusOK, paste.Data)
// nolint:wrapcheck
return ectx.String(http.StatusOK, paste.Data)
}

View File

@@ -1,70 +1,75 @@
package pastes
import (
// stdlib
"net/http"
"regexp"
"strconv"
"strings"
"time"
// local
"go.dev.pztrn.name/fastpastebin/internal/captcha"
"go.dev.pztrn.name/fastpastebin/internal/structs"
"go.dev.pztrn.name/fastpastebin/internal/templater"
// other
"github.com/alecthomas/chroma/lexers"
"github.com/labstack/echo"
"go.dev.pztrn.name/fastpastebin/internal/captcha"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
"go.dev.pztrn.name/fastpastebin/internal/structs"
"go.dev.pztrn.name/fastpastebin/internal/templater"
)
const KeepPastesForever = "forever"
// POST for "/paste/" which will create new paste and redirect to
// "/pastes/CREATED_PASTE_ID". This handler will do all the job for
// requests comes from browsers via web interface.
func pastePOSTWebInterface(ec echo.Context) error {
func pastePOSTWebInterface(ectx echo.Context) error {
// We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection()
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available")
dbConn := ctx.Database.GetDatabaseConnection()
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/database_not_available")
}
params, err := ec.FormParams()
params, err := ectx.FormParams()
if err != nil {
c.Logger.Error().Msg("Passed paste form is empty")
ctx.Logger.Error().Msg("Passed paste form is empty")
errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste")
errtpl := templater.GetErrorTemplate(ectx, "Cannot create empty paste")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
c.Logger.Debug().Msgf("Received parameters: %+v", params)
ctx.Logger.Debug().Msgf("Received parameters: %+v", params)
// Do nothing if paste contents is empty.
if len(params["paste-contents"][0]) == 0 {
c.Logger.Debug().Msg("Empty paste submitted, ignoring")
ctx.Logger.Debug().Msg("Empty paste submitted, ignoring")
errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.")
errtpl := templater.GetErrorTemplate(ectx, "Empty pastes aren't allowed.")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" {
c.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != KeepPastesForever {
ctx.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value")
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
// Verify captcha.
if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) {
c.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
ctx.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution")
errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.")
errtpl := templater.GetErrorTemplate(ectx, "Invalid captcha solution.")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
// nolint:exhaustruct
paste := &structs.Paste{
Title: params["paste-title"][0],
Data: params["paste-contents"][0],
@@ -80,7 +85,7 @@ func pastePOSTWebInterface(ec echo.Context) error {
keepFor := 0
keepForUnit := 0
if params["paste-keep-for"][0] != "forever" {
if params["paste-keep-for"][0] != KeepPastesForever {
keepForUnitRegex := regexp.MustCompile("[Mmhd]")
keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0]
@@ -89,16 +94,17 @@ func pastePOSTWebInterface(ec echo.Context) error {
keepFor, err = strconv.Atoi(keepForRaw)
if err != nil {
if params["paste-keep-for"][0] == "forever" {
c.Logger.Debug().Msg("Keeping paste forever!")
if params["paste-keep-for"][0] == KeepPastesForever {
ctx.Logger.Debug().Msg("Keeping paste forever!")
keepFor = 0
} else {
c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
ctx.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer")
errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
errtpl := templater.GetErrorTemplate(ectx, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
}
@@ -132,22 +138,25 @@ func pastePOSTWebInterface(ec echo.Context) error {
_ = paste.CreatePassword(pastePassword[0])
}
id, err2 := c.Database.SavePaste(paste)
pasteID, err2 := ctx.Database.SavePaste(paste)
if err2 != nil {
c.Logger.Error().Err(err2).Msg("Failed to save paste")
ctx.Logger.Error().Err(err2).Msg("Failed to save paste")
errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.")
errtpl := templater.GetErrorTemplate(ectx, "Failed to save paste. Please, try again later.")
return ec.HTML(http.StatusBadRequest, errtpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusBadRequest, errtpl)
}
newPasteIDAsString := strconv.FormatInt(id, 10)
c.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
newPasteIDAsString := strconv.FormatInt(pasteID, 10)
ctx.Logger.Debug().Msg("Paste saved, URL: /paste/" + newPasteIDAsString)
// Private pastes have it's timestamp in URL.
if paste.Private {
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString+"/"+strconv.FormatInt(paste.CreatedAt.Unix(), 10))
}
return ec.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/paste/"+newPasteIDAsString)
}

View File

@@ -25,87 +25,87 @@
package pastes
import (
// stdlib
"net/http"
"strconv"
"strings"
// local
"github.com/labstack/echo"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
"go.dev.pztrn.name/fastpastebin/internal/pagination"
"go.dev.pztrn.name/fastpastebin/internal/templater"
// other
"github.com/labstack/echo"
)
// GET for "/pastes/", a list of publicly available pastes.
// Web interface version.
func pastesGET(ec echo.Context) error {
func pastesGET(ectx echo.Context) error {
// We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection()
if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available")
dbConn := ctx.Database.GetDatabaseConnection()
if ctx.Config.Database.Type != flatfiles.FlatFileDialect && dbConn == nil {
// nolint:wrapcheck
return ectx.Redirect(http.StatusFound, "/database_not_available")
}
pageFromParamRaw := ec.Param("page")
pageFromParamRaw := ectx.Param("page")
var page = 1
page := 1
if pageFromParamRaw != "" {
pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0]
page, _ = strconv.Atoi(pageRaw)
}
c.Logger.Debug().Int("page", page).Msg("Requested page")
ctx.Logger.Debug().Int("page", page).Msg("Requested page")
// Get pastes IDs.
pastes, err3 := c.Database.GetPagedPastes(page)
c.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
pastes, err3 := ctx.Database.GetPagedPastes(page)
ctx.Logger.Debug().Int("count", len(pastes)).Msg("Got pastes")
var pastesString = "No pastes to show."
pastesString := "No pastes to show."
// Show "No pastes to show" on any error for now.
if err3 != nil {
c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
ctx.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.")
noPastesToShowTpl := templater.GetErrorTemplate(ectx, "No pastes to show.")
return ec.HTML(http.StatusOK, noPastesToShowTpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusOK, noPastesToShowTpl)
}
if len(pastes) > 0 {
pastesString = ""
for i := range pastes {
for _, paste := range pastes {
pasteDataMap := make(map[string]string)
pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID)
pasteDataMap["pasteTitle"] = pastes[i].Title
pasteDataMap["pasteDate"] = pastes[i].CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
pasteDataMap["pasteID"] = strconv.Itoa(paste.ID)
pasteDataMap["pasteTitle"] = paste.Title
pasteDataMap["pasteDate"] = paste.CreatedAt.Format("2006-01-02 @ 15:04:05") + " UTC"
// Get max 4 lines of each paste.
pasteDataSplitted := strings.Split(pastes[i].Data, "\n")
pasteDataSplitted := strings.Split(paste.Data, "\n")
var pasteData string
if len(pasteDataSplitted) < 4 {
pasteData = pastes[i].Data
pasteData = paste.Data
} else {
pasteData = strings.Join(pasteDataSplitted[0:4], "\n")
}
pasteDataMap["pasteData"] = pasteData
pasteTpl := templater.GetRawTemplate(ec, "pastelist_paste.html", pasteDataMap)
pasteTpl := templater.GetRawTemplate(ectx, "pastelist_paste.html", pasteDataMap)
pastesString += pasteTpl
}
}
// Pagination.
pages := c.Database.GetPastesPages()
c.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
pages := ctx.Database.GetPastesPages()
ctx.Logger.Debug().Int("total pages", pages).Int("current page", page).Msg("Paging data")
paginationHTML := pagination.CreateHTML(page, pages, "/pastes/")
pasteListTpl := templater.GetTemplate(ec, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
pasteListTpl := templater.GetTemplate(ectx, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML})
return ec.HTML(http.StatusOK, pasteListTpl)
// nolint:wrapcheck
return ectx.HTML(http.StatusOK, pasteListTpl)
}

26
go.mod
View File

@@ -1,21 +1,23 @@
module go.dev.pztrn.name/fastpastebin
go 1.13
go 1.16
require (
github.com/alecthomas/chroma v0.8.2
github.com/alecthomas/chroma v0.10.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/jmoiron/sqlx v1.2.0
github.com/go-sql-driver/mysql v1.6.0
github.com/jmoiron/sqlx v1.3.5
github.com/kr/pretty v0.3.0 // indirect
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.3.0 // indirect
github.com/lib/pq v1.9.0
github.com/pressly/goose v2.6.0+incompatible
github.com/rs/zerolog v1.20.0
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
github.com/labstack/gommon v0.3.1 // indirect
github.com/lib/pq v1.10.6
github.com/pressly/goose v2.7.0+incompatible
github.com/rs/zerolog v1.27.0
go.dev.pztrn.name/flagger v0.0.0-20211119225333-c010875aa337
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0
)

146
go.sum
View File

@@ -1,15 +1,7 @@
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@@ -17,91 +9,83 @@ github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pressly/goose v2.6.0+incompatible h1:3f8zIQ8rfgP9tyI0Hmcs2YNAqUCL1c+diLe3iU8Qd/k=
github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/pressly/goose v2.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ=
github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c h1:+GgFefaTLsYDS0lXc8LNzTo6tcsA9qO3EkTAKduPAsI=
go.dev.pztrn.name/flagger v0.0.0-20200617193309-89bc9818b76c/go.mod h1:ttPExQNCubgqqO5Y19LfIBKqmWtBocY7P9MXQEECuZo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
go.dev.pztrn.name/flagger v0.0.0-20211119225333-c010875aa337 h1:OerezdlV+T80z1tCzOQg6HSpjheU3dmxHbUbQ8NVSAs=
go.dev.pztrn.name/flagger v0.0.0-20211119225333-c010875aa337/go.mod h1:ttPExQNCubgqqO5Y19LfIBKqmWtBocY7P9MXQEECuZo=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -25,28 +25,25 @@
package captcha
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
// other
"github.com/dchest/captcha"
"github.com/labstack/echo"
"github.com/rs/zerolog"
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
ctx *context.Context
log zerolog.Logger
)
// New initializes captcha package and adds necessary HTTP and API
// endpoints.
func New(cc *context.Context) {
c = cc
log = c.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
ctx = cc
log = ctx.Logger.With().Str("type", "internal").Str("package", "captcha").Logger()
// New paste.
c.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
ctx.Echo.GET("/captcha/:id.png", echo.WrapHandler(captcha.Server(captcha.StdWidth, captcha.StdHeight)))
}
// NewCaptcha creates new captcha string.

View File

@@ -28,6 +28,6 @@ package config
type HTTP struct {
Address string `yaml:"address"`
Port string `yaml:"port"`
AllowInsecure bool `yaml:"allow_insecure"`
MaxBodySizeMegabytes string `yaml:"max_body_size_megabytes"`
AllowInsecure bool `yaml:"allow_insecure"`
}

View File

@@ -26,7 +26,7 @@ package config
// Logging describes logger configuration.
type Logging struct {
LogToFile bool `yaml:"log_to_file"`
FileName string `yaml:"filename"`
LogLevel string `yaml:"loglevel"`
LogToFile bool `yaml:"log_to_file"`
}

View File

@@ -25,18 +25,14 @@
package context
import (
// stdlib
"io/ioutil"
"os"
"path/filepath"
// local
"go.dev.pztrn.name/fastpastebin/internal/config"
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
// other
"github.com/labstack/echo"
"github.com/rs/zerolog"
"go.dev.pztrn.name/fastpastebin/internal/config"
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
"go.dev.pztrn.name/flagger"
"gopkg.in/yaml.v2"
)
@@ -104,6 +100,7 @@ func (c *Context) LoadConfiguration() {
c.Logger.Debug().Str("path", configPath).Msg("Configuration file path")
// nolint:exhaustruct
c.Config = &config.Struct{}
// Read configuration file.

View File

@@ -31,5 +31,6 @@ const (
// New creates new context.
func New() *Context {
// nolint:exhaustruct
return &Context{}
}

View File

@@ -1,12 +1,11 @@
package context
import (
// local
"go.dev.pztrn.name/fastpastebin/assets/static"
"net/http"
// other
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"go.dev.pztrn.name/fastpastebin/assets"
)
func (c *Context) initializeHTTPServer() {
@@ -19,7 +18,7 @@ func (c *Context) initializeHTTPServer() {
c.Echo.HidePort = true
// Static files.
c.Echo.GET("/static/*", echo.WrapHandler(static.Handler))
c.Echo.GET("/static/*", echo.WrapHandler(http.FileServer(http.FS(assets.Data))))
listenAddress := c.Config.HTTP.Address + ":" + c.Config.HTTP.Port
@@ -32,16 +31,16 @@ func (c *Context) initializeHTTPServer() {
// Wrapper around previous function.
func (c *Context) echoReqLogger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ec echo.Context) error {
return func(ectx echo.Context) error {
c.Logger.Info().
Str("IP", ec.RealIP()).
Str("Host", ec.Request().Host).
Str("Method", ec.Request().Method).
Str("Path", ec.Request().URL.Path).
Str("UA", ec.Request().UserAgent()).
Str("IP", ectx.RealIP()).
Str("Host", ectx.Request().Host).
Str("Method", ectx.Request().Method).
Str("Path", ectx.Request().URL.Path).
Str("UA", ectx.Request().UserAgent()).
Msg("HTTP request")
return next(ec)
return next(ectx)
}
}
}

View File

@@ -1,56 +1,55 @@
package context
import (
// stdlib
"fmt"
"os"
"runtime"
"strings"
"time"
// other
"github.com/rs/zerolog"
)
// Puts memory usage into log lines.
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
var m runtime.MemStats
func (c *Context) getMemoryUsage(event *zerolog.Event, level zerolog.Level, message string) {
var memstats runtime.MemStats
runtime.ReadMemStats(&m)
runtime.ReadMemStats(&memstats)
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024))
e.Str("memsys", fmt.Sprintf("%dMB", m.Sys/1024/1024))
e.Str("numgc", fmt.Sprintf("%d", m.NumGC))
event.Str("memalloc", fmt.Sprintf("%dMB", memstats.Alloc/1024/1024))
event.Str("memsys", fmt.Sprintf("%dMB", memstats.Sys/1024/1024))
event.Str("numgc", fmt.Sprintf("%d", memstats.NumGC))
}
// Initializes logger.
func (c *Context) initializeLogger() {
// Устанавливаем форматирование логгера.
// nolint:exhaustruct
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
output.FormatLevel = func(i interface{}) string {
var v string
output.FormatLevel = func(lvlRaw interface{}) string {
var lvl string
if ii, ok := i.(string); ok {
ii = strings.ToUpper(ii)
switch ii {
if lvlAsString, ok := lvlRaw.(string); ok {
lvlAsString = strings.ToUpper(lvlAsString)
switch lvlAsString {
case "DEBUG":
v = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[30m%-5s\x1b[0m", lvlAsString)
case "ERROR":
v = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[31m%-5s\x1b[0m", lvlAsString)
case "FATAL":
v = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[35m%-5s\x1b[0m", lvlAsString)
case "INFO":
v = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[32m%-5s\x1b[0m", lvlAsString)
case "PANIC":
v = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[36m%-5s\x1b[0m", lvlAsString)
case "WARN":
v = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", ii)
lvl = fmt.Sprintf("\x1b[33m%-5s\x1b[0m", lvlAsString)
default:
v = ii
lvl = lvlAsString
}
}
return fmt.Sprintf("| %s |", v)
return fmt.Sprintf("| %s |", lvl)
}
c.Logger = zerolog.New(output).With().Timestamp().Logger()

View File

@@ -25,19 +25,16 @@
package database
import (
// stdlib
"database/sql"
"time"
// local
// MySQL driver.
_ "github.com/go-sql-driver/mysql"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles"
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql"
"go.dev.pztrn.name/fastpastebin/internal/structs"
// other
_ "github.com/go-sql-driver/mysql"
)
// Database represents control structure for database connection.
@@ -49,7 +46,7 @@ type Database struct {
// a subject of change in future.
func (db *Database) cleanup() {
for {
c.Logger.Info().Msg("Starting pastes cleanup procedure...")
ctx.Logger.Info().Msg("Starting pastes cleanup procedure...")
pages := db.db.GetPastesPages()
@@ -58,7 +55,7 @@ func (db *Database) cleanup() {
for i := 0; i < pages; i++ {
pastes, err := db.db.GetPagedPastes(i)
if err != nil {
c.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
ctx.Logger.Error().Err(err).Int("page", i).Msg("Failed to perform database cleanup")
}
for _, paste := range pastes {
@@ -71,17 +68,18 @@ func (db *Database) cleanup() {
for _, pasteID := range pasteIDsToRemove {
err := db.DeletePaste(pasteID)
if err != nil {
c.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
ctx.Logger.Error().Err(err).Int("paste", pasteID).Msg("Failed to delete paste!")
}
}
c.Logger.Info().Msg("Pastes cleanup done.")
ctx.Logger.Info().Msg("Pastes cleanup done.")
time.Sleep(time.Hour)
}
}
func (db *Database) DeletePaste(pasteID int) error {
// nolint:wrapcheck
return db.db.DeletePaste(pasteID)
}
@@ -94,10 +92,12 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
}
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
// nolint:wrapcheck
return db.db.GetPaste(pasteID)
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
// nolint:wrapcheck
return db.db.GetPagedPastes(page)
}
@@ -107,16 +107,16 @@ func (db *Database) GetPastesPages() int {
// Initialize initializes connection to database.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
ctx.Logger.Info().Msg("Initializing database connection...")
if c.Config.Database.Type == "mysql" {
mysql.New(c)
} else if c.Config.Database.Type == "flatfiles" {
flatfiles.New(c)
} else if c.Config.Database.Type == "postgresql" {
postgresql.New(c)
if ctx.Config.Database.Type == "mysql" {
mysql.New(ctx)
} else if ctx.Config.Database.Type == flatfiles.FlatFileDialect {
flatfiles.New(ctx)
} else if ctx.Config.Database.Type == "postgresql" {
postgresql.New(ctx)
} else {
c.Logger.Fatal().Str("type", c.Config.Database.Type).Msg("Unknown database type")
ctx.Logger.Fatal().Str("type", ctx.Config.Database.Type).Msg("Unknown database type")
}
go db.cleanup()
@@ -128,6 +128,7 @@ func (db *Database) RegisterDialect(di dialectinterface.Interface) {
}
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
// nolint:wrapcheck
return db.db.SavePaste(p)
}

View File

@@ -25,19 +25,21 @@
package flatfiles
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
)
const FlatFileDialect = "flatfiles"
var (
c *context.Context
f *FlatFiles
ctx *context.Context
flf *FlatFiles
)
func New(cc *context.Context) {
c = cc
f = &FlatFiles{}
ctx = cc
// nolint:exhaustruct
flf = &FlatFiles{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -25,7 +25,6 @@
package flatfiles
import (
// stdlib
"database/sql"
"encoding/json"
"io/ioutil"
@@ -36,14 +35,13 @@ import (
"strings"
"sync"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
type FlatFiles struct {
writeMutex sync.Mutex
path string
pastesIndex []Index
writeMutex sync.Mutex
}
// DeletePaste deletes paste from disk and index.
@@ -51,8 +49,9 @@ func (ff *FlatFiles) DeletePaste(pasteID int) error {
// Delete from disk.
err := os.Remove(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"))
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to delete paste!")
ctx.Logger.Error().Err(err).Msg("Failed to delete paste!")
// nolint:wrapcheck
return err
}
@@ -84,22 +83,27 @@ func (ff *FlatFiles) GetDatabaseConnection() *sql.DB {
func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
ff.writeMutex.Lock()
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
c.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
ctx.Logger.Debug().Str("path", pastePath).Msg("Trying to load paste data")
pasteInBytes, err := ioutil.ReadFile(pastePath)
if err != nil {
c.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
ctx.Logger.Debug().Err(err).Msg("Failed to read paste from storage")
// nolint:wrapcheck
return nil, err
}
c.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
ctx.Logger.Debug().Int("paste bytes", len(pasteInBytes)).Msg("Loaded paste")
ff.writeMutex.Unlock()
// nolint:exhaustruct
paste := &structs.Paste{}
err1 := json.Unmarshal(pasteInBytes, paste)
if err1 != nil {
c.Logger.Error().Err(err1).Msgf("Failed to parse paste")
ctx.Logger.Error().Err(err1).Msgf("Failed to parse paste")
// nolint:wrapcheck
return nil, err1
}
@@ -110,7 +114,7 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
// Pagination.
startPagination := 0
if page > 1 {
startPagination = (page - 1) * c.Config.Pastes.Pagination
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
}
// Iteration one - get only public pastes.
@@ -126,34 +130,39 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
pastesData := make([]structs.Paste, 0)
for idx, paste := range publicPastes {
if len(pastesData) == c.Config.Pastes.Pagination {
if len(pastesData) == ctx.Config.Pastes.Pagination {
break
}
if idx < startPagination {
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too low index")
continue
}
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*c.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*c.Config.Pastes.Pagination)) {
c.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
if (idx-1 >= startPagination && page > 1 && idx > startPagination+((page-1)*ctx.Config.Pastes.Pagination)) || (idx-1 >= startPagination && page == 1 && idx > startPagination+(page*ctx.Config.Pastes.Pagination)) {
ctx.Logger.Debug().Int("paste index", idx).Msg("Paste isn't in pagination query: too high index")
break
}
c.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
ctx.Logger.Debug().Int("ID", paste.ID).Int("index", idx).Msg("Getting paste data")
// Get paste data.
// nolint:exhaustruct
pasteData := &structs.Paste{}
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to read paste data")
ctx.Logger.Error().Err(err).Msg("Failed to read paste data")
continue
}
err1 := json.Unmarshal(pasteRawData, pasteData)
if err1 != nil {
c.Logger.Error().Err(err1).Msg("Failed to parse paste data")
ctx.Logger.Error().Err(err1).Msg("Failed to parse paste data")
continue
}
@@ -176,9 +185,9 @@ func (ff *FlatFiles) GetPastesPages() int {
ff.writeMutex.Unlock()
// Calculate pages.
pages := len(publicPastes) / c.Config.Pastes.Pagination
pages := len(publicPastes) / ctx.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(publicPastes)%c.Config.Pastes.Pagination > 0 {
if len(publicPastes)%ctx.Config.Pastes.Pagination > 0 {
pages++
}
@@ -186,14 +195,14 @@ func (ff *FlatFiles) GetPastesPages() int {
}
func (ff *FlatFiles) Initialize() {
c.Logger.Info().Msg("Initializing flatfiles storage...")
ctx.Logger.Info().Msg("Initializing flatfiles storage...")
path := c.Config.Database.Path
path := ctx.Config.Database.Path
// Get proper paste file path.
if strings.Contains(c.Config.Database.Path, "~") {
if strings.Contains(ctx.Config.Database.Path, "~") {
curUser, err := user.Current()
if err != nil {
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
ctx.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
path = strings.Replace(path, "~", "/", -1)
}
@@ -204,68 +213,73 @@ func (ff *FlatFiles) Initialize() {
path, _ = filepath.Abs(path)
ff.path = path
c.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
ctx.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
// Create directory if necessary.
if _, err := os.Stat(ff.path); err != nil {
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
_ = os.MkdirAll(ff.path, os.ModePerm)
} else {
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
}
// Create directory for pastes.
if _, err := os.Stat(filepath.Join(ff.path, "pastes")); err != nil {
c.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory does not exist, creating...")
_ = os.MkdirAll(filepath.Join(ff.path, "pastes"), os.ModePerm)
} else {
c.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
ctx.Logger.Debug().Str("directory", ff.path).Msg("Directory already exists")
}
// Load pastes index.
ff.pastesIndex = []Index{}
if _, err := os.Stat(filepath.Join(ff.path, "pastes", "index.json")); err != nil {
c.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
ctx.Logger.Warn().Msg("Pastes index file does not exist, will create new one")
} else {
indexData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", "index.json"))
if err != nil {
c.Logger.Fatal().Msg("Failed to read contents of index file!")
ctx.Logger.Fatal().Msg("Failed to read contents of index file!")
}
err1 := json.Unmarshal(indexData, &ff.pastesIndex)
if err1 != nil {
c.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.")
ctx.Logger.Error().Err(err1).Msg("Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable.")
}
c.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
ctx.Logger.Debug().Int("pastes count", len(ff.pastesIndex)).Msg("Parsed pastes index")
}
}
func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
func (ff *FlatFiles) SavePaste(paste *structs.Paste) (int64, error) {
ff.writeMutex.Lock()
// Write paste data on disk.
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
pasteID := len(filesOnDisk) + 1
p.ID = pasteID
paste.ID = pasteID
c.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
ctx.Logger.Debug().Int("new paste ID", pasteID).Msg("Writing paste to disk")
data, err := json.Marshal(p)
data, err := json.Marshal(paste)
if err != nil {
ff.writeMutex.Unlock()
// nolint:wrapcheck
return 0, err
}
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0o600)
if err != nil {
ff.writeMutex.Unlock()
// nolint:wrapcheck
return 0, err
}
// Add it to cache.
// nolint:exhaustruct
indexData := Index{}
indexData.ID = pasteID
indexData.Private = p.Private
indexData.Private = paste.Private
ff.pastesIndex = append(ff.pastesIndex, indexData)
ff.writeMutex.Unlock()
@@ -273,17 +287,19 @@ func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
}
func (ff *FlatFiles) Shutdown() {
c.Logger.Info().Msg("Saving indexes...")
ctx.Logger.Info().Msg("Saving indexes...")
indexData, err := json.Marshal(ff.pastesIndex)
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
ctx.Logger.Error().Err(err).Msg("Failed to encode index data into JSON")
return
}
err1 := ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0644)
err1 := ioutil.WriteFile(filepath.Join(ff.path, "pastes", "index.json"), indexData, 0o600)
if err1 != nil {
c.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
ctx.Logger.Error().Err(err1).Msg("Failed to write index data to file. Pretty sure that you've lost your pastes.")
return
}
}

View File

@@ -25,43 +25,41 @@
package flatfiles
import (
// stdlib
"database/sql"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) DeletePaste(pasteID int) error {
return f.DeletePaste(pasteID)
return flf.DeletePaste(pasteID)
}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return f.GetDatabaseConnection()
return flf.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return f.GetPaste(pasteID)
return flf.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return f.GetPagedPastes(page)
return flf.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return f.GetPastesPages()
return flf.GetPastesPages()
}
func (dbh Handler) Initialize() {
f.Initialize()
flf.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return f.SavePaste(p)
return flf.SavePaste(p)
}
func (dbh Handler) Shutdown() {
f.Shutdown()
flf.Shutdown()
}

View File

@@ -25,10 +25,8 @@
package dialectinterface
import (
// stdlib
"database/sql"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
)

View File

@@ -25,19 +25,19 @@
package mysql
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
)
var (
c *context.Context
d *Database
ctx *context.Context
dbAdapter *Database
)
func New(cc *context.Context) {
c = cc
d = &Database{}
ctx = cc
// nolint:exhaustruct
dbAdapter = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -25,43 +25,41 @@
package mysql
import (
// stdlib
"database/sql"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) DeletePaste(pasteID int) error {
return d.DeletePaste(pasteID)
return dbAdapter.DeletePaste(pasteID)
}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
return dbAdapter.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
return dbAdapter.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
return dbAdapter.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
return dbAdapter.GetPastesPages()
}
func (dbh Handler) Initialize() {
d.Initialize()
dbAdapter.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
return dbAdapter.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
dbAdapter.Shutdown()
}

View File

@@ -25,7 +25,6 @@
package migrations
import (
// stdlib
"database/sql"
)
@@ -39,6 +38,7 @@ func InitialUp(tx *sql.Tx) error {
keep_for_unit_type int(1) NOT NULL DEFAULT 1 COMMENT 'Keep for unit type. 1 - minutes, 2 - hours, 3 - days, 4 - months.',
PRIMARY KEY (id), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Pastes';`)
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,13 +25,13 @@
package migrations
import (
// stdlib
"database/sql"
)
func PasteLangUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `language` VARCHAR(191) NOT NULL DEFAULT 'text' COMMENT 'Paste language'")
if err != nil {
// nolint:wrapcheck
return err
}
@@ -41,6 +41,7 @@ func PasteLangUp(tx *sql.Tx) error {
func PasteLangDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `language`")
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,13 +25,13 @@
package migrations
import (
// stdlib
"database/sql"
)
func PrivatePastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `private` BOOL NOT NULL DEFAULT false COMMENT 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.'")
if err != nil {
// nolint:wrapcheck
return err
}
@@ -41,6 +41,7 @@ func PrivatePastesUp(tx *sql.Tx) error {
func PrivatePastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `private`")
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,32 +25,35 @@
package migrations
import (
// stdlib
"database/sql"
)
func PasswordedPastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
func PasswordedPastesUp(txn *sql.Tx) error {
_, err := txn.Exec("ALTER TABLE `pastes` ADD `password` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password for paste (scrypted and sha256ed).'")
if err != nil {
// nolint:wrapcheck
return err
}
_, err1 := tx.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
_, err1 := txn.Exec("ALTER TABLE `pastes` ADD `password_salt` varchar(64) NOT NULL DEFAULT '' COMMENT 'Password salt (sha256ed).'")
if err1 != nil {
// nolint:wrapcheck
return err1
}
return nil
}
func PasswordedPastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
func PasswordedPastesDown(txn *sql.Tx) error {
_, err := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password`")
if err != nil {
// nolint:wrapcheck
return err
}
_, err1 := tx.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
_, err1 := txn.Exec("ALTER TABLE `pastes` DROP COLUMN `password_salt`")
if err1 != nil {
// nolint:wrapcheck
return err1
}

View File

@@ -25,26 +25,20 @@
package migrations
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
// other
//"gitlab.com/jmoiron/sqlx"
"github.com/pressly/goose"
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
)
var ctx *context.Context
// New initializes migrations.
func New(cc *context.Context) {
c = cc
ctx = cc
}
// Migrate launching migrations.
func Migrate() {
c.Logger.Info().Msg("Migrating database...")
ctx.Logger.Info().Msg("Migrating database...")
_ = goose.SetDialect("mysql")
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
@@ -53,13 +47,13 @@ func Migrate() {
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
// Add new migrations BEFORE this message.
dbConn := c.Database.GetDatabaseConnection()
dbConn := ctx.Database.GetDatabaseConnection()
if dbConn != nil {
err := goose.Up(dbConn, ".")
if err != nil {
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
}
} else {
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
}
}

View File

@@ -25,17 +25,14 @@
package mysql
import (
// stdlib
"database/sql"
"fmt"
// local
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
"go.dev.pztrn.name/fastpastebin/internal/structs"
// other
// MySQL driver.
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql/migrations"
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
// Database is a MySQL/MariaDB connection controlling structure.
@@ -62,6 +59,7 @@ func (db *Database) DeletePaste(pasteID int) error {
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
if err != nil {
// nolint:wrapcheck
return err
}
@@ -82,14 +80,16 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check()
p := &structs.Paste{}
// nolint:exhaustruct
paste := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
if err != nil {
// nolint:wrapcheck
return nil, err
}
return p, nil
return paste, nil
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
@@ -103,11 +103,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
// Pagination.
startPagination := 0
if page > 1 {
startPagination = (page - 1) * c.Config.Pastes.Pagination
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
}
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), c.Config.Pastes.Pagination, startPagination)
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM `pastes` WHERE private != true ORDER BY id DESC LIMIT ? OFFSET ?"), ctx.Config.Pastes.Pagination, startPagination)
if err != nil {
// nolint:wrapcheck
return nil, err
}
@@ -141,9 +142,9 @@ func (db *Database) GetPastesPages() int {
}
// Calculate pages.
pages := len(pastes) / c.Config.Pastes.Pagination
pages := len(pastes) / ctx.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(pastes)%c.Config.Pastes.Pagination > 0 {
if len(pastes)%ctx.Config.Pastes.Pagination > 0 {
pages++
}
@@ -152,35 +153,36 @@ func (db *Database) GetPastesPages() int {
// Initialize initializes MySQL/MariaDB connection.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
ctx.Logger.Info().Msg("Initializing database connection...")
// There might be only user, without password. MySQL/MariaDB driver
// in DSN wants "user" or "user:password", "user:" is invalid.
var userpass string
if c.Config.Database.Password == "" {
userpass = c.Config.Database.Username
if ctx.Config.Database.Password == "" {
userpass = ctx.Config.Database.Username
} else {
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
}
dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
c.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
dbConnString := fmt.Sprintf("%s@tcp(%s:%s)/%s?parseTime=true&collation=utf8mb4_unicode_ci&charset=utf8mb4", userpass, ctx.Config.Database.Address, ctx.Config.Database.Port, ctx.Config.Database.Database)
ctx.Logger.Debug().Str("DSN", dbConnString).Msgf("Database connection string composed")
dbConn, err := sqlx.Connect("mysql", dbConnString)
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to connect to database")
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
return
}
// Force UTC for current connection.
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
c.Logger.Info().Msg("Database connection established")
ctx.Logger.Info().Msg("Database connection established")
db.db = dbConn
// Perform migrations.
migrations.New(c)
migrations.New(ctx)
migrations.Migrate()
}
@@ -189,22 +191,24 @@ func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
result, err := db.db.NamedExec("INSERT INTO `pastes` (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt)", p)
if err != nil {
// nolint:wrapcheck
return 0, err
}
ID, err1 := result.LastInsertId()
lastInsertID, err1 := result.LastInsertId()
if err1 != nil {
// nolint:wrapcheck
return 0, err
}
return ID, nil
return lastInsertID, nil
}
func (db *Database) Shutdown() {
if db.db != nil {
err := db.db.Close()
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to close database connection")
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
}
}
}

View File

@@ -25,19 +25,19 @@
package postgresql
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
)
var (
c *context.Context
d *Database
ctx *context.Context
dbAdapter *Database
)
func New(cc *context.Context) {
c = cc
d = &Database{}
ctx = cc
// nolint:exhaustruct
dbAdapter = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
ctx.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
}

View File

@@ -25,43 +25,41 @@
package postgresql
import (
// stdlib
"database/sql"
// local
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
type Handler struct{}
func (dbh Handler) DeletePaste(pasteID int) error {
return d.DeletePaste(pasteID)
return dbAdapter.DeletePaste(pasteID)
}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
return dbAdapter.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
return dbAdapter.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
return dbAdapter.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
return dbAdapter.GetPastesPages()
}
func (dbh Handler) Initialize() {
d.Initialize()
dbAdapter.Initialize()
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
return dbAdapter.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
dbAdapter.Shutdown()
}

View File

@@ -25,7 +25,6 @@
package migrations
import (
// stdlib
"database/sql"
)
@@ -49,6 +48,7 @@ func InitialUp(tx *sql.Tx) error {
COMMENT ON COLUMN pastes.keep_for_unit_type IS 'Keep for unit type. 0 - forever, 1 - minutes, 2 - hours, 3 - days, 4 - months.';
`)
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,13 +25,13 @@
package migrations
import (
// stdlib
"database/sql"
)
func PasteLangUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN language VARCHAR(191) NOT NULL DEFAULT 'text'; COMMENT ON COLUMN pastes.language IS 'Paste language';")
if err != nil {
// nolint:wrapcheck
return err
}
@@ -41,6 +41,7 @@ func PasteLangUp(tx *sql.Tx) error {
func PasteLangDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN language")
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,13 +25,13 @@
package migrations
import (
// stdlib
"database/sql"
)
func PrivatePastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN private BOOLEAN NOT NULL DEFAULT false; COMMENT ON COLUMN pastes.private IS 'Private paste? If true - additional URL parameter (UNIX TIMESTAMP) of paste will be required to access.';")
if err != nil {
// nolint:wrapcheck
return err
}
@@ -41,6 +41,7 @@ func PrivatePastesUp(tx *sql.Tx) error {
func PrivatePastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN private")
if err != nil {
// nolint:wrapcheck
return err
}

View File

@@ -25,32 +25,35 @@
package migrations
import (
// stdlib
"database/sql"
)
func PasswordedPastesUp(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
func PasswordedPastesUp(txn *sql.Tx) error {
_, err := txn.Exec("ALTER TABLE pastes ADD COLUMN password VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password IS 'Password for paste (scrypted and sha256ed).';")
if err != nil {
// nolint:wrapcheck
return err
}
_, err1 := tx.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
_, err1 := txn.Exec("ALTER TABLE pastes ADD COLUMN password_salt VARCHAR(64) NOT NULL DEFAULT ''; COMMENT ON COLUMN pastes.password_salt IS 'Password salt (sha256ed).';")
if err1 != nil {
// nolint:wrapcheck
return err1
}
return nil
}
func PasswordedPastesDown(tx *sql.Tx) error {
_, err := tx.Exec("ALTER TABLE pastes DROP COLUMN password")
func PasswordedPastesDown(txn *sql.Tx) error {
_, err := txn.Exec("ALTER TABLE pastes DROP COLUMN password")
if err != nil {
// nolint:wrapcheck
return err
}
_, err1 := tx.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
_, err1 := txn.Exec("ALTER TABLE pastes DROP COLUMN password_salt")
if err1 != nil {
// nolint:wrapcheck
return err1
}

View File

@@ -25,26 +25,20 @@
package migrations
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
// other
//"gitlab.com/jmoiron/sqlx"
"github.com/pressly/goose"
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
)
var ctx *context.Context
// New initializes migrations.
func New(cc *context.Context) {
c = cc
ctx = cc
}
// Migrate launching migrations.
func Migrate() {
c.Logger.Info().Msg("Migrating database...")
ctx.Logger.Info().Msg("Migrating database...")
_ = goose.SetDialect("postgres")
goose.AddNamedMigration("1_initial.go", InitialUp, nil)
@@ -53,14 +47,14 @@ func Migrate() {
goose.AddNamedMigration("4_passworded_pastes.go", PasswordedPastesUp, PasswordedPastesDown)
// Add new migrations BEFORE this message.
dbConn := c.Database.GetDatabaseConnection()
dbConn := ctx.Database.GetDatabaseConnection()
if dbConn != nil {
err := goose.Up(dbConn, ".")
if err != nil {
c.Logger.Info().Msgf("%+v", err)
c.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
ctx.Logger.Info().Msgf("%+v", err)
ctx.Logger.Panic().Msgf("Failed to migrate database to latest version: %s", err.Error())
}
} else {
c.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
ctx.Logger.Warn().Msg("Current database dialect isn't supporting migrations, skipping")
}
}

View File

@@ -24,20 +24,19 @@
package postgresql
// nolint:gci
import (
// stdlib
"database/sql"
"fmt"
"net"
"time"
// local
// PostgreSQL driver.
_ "github.com/lib/pq"
"github.com/jmoiron/sqlx"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql/migrations"
"go.dev.pztrn.name/fastpastebin/internal/structs"
// other
"github.com/jmoiron/sqlx"
// postgresql adapter
_ "github.com/lib/pq"
)
// Database is a PostgreSQL connection controlling structure.
@@ -64,6 +63,7 @@ func (db *Database) DeletePaste(pasteID int) error {
_, err := db.db.Exec(db.db.Rebind("DELETE FROM pastes WHERE id=?"), pasteID)
if err != nil {
// nolint:wrapcheck
return err
}
@@ -84,10 +84,12 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check()
p := &structs.Paste{}
// nolint:exhaustruct
paste := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
err := db.db.Get(paste, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
if err != nil {
// nolint:wrapcheck
return nil, err
}
@@ -95,10 +97,10 @@ func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
// timestamps in server's local timezone. We should convert them.
loc, _ := time.LoadLocation("UTC")
utcCreatedAt := p.CreatedAt.In(loc)
p.CreatedAt = &utcCreatedAt
utcCreatedAt := paste.CreatedAt.In(loc)
paste.CreatedAt = &utcCreatedAt
return p, nil
return paste, nil
}
func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
@@ -112,11 +114,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
// Pagination.
startPagination := 0
if page > 1 {
startPagination = (page - 1) * c.Config.Pastes.Pagination
startPagination = (page - 1) * ctx.Config.Pastes.Pagination
}
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), c.Config.Pastes.Pagination, startPagination)
err := db.db.Select(&pastesRaw, db.db.Rebind("SELECT * FROM pastes WHERE private != true ORDER BY id DESC LIMIT $1 OFFSET $2"), ctx.Config.Pastes.Pagination, startPagination)
if err != nil {
// nolint:wrapcheck
return nil, err
}
@@ -156,9 +159,9 @@ func (db *Database) GetPastesPages() int {
}
// Calculate pages.
pages := len(pastes) / c.Config.Pastes.Pagination
pages := len(pastes) / ctx.Config.Pastes.Pagination
// Check if we have any remainder. Add 1 to pages count if so.
if len(pastes)%c.Config.Pastes.Pagination > 0 {
if len(pastes)%ctx.Config.Pastes.Pagination > 0 {
pages++
}
@@ -167,56 +170,59 @@ func (db *Database) GetPastesPages() int {
// Initialize initializes MySQL/MariaDB connection.
func (db *Database) Initialize() {
c.Logger.Info().Msg("Initializing database connection...")
ctx.Logger.Info().Msg("Initializing database connection...")
var userpass string
if c.Config.Database.Password == "" {
userpass = c.Config.Database.Username
if ctx.Config.Database.Password == "" {
userpass = ctx.Config.Database.Username
} else {
userpass = c.Config.Database.Username + ":" + c.Config.Database.Password
userpass = ctx.Config.Database.Username + ":" + ctx.Config.Database.Password
}
dbConnString := fmt.Sprintf("postgres://%s@%s:%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, c.Config.Database.Address, c.Config.Database.Port, c.Config.Database.Database)
c.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed")
dbConnString := fmt.Sprintf("postgres://%s@%s/%s?connect_timeout=10&fallback_application_name=fastpastebin&sslmode=disable", userpass, net.JoinHostPort(ctx.Config.Database.Address, ctx.Config.Database.Port), ctx.Config.Database.Database)
ctx.Logger.Debug().Str("DSN", dbConnString).Msg("Database connection string composed")
dbConn, err := sqlx.Connect("postgres", dbConnString)
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to connect to database")
ctx.Logger.Error().Err(err).Msg("Failed to connect to database")
return
}
c.Logger.Info().Msg("Database connection established")
ctx.Logger.Info().Msg("Database connection established")
db.db = dbConn
// Perform migrations.
migrations.New(c)
migrations.New(ctx)
migrations.Migrate()
}
func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
func (db *Database) SavePaste(paste *structs.Paste) (int64, error) {
db.check()
stmt, err := db.db.PrepareNamed("INSERT INTO pastes (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt) RETURNING id")
if err != nil {
// nolint:wrapcheck
return 0, err
}
var id int64
var newPasteID int64
err = stmt.Get(&id, p)
err = stmt.Get(&newPasteID, paste)
if err != nil {
// nolint:wrapcheck
return 0, err
}
return id, nil
return newPasteID, nil
}
func (db *Database) Shutdown() {
if db.db != nil {
err := db.db.Close()
if err != nil {
c.Logger.Error().Err(err).Msg("Failed to close database connection")
ctx.Logger.Error().Err(err).Msg("Failed to close database connection")
}
}
}

View File

@@ -25,20 +25,20 @@
package database
import (
// local
"go.dev.pztrn.name/fastpastebin/internal/context"
databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface"
)
var (
c *context.Context
d *Database
ctx *context.Context
dbAdapter *Database
)
// New initializes database structure.
func New(cc *context.Context) {
c = cc
d = &Database{}
ctx = cc
// nolint:exhaustruct
dbAdapter = &Database{}
c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
ctx.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
}

View File

@@ -25,11 +25,8 @@
package database
import (
// stdlib
"database/sql"
// local
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
"go.dev.pztrn.name/fastpastebin/internal/structs"
)
@@ -39,38 +36,38 @@ import (
type Handler struct{}
func (dbh Handler) DeletePaste(pasteID int) error {
return d.DeletePaste(pasteID)
return dbAdapter.DeletePaste(pasteID)
}
func (dbh Handler) GetDatabaseConnection() *sql.DB {
return d.GetDatabaseConnection()
return dbAdapter.GetDatabaseConnection()
}
func (dbh Handler) GetPaste(pasteID int) (*structs.Paste, error) {
return d.GetPaste(pasteID)
return dbAdapter.GetPaste(pasteID)
}
func (dbh Handler) GetPagedPastes(page int) ([]structs.Paste, error) {
return d.GetPagedPastes(page)
return dbAdapter.GetPagedPastes(page)
}
func (dbh Handler) GetPastesPages() int {
return d.GetPastesPages()
return dbAdapter.GetPastesPages()
}
// Initialize initializes connection to database.
func (dbh Handler) Initialize() {
d.Initialize()
dbAdapter.Initialize()
}
func (dbh Handler) RegisterDialect(di dialectinterface.Interface) {
d.RegisterDialect(di)
dbAdapter.RegisterDialect(di)
}
func (dbh Handler) SavePaste(p *structs.Paste) (int64, error) {
return d.SavePaste(p)
return dbAdapter.SavePaste(p)
}
func (dbh Handler) Shutdown() {
d.Shutdown()
dbAdapter.Shutdown()
}

View File

@@ -25,10 +25,8 @@
package databaseinterface
import (
// stdlib
"database/sql"
// local
dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface"
"go.dev.pztrn.name/fastpastebin/internal/structs"
)

View File

@@ -1,33 +1,31 @@
package pagination
import (
// stdlib
"strconv"
"strings"
// local
"go.dev.pztrn.name/fastpastebin/assets/static"
"go.dev.pztrn.name/fastpastebin/assets"
)
// CreateHTML creates pagination HTML based on passed parameters.
func CreateHTML(currentPage int, pages int, linksBase string) string {
// Load templates.
paginationHTMLRaw, err := static.ReadFile("pagination.html")
paginationHTMLRaw, err := assets.Data.ReadFile("pagination.html")
if err != nil {
return "Missing pagination.html"
}
paginationLinkRaw, err1 := static.ReadFile("pagination_link.html")
paginationLinkRaw, err1 := assets.Data.ReadFile("pagination_link.html")
if err1 != nil {
return "Missing pagination_link.html"
}
paginationLinkCurrentRaw, err2 := static.ReadFile("pagination_link_current.html")
paginationLinkCurrentRaw, err2 := assets.Data.ReadFile("pagination_link_current.html")
if err2 != nil {
return "Missing pagination_link_current.html"
}
paginationEllipsisRaw, err3 := static.ReadFile("pagination_ellipsis.html")
paginationEllipsisRaw, err3 := assets.Data.ReadFile("pagination_ellipsis.html")
if err3 != nil {
return "Missing pagination_ellipsis.html"
}
@@ -44,13 +42,16 @@ func CreateHTML(currentPage int, pages int, linksBase string) string {
var (
ellipsisStartAdded = false
ellipsisEndAdded = false
// nolint:varnamelen
i = 2
)
for i <= pages {
// ToDo: fix it!
// nolint:nestif
if pages > 5 {
if currentPage-3 < i && currentPage+3 > i || i == pages {
var paginationItemRaw = string(paginationLinkRaw)
paginationItemRaw := string(paginationLinkRaw)
if i == currentPage {
paginationItemRaw = string(paginationLinkCurrentRaw)
}
@@ -68,7 +69,7 @@ func CreateHTML(currentPage int, pages int, linksBase string) string {
}
}
} else {
var paginationItemRaw = string(paginationLinkRaw)
paginationItemRaw := string(paginationLinkRaw)
if i == currentPage {
paginationItemRaw = string(paginationLinkCurrentRaw)
}

View File

@@ -25,26 +25,31 @@
package structs
import (
// stdlib
"crypto/sha256"
"fmt"
"math/rand"
"time"
// other
"golang.org/x/crypto/scrypt"
)
const (
// PasteKeepForever indicates that paste should be kept forever.
PasteKeepForever = 0
// PasteKeepForMinutes indicates that saved timeout is in minutes.
PasteKeepForMinutes = 1
// PasteKeepForHours indicates that saved timeout is in hours.
PasteKeepForHours = 2
// PasteKeepForDays indicates that saved timeout is in days.
PasteKeepForDays = 3
// PasteKeepForMonths indicates that saved timeout is in months.
PasteKeepForMonths = 4
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
// PasteKeepsCorrelation is a correlation map between database representation
// and passed data representation.
var PasteKeepsCorrelation = map[string]int{
"M": PasteKeepForMinutes,
"h": PasteKeepForHours,
@@ -70,6 +75,8 @@ type Paste struct {
// CreatePassword creates password for current paste.
func (p *Paste) CreatePassword(password string) error {
// Create salt - random string.
// Yes, it is insecure. Should be refactored!
// nolint:gosec
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
saltBytes := make([]byte, 64)
@@ -83,6 +90,7 @@ func (p *Paste) CreatePassword(password string) error {
// Create crypted password and hash it.
passwordCrypted, err := scrypt.Key([]byte(password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
if err != nil {
// nolint:wrapcheck
return err
}
@@ -95,6 +103,7 @@ func (p *Paste) CreatePassword(password string) error {
// GenerateCryptedCookieValue generates crypted cookie value for paste.
func (p *Paste) GenerateCryptedCookieValue() string {
cookieValueCrypted, _ := scrypt.Key([]byte(p.Password), []byte(p.PasswordSalt), 131072, 8, 1, 64)
return fmt.Sprintf("%x", sha256.Sum256(cookieValueCrypted))
}

View File

@@ -25,21 +25,17 @@
package templater
import (
// stdlib
"net/http"
"strings"
// local
"go.dev.pztrn.name/fastpastebin/assets/static"
"go.dev.pztrn.name/fastpastebin/internal/context"
// other
"github.com/labstack/echo"
"github.com/rs/zerolog"
"go.dev.pztrn.name/fastpastebin/assets"
"go.dev.pztrn.name/fastpastebin/internal/context"
)
var (
c *context.Context
ctx *context.Context
log zerolog.Logger
)
@@ -54,11 +50,12 @@ func GetErrorTemplate(ec echo.Context, errorText string) string {
}
// GetRawTemplate returns only raw template data.
func GetRawTemplate(ec echo.Context, templateName string, data map[string]string) string {
func GetRawTemplate(ectx echo.Context, templateName string, data map[string]string) string {
// Getting main template.
tplRaw, err := static.ReadFile(templateName)
tplRaw, err := assets.Data.ReadFile(templateName)
if err != nil {
_ = ec.String(http.StatusBadRequest, templateName+" not found.")
_ = ectx.String(http.StatusBadRequest, templateName+" not found.")
return ""
}
@@ -72,27 +69,30 @@ func GetRawTemplate(ec echo.Context, templateName string, data map[string]string
}
// GetTemplate returns formatted template that can be outputted to client.
func GetTemplate(ec echo.Context, name string, data map[string]string) string {
func GetTemplate(ectx echo.Context, name string, data map[string]string) string {
log.Debug().Str("name", name).Msg("Requested template")
// Getting main template.
mainhtml, err := static.ReadFile("main.html")
mainhtml, err := assets.Data.ReadFile("main.html")
if err != nil {
_ = ec.String(http.StatusBadRequest, "main.html not found.")
_ = ectx.String(http.StatusBadRequest, "main.html not found.")
return ""
}
// Getting navigation.
navhtml, err1 := static.ReadFile("navigation.html")
navhtml, err1 := assets.Data.ReadFile("navigation.html")
if err1 != nil {
_ = ec.String(http.StatusBadRequest, "navigation.html not found.")
_ = ectx.String(http.StatusBadRequest, "navigation.html not found.")
return ""
}
// Getting footer.
footerhtml, err2 := static.ReadFile("footer.html")
footerhtml, err2 := assets.Data.ReadFile("footer.html")
if err2 != nil {
_ = ec.String(http.StatusBadRequest, "footer.html not found.")
_ = ectx.String(http.StatusBadRequest, "footer.html not found.")
return ""
}
@@ -103,9 +103,10 @@ func GetTemplate(ec echo.Context, name string, data map[string]string) string {
tpl = strings.Replace(tpl, "{version}", context.Version, 1)
// Get requested template.
reqhtml, err3 := static.ReadFile(name)
reqhtml, err3 := assets.Data.ReadFile(name)
if err3 != nil {
_ = ec.String(http.StatusBadRequest, name+" not found.")
_ = ectx.String(http.StatusBadRequest, name+" not found.")
return ""
}
@@ -122,6 +123,6 @@ func GetTemplate(ec echo.Context, name string, data map[string]string) string {
// Initialize initializes package.
func Initialize(cc *context.Context) {
c = cc
log = c.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
ctx = cc
log = ctx.Logger.With().Str("type", "internal").Str("package", "templater").Logger()
}

View File

@@ -1,19 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
/cmd/chroma/chroma
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
_models/
_examples/

View File

@@ -1,55 +0,0 @@
run:
tests: true
skip-dirs:
- _examples
output:
print-issued-lines: false
linters:
enable-all: true
disable:
- maligned
- megacheck
- lll
- gocyclo
- dupl
- gochecknoglobals
- funlen
- godox
- wsl
- gomnd
- gocognit
linters-settings:
govet:
check-shadowing: true
gocyclo:
min-complexity: 10
dupl:
threshold: 100
goconst:
min-len: 8
min-occurrences: 3
issues:
max-per-linter: 0
max-same: 0
exclude-use-default: false
exclude:
# Captured by errcheck.
- '^(G104|G204):'
# Very commonly not checked.
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
- 'composite literal uses unkeyed fields'
- 'declaration of "err" shadows declaration'
- 'should not use dot imports'
- 'Potential file inclusion via variable'
- 'should have comment or be unexported'
- 'comment on exported var .* should be of the form'
- 'at least one file in a package should have a package comment'
- 'string literal contains the Unicode'
- 'methods on the same type should have the same receiver name'
- '_TokenType_name should be _TokenTypeName'
- '`_TokenType_map` should be `_TokenTypeMap`'

View File

@@ -1,33 +0,0 @@
project_name: chroma
release:
github:
owner: alecthomas
name: chroma
brews:
-
install: bin.install "chroma"
builds:
- goos:
- linux
- darwin
- windows
goarch:
- amd64
- "386"
goarm:
- "6"
main: ./cmd/chroma/main.go
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
binary: chroma
archives:
-
format: tar.gz
name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
files:
- COPYING
- README*
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
checksum:
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'

View File

@@ -1,12 +0,0 @@
sudo: false
language: go
go:
- "1.13.x"
script:
- go test -v ./...
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.22.2
- ./bin/golangci-lint run
- git clean -fdx .
after_success:
curl -sL https://git.io/goreleaser | bash && goreleaser

View File

@@ -1,19 +0,0 @@
Copyright (C) 2017 Alec Thomas
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.

View File

@@ -1,19 +0,0 @@
.PHONY: chromad upload all
all: README.md tokentype_string.go
README.md: lexers/*/*.go
./table.py
tokentype_string.go: types.go
go generate
chromad:
(cd ./cmd/chromad && go get github.com/GeertJohan/go.rice/rice@master && go install github.com/GeertJohan/go.rice/rice)
rm -f chromad
(export CGOENABLED=0 GOOS=linux ; cd ./cmd/chromad && go build -o ../../chromad .)
rice append -i ./cmd/chromad --exec=./chromad
upload: chromad
scp chromad root@swapoff.org: && \
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'

View File

@@ -1,267 +0,0 @@
# Chroma — A general purpose syntax highlighter in pure Go [![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.svg)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.svg)](https://gitter.im/alecthomas/Lobby)
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
Chroma takes source code and other structured text and converts it into syntax
highlighted HTML, ANSI-coloured text, etc.
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
translators for Pygments lexers and styles.
<a id="markdown-table-of-contents" name="table-of-contents"></a>
## Table of Contents
<!-- TOC -->
1. [Table of Contents](#table-of-contents)
2. [Supported languages](#supported-languages)
3. [Try it](#try-it)
4. [Using the library](#using-the-library)
1. [Quick start](#quick-start)
2. [Identifying the language](#identifying-the-language)
3. [Formatting the output](#formatting-the-output)
4. [The HTML formatter](#the-html-formatter)
5. [More detail](#more-detail)
1. [Lexers](#lexers)
2. [Formatters](#formatters)
3. [Styles](#styles)
6. [Command-line interface](#command-line-interface)
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
<!-- /TOC -->
<a id="markdown-supported-languages" name="supported-languages"></a>
## Supported languages
Prefix | Language
:----: | --------
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck
C | C, C#, C++, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
D | D, Dart, Diff, Django/Jinja, Docker, DTD
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
F | Factor, Fish, Forth, Fortran, FSharp
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HTML, HTTP, Hy
I | Idris, INI, Io
J | J, Java, JavaScript, JSON, Julia, Jungle
K | Kotlin
L | Lighttpd configuration file, LLVM, Lua
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode
P | PacmanConf, Perl, PHP, Pig, PkgConfig, PL/pgSQL, plaintext, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3
Q | QBasic
R | R, Racket, Ragel, react, reg, reStructuredText, Rexx, Ruby, Rust
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, SML, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, SYSTEMD, systemverilog
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
V | VB.net, verilog, VHDL, VimL, vue
W | WDTE
X | XML, Xorg
Y | YAML
_I will attempt to keep this section up to date, but an authoritative list can be
displayed with `chroma --list`._
<a id="markdown-try-it" name="try-it"></a>
## Try it
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
<a id="markdown-using-the-library" name="using-the-library"></a>
## Using the library
Chroma, like Pygments, has the concepts of
[lexers](https://github.com/alecthomas/chroma/tree/master/lexers),
[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and
[styles](https://github.com/alecthomas/chroma/tree/master/styles).
Lexers convert source text into a stream of tokens, styles specify how token
types are mapped to colours, and formatters convert tokens and styles into
formatted output.
A package exists for each of these, containing a global `Registry` variable
with all of the registered implementations. There are also helper functions
for using the registry in each package, such as looking up lexers by name or
matching filenames, etc.
In all cases, if a lexer, formatter or style can not be determined, `nil` will
be returned. In this situation you may want to default to the `Fallback`
value in each respective package, which provides sane defaults.
<a id="markdown-quick-start" name="quick-start"></a>
### Quick start
A convenience function exists that can be used to simply format some source
text, without any effort:
```go
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
```
<a id="markdown-identifying-the-language" name="identifying-the-language"></a>
### Identifying the language
To highlight code, you'll first have to identify what language the code is
written in. There are three primary ways to do that:
1. Detect the language from its filename.
```go
lexer := lexers.Match("foo.go")
```
3. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`).
```go
lexer := lexers.Get("go")
```
3. Detect the language from its content.
```go
lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")
```
In all cases, `nil` will be returned if the language can not be identified.
```go
if lexer == nil {
lexer = lexers.Fallback
}
```
At this point, it should be noted that some lexers can be extremely chatty. To
mitigate this, you can use the coalescing lexer to coalesce runs of identical
token types into a single token:
```go
lexer = chroma.Coalesce(lexer)
```
<a id="markdown-formatting-the-output" name="formatting-the-output"></a>
### Formatting the output
Once a language is identified you will need to pick a formatter and a style (theme).
```go
style := styles.Get("swapoff")
if style == nil {
style = styles.Fallback
}
formatter := formatters.Get("html")
if formatter == nil {
formatter = formatters.Fallback
}
```
Then obtain an iterator over the tokens:
```go
contents, err := ioutil.ReadAll(r)
iterator, err := lexer.Tokenise(nil, string(contents))
```
And finally, format the tokens from the iterator:
```go
err := formatter.Format(w, style, iterator)
```
<a id="markdown-the-html-formatter" name="the-html-formatter"></a>
### The HTML formatter
By default the `html` registered formatter generates standalone HTML with
embedded CSS. More flexibility is available through the `formatters/html` package.
Firstly, the output generated by the formatter can be customised with the
following constructor options:
- `Standalone()` - generate standalone HTML with embedded CSS.
- `WithClasses()` - use classes rather than inlined style attributes.
- `ClassPrefix(prefix)` - prefix each generated CSS class.
- `TabWidth(width)` - Set the rendered tab width, in characters.
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
- `LinkableLineNumbers()` - Make the line numbers linkable.
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with:
```go
formatter := html.New(html.WithClasses())
err := formatter.WriteCSS(w, style)
```
<a id="markdown-more-detail" name="more-detail"></a>
## More detail
<a id="markdown-lexers" name="lexers"></a>
### Lexers
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
for details on implementing lexers. Most concepts apply directly to Chroma,
but see existing lexer implementations for real examples.
In many cases lexers can be automatically converted directly from Pygments by
using the included Python 3 script `pygments2chroma.py`. I use something like
the following:
```sh
python3 ~/Projects/chroma/_tools/pygments2chroma.py \
pygments.lexers.jvm.KotlinLexer \
> ~/Projects/chroma/lexers/kotlin.go \
&& gofmt -s -w ~/Projects/chroma/lexers/*.go
```
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
for a list of lexers, and notes on some of the issues importing them.
<a id="markdown-formatters" name="formatters"></a>
### Formatters
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
A `noop` formatter is included that outputs the token text only, and a `tokens`
formatter outputs raw tokens. The latter is useful for debugging lexers.
<a id="markdown-styles" name="styles"></a>
### Styles
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
~~~go
chroma.Background: "#f8f8f2 bg:#000000",
~~~
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
<a id="markdown-command-line-interface" name="command-line-interface"></a>
## Command-line interface
A command-line interface to Chroma is included. It can be installed with:
```sh
go get -u github.com/alecthomas/chroma/cmd/chroma
```
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
## What's missing compared to Pygments?
- Quite a few lexers, for various reasons (pull-requests welcome):
- Pygments lexers for complex languages often include custom code to
handle certain aspects, such as Perl6's ability to nest code inside
regular expressions. These require time and effort to convert.
- I mostly only converted languages I had heard of, to reduce the porting cost.
- Some more esoteric features of Pygments are omitted for simplicity.
- Though the Chroma API supports content detection, very few languages support them.
I have plans to implement a statistical analyser at some point, but not enough time.

View File

@@ -1,35 +0,0 @@
package chroma
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} }
type coalescer struct{ Lexer }
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
var prev Token
it, err := d.Lexer.Tokenise(options, text)
if err != nil {
return nil, err
}
return func() Token {
for token := it(); token != (EOF); token = it() {
if len(token.Value) == 0 {
continue
}
if prev == EOF {
prev = token
} else {
if prev.Type == token.Type && len(prev.Value) < 8192 {
prev.Value += token.Value
} else {
out := prev
prev = token
return out
}
}
}
out := prev
prev = EOF
return out
}, nil
}

View File

@@ -1,164 +0,0 @@
package chroma
import (
"fmt"
"math"
"strconv"
"strings"
)
// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
var ANSI2RGB = map[string]string{
"#ansiblack": "000000",
"#ansidarkred": "7f0000",
"#ansidarkgreen": "007f00",
"#ansibrown": "7f7fe0",
"#ansidarkblue": "00007f",
"#ansipurple": "7f007f",
"#ansiteal": "007f7f",
"#ansilightgray": "e5e5e5",
// Normal
"#ansidarkgray": "555555",
"#ansired": "ff0000",
"#ansigreen": "00ff00",
"#ansiyellow": "ffff00",
"#ansiblue": "0000ff",
"#ansifuchsia": "ff00ff",
"#ansiturquoise": "00ffff",
"#ansiwhite": "ffffff",
// Aliases without the "ansi" prefix, because...why?
"#black": "000000",
"#darkred": "7f0000",
"#darkgreen": "007f00",
"#brown": "7f7fe0",
"#darkblue": "00007f",
"#purple": "7f007f",
"#teal": "007f7f",
"#lightgray": "e5e5e5",
// Normal
"#darkgray": "555555",
"#red": "ff0000",
"#green": "00ff00",
"#yellow": "ffff00",
"#blue": "0000ff",
"#fuchsia": "ff00ff",
"#turquoise": "00ffff",
"#white": "ffffff",
}
// Colour represents an RGB colour.
type Colour int32
// NewColour creates a Colour directly from RGB values.
func NewColour(r, g, b uint8) Colour {
return ParseColour(fmt.Sprintf("%02x%02x%02x", r, g, b))
}
// Distance between this colour and another.
//
// This uses the approach described here (https://www.compuphase.com/cmetric.htm).
// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs.
func (c Colour) Distance(e2 Colour) float64 {
ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue())
br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue())
rmean := (ar + br) / 2
r := ar - br
g := ag - bg
b := ab - bb
return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8)))
}
// Brighten returns a copy of this colour with its brightness adjusted.
//
// If factor is negative, the colour is darkened.
//
// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html).
func (c Colour) Brighten(factor float64) Colour {
r := float64(c.Red())
g := float64(c.Green())
b := float64(c.Blue())
if factor < 0 {
factor++
r *= factor
g *= factor
b *= factor
} else {
r = (255-r)*factor + r
g = (255-g)*factor + g
b = (255-b)*factor + b
}
return NewColour(uint8(r), uint8(g), uint8(b))
}
// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
func (c Colour) BrightenOrDarken(factor float64) Colour {
if c.Brightness() < 0.5 {
return c.Brighten(factor)
}
return c.Brighten(-factor)
}
// Brightness of the colour (roughly) in the range 0.0 to 1.0
func (c Colour) Brightness() float64 {
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
}
// ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
// Will return an "unset" colour if invalid.
func ParseColour(colour string) Colour {
colour = normaliseColour(colour)
n, err := strconv.ParseUint(colour, 16, 32)
if err != nil {
return 0
}
return Colour(n + 1)
}
// MustParseColour is like ParseColour except it panics if the colour is invalid.
//
// Will panic if colour is in an invalid format.
func MustParseColour(colour string) Colour {
parsed := ParseColour(colour)
if !parsed.IsSet() {
panic(fmt.Errorf("invalid colour %q", colour))
}
return parsed
}
// IsSet returns true if the colour is set.
func (c Colour) IsSet() bool { return c != 0 }
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
// Red component of colour.
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
// Green component of colour.
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
// Blue component of colour.
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
// Colours is an orderable set of colours.
type Colours []Colour
func (c Colours) Len() int { return len(c) }
func (c Colours) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
// Convert colours to #rrggbb.
func normaliseColour(colour string) string {
if ansi, ok := ANSI2RGB[colour]; ok {
return ansi
}
if strings.HasPrefix(colour, "#") {
colour = colour[1:]
if len(colour) == 3 {
return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
}
}
return colour
}

View File

@@ -1,137 +0,0 @@
package chroma
import (
"bytes"
)
type delegatingLexer struct {
root Lexer
language Lexer
}
// DelegatingLexer combines two lexers to handle the common case of a language embedded inside another, such as PHP
// inside HTML or PHP inside plain text.
//
// It takes two lexer as arguments: a root lexer and a language lexer. First everything is scanned using the language
// lexer, which must return "Other" for unrecognised tokens. Then all "Other" tokens are lexed using the root lexer.
// Finally, these two sets of tokens are merged.
//
// The lexers from the template lexer package use this base lexer.
func DelegatingLexer(root Lexer, language Lexer) Lexer {
return &delegatingLexer{
root: root,
language: language,
}
}
func (d *delegatingLexer) Config() *Config {
return d.language.Config()
}
// An insertion is the character range where language tokens should be inserted.
type insertion struct {
start, end int
tokens []Token
}
func (d *delegatingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit
tokens, err := Tokenise(Coalesce(d.language), options, text)
if err != nil {
return nil, err
}
// Compute insertions and gather "Other" tokens.
others := &bytes.Buffer{}
insertions := []*insertion{}
var insert *insertion
offset := 0
var last Token
for _, t := range tokens {
if t.Type == Other {
if last != EOF && insert != nil && last.Type != Other {
insert.end = offset
}
others.WriteString(t.Value)
} else {
if last == EOF || last.Type == Other {
insert = &insertion{start: offset}
insertions = append(insertions, insert)
}
insert.tokens = append(insert.tokens, t)
}
last = t
offset += len(t.Value)
}
if len(insertions) == 0 {
return d.root.Tokenise(options, text)
}
// Lex the other tokens.
rootTokens, err := Tokenise(Coalesce(d.root), options, others.String())
if err != nil {
return nil, err
}
// Interleave the two sets of tokens.
var out []Token
offset = 0 // Offset into text.
tokenIndex := 0
nextToken := func() Token {
if tokenIndex >= len(rootTokens) {
return EOF
}
t := rootTokens[tokenIndex]
tokenIndex++
return t
}
insertionIndex := 0
nextInsertion := func() *insertion {
if insertionIndex >= len(insertions) {
return nil
}
i := insertions[insertionIndex]
insertionIndex++
return i
}
t := nextToken()
i := nextInsertion()
for t != EOF || i != nil {
// fmt.Printf("%d->%d:%q %d->%d:%q\n", offset, offset+len(t.Value), t.Value, i.start, i.end, Stringify(i.tokens...))
if t == EOF || (i != nil && i.start < offset+len(t.Value)) {
var l Token
l, t = splitToken(t, i.start-offset)
if l != EOF {
out = append(out, l)
offset += len(l.Value)
}
out = append(out, i.tokens...)
offset += i.end - i.start
if t == EOF {
t = nextToken()
}
i = nextInsertion()
} else {
out = append(out, t)
offset += len(t.Value)
t = nextToken()
}
}
return Literator(out...), nil
}
func splitToken(t Token, offset int) (l Token, r Token) {
if t == EOF {
return EOF, EOF
}
if offset == 0 {
return EOF, t
}
if offset == len(t.Value) {
return t, EOF
}
l = t.Clone()
r = t.Clone()
l.Value = l.Value[:offset]
r.Value = r.Value[offset:]
return
}

View File

@@ -1,7 +0,0 @@
// Package chroma takes source code and other structured text and converts it into syntax highlighted HTML, ANSI-
// coloured text, etc.
//
// Chroma is based heavily on Pygments, and includes translators for Pygments lexers and styles.
//
// For more information, go here: https://github.com/alecthomas/chroma
package chroma

View File

@@ -1,43 +0,0 @@
package chroma
import (
"io"
)
// A Formatter for Chroma lexers.
type Formatter interface {
// Format returns a formatting function for tokens.
//
// If the iterator panics, the Formatter should recover.
Format(w io.Writer, style *Style, iterator Iterator) error
}
// A FormatterFunc is a Formatter implemented as a function.
//
// Guards against iterator panics.
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint
defer func() {
if perr := recover(); perr != nil {
err = perr.(error)
}
}()
return f(w, s, it)
}
type recoveringFormatter struct {
Formatter
}
func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) {
defer func() {
if perr := recover(); perr != nil {
err = perr.(error)
}
}()
return r.Formatter.Format(w, s, it)
}
// RecoveringFormatter wraps a formatter with panic recovery.
func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} }

View File

@@ -1,57 +0,0 @@
package formatters
import (
"io"
"sort"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/formatters/svg"
)
var (
// NoOp formatter.
NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, iterator chroma.Iterator) error {
for t := iterator(); t != chroma.EOF; t = iterator() {
if _, err := io.WriteString(w, t.Value); err != nil {
return err
}
}
return nil
}))
// Default HTML formatter outputs self-contained HTML.
htmlFull = Register("html", html.New(html.Standalone(true), html.WithClasses(true))) // nolint
SVG = Register("svg", svg.New(svg.EmbedFont("Liberation Mono", svg.FontLiberationMono, svg.WOFF)))
)
// Fallback formatter.
var Fallback = NoOp
// Registry of Formatters.
var Registry = map[string]chroma.Formatter{}
// Names of registered formatters.
func Names() []string {
out := []string{}
for name := range Registry {
out = append(out, name)
}
sort.Strings(out)
return out
}
// Get formatter by name.
//
// If the given formatter is not found, the Fallback formatter will be returned.
func Get(name string) chroma.Formatter {
if f, ok := Registry[name]; ok {
return f
}
return Fallback
}
// Register a named formatter.
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
Registry[name] = formatter
return formatter
}

View File

@@ -1,435 +0,0 @@
package html
import (
"fmt"
"html"
"io"
"sort"
"strings"
"github.com/alecthomas/chroma"
)
// Option sets an option of the HTML formatter.
type Option func(f *Formatter)
// Standalone configures the HTML formatter for generating a standalone HTML document.
func Standalone(b bool) Option { return func(f *Formatter) { f.standalone = b } }
// ClassPrefix sets the CSS class prefix.
func ClassPrefix(prefix string) Option { return func(f *Formatter) { f.prefix = prefix } }
// WithClasses emits HTML using CSS classes, rather than inline styles.
func WithClasses(b bool) Option { return func(f *Formatter) { f.Classes = b } }
// TabWidth sets the number of characters for a tab. Defaults to 8.
func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } }
// PreventSurroundingPre prevents the surrounding pre tags around the generated code.
func PreventSurroundingPre(b bool) Option {
return func(f *Formatter) {
if b {
f.preWrapper = nopPreWrapper
} else {
f.preWrapper = defaultPreWrapper
}
}
}
// WithPreWrapper allows control of the surrounding pre tags.
func WithPreWrapper(wrapper PreWrapper) Option {
return func(f *Formatter) {
f.preWrapper = wrapper
}
}
// WithLineNumbers formats output with line numbers.
func WithLineNumbers(b bool) Option {
return func(f *Formatter) {
f.lineNumbers = b
}
}
// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
// and code in table td's, which make them copy-and-paste friendly.
func LineNumbersInTable(b bool) Option {
return func(f *Formatter) {
f.lineNumbersInTable = b
}
}
// LinkableLineNumbers decorates the line numbers HTML elements with an "id"
// attribute so they can be linked.
func LinkableLineNumbers(b bool, prefix string) Option {
return func(f *Formatter) {
f.linkableLineNumbers = b
f.lineNumbersIDPrefix = prefix
}
}
// HighlightLines higlights the given line ranges with the Highlight style.
//
// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
func HighlightLines(ranges [][2]int) Option {
return func(f *Formatter) {
f.highlightRanges = ranges
sort.Sort(f.highlightRanges)
}
}
// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
func BaseLineNumber(n int) Option {
return func(f *Formatter) {
f.baseLineNumber = n
}
}
// New HTML formatter.
func New(options ...Option) *Formatter {
f := &Formatter{
baseLineNumber: 1,
preWrapper: defaultPreWrapper,
}
for _, option := range options {
option(f)
}
return f
}
// PreWrapper defines the operations supported in WithPreWrapper.
type PreWrapper interface {
// Start is called to write a start <pre> element.
// The code flag tells whether this block surrounds
// highlighted code. This will be false when surrounding
// line numbers.
Start(code bool, styleAttr string) string
// End is called to write the end </pre> element.
End(code bool) string
}
type preWrapper struct {
start func(code bool, styleAttr string) string
end func(code bool) string
}
func (p preWrapper) Start(code bool, styleAttr string) string {
return p.start(code, styleAttr)
}
func (p preWrapper) End(code bool) string {
return p.end(code)
}
var (
nopPreWrapper = preWrapper{
start: func(code bool, styleAttr string) string { return "" },
end: func(code bool) string { return "" },
}
defaultPreWrapper = preWrapper{
start: func(code bool, styleAttr string) string {
return fmt.Sprintf("<pre%s>", styleAttr)
},
end: func(code bool) string {
return "</pre>"
},
}
)
// Formatter that generates HTML.
type Formatter struct {
standalone bool
prefix string
Classes bool // Exported field to detect when classes are being used
preWrapper PreWrapper
tabWidth int
lineNumbers bool
lineNumbersInTable bool
linkableLineNumbers bool
lineNumbersIDPrefix string
highlightRanges highlightRanges
baseLineNumber int
}
type highlightRanges [][2]int
func (h highlightRanges) Len() int { return len(h) }
func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
return f.writeHTML(w, style, iterator.Tokens())
}
// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
//
// OTOH we need to be super careful about correct escaping...
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
css := f.styleToCSS(style)
if !f.Classes {
for t, style := range css {
css[t] = compressStyle(style)
}
}
if f.standalone {
fmt.Fprint(w, "<html>\n")
if f.Classes {
fmt.Fprint(w, "<style type=\"text/css\">\n")
err = f.WriteCSS(w, style)
if err != nil {
return err
}
fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background])
fmt.Fprint(w, "</style>")
}
fmt.Fprintf(w, "<body%s>\n", f.styleAttr(css, chroma.Background))
}
wrapInTable := f.lineNumbers && f.lineNumbersInTable
lines := chroma.SplitTokensIntoLines(tokens)
lineDigits := len(fmt.Sprintf("%d", f.baseLineNumber+len(lines)-1))
highlightIndex := 0
if wrapInTable {
// List line numbers in its own <td>
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.Background))
fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable))
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.Background)))
for index := range lines {
line := f.baseLineNumber + index
highlight, next := f.shouldHighlight(highlightIndex, line)
if next {
highlightIndex++
}
if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
}
fmt.Fprintf(w, "<span%s%s>%*d\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), lineDigits, line)
if highlight {
fmt.Fprintf(w, "</span>")
}
}
fmt.Fprint(w, f.preWrapper.End(false))
fmt.Fprint(w, "</td>\n")
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
}
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.Background)))
highlightIndex = 0
for index, tokens := range lines {
// 1-based line number.
line := f.baseLineNumber + index
highlight, next := f.shouldHighlight(highlightIndex, line)
if next {
highlightIndex++
}
if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
}
if f.lineNumbers && !wrapInTable {
fmt.Fprintf(w, "<span%s%s>%*d</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), lineDigits, line)
}
for _, token := range tokens {
html := html.EscapeString(token.String())
attr := f.styleAttr(css, token.Type)
if attr != "" {
html = fmt.Sprintf("<span%s>%s</span>", attr, html)
}
fmt.Fprint(w, html)
}
if highlight {
fmt.Fprintf(w, "</span>")
}
}
fmt.Fprintf(w, f.preWrapper.End(true))
if wrapInTable {
fmt.Fprint(w, "</td></tr></table>\n")
fmt.Fprint(w, "</div>\n")
}
if f.standalone {
fmt.Fprint(w, "\n</body>\n")
fmt.Fprint(w, "</html>\n")
}
return nil
}
func (f *Formatter) lineIDAttribute(line int) string {
if !f.linkableLineNumbers {
return ""
}
return fmt.Sprintf(" id=\"%s%d\"", f.lineNumbersIDPrefix, line)
}
func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) {
next := false
for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] {
highlightIndex++
next = true
}
if highlightIndex < len(f.highlightRanges) {
hrange := f.highlightRanges[highlightIndex]
if line >= hrange[0] && line <= hrange[1] {
return true, next
}
}
return false, next
}
func (f *Formatter) class(t chroma.TokenType) string {
for t != 0 {
if cls, ok := chroma.StandardTypes[t]; ok {
if cls != "" {
return f.prefix + cls
}
return ""
}
t = t.Parent()
}
if cls := chroma.StandardTypes[t]; cls != "" {
return f.prefix + cls
}
return ""
}
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType, extraCSS ...string) string {
if f.Classes {
cls := f.class(tt)
if cls == "" {
return ""
}
return fmt.Sprintf(` class="%s"`, cls)
}
if _, ok := styles[tt]; !ok {
tt = tt.SubCategory()
if _, ok := styles[tt]; !ok {
tt = tt.Category()
if _, ok := styles[tt]; !ok {
return ""
}
}
}
css := []string{styles[tt]}
css = append(css, extraCSS...)
return fmt.Sprintf(` style="%s"`, strings.Join(css, ";"))
}
func (f *Formatter) tabWidthStyle() string {
if f.tabWidth != 0 && f.tabWidth != 8 {
return fmt.Sprintf("; -moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d", f.tabWidth)
}
return ""
}
// WriteCSS writes CSS style definitions (without any surrounding HTML).
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
css := f.styleToCSS(style)
// Special-case background as it is mapped to the outer ".chroma" class.
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
return err
}
// Special-case code column of table to expand width.
if f.lineNumbers && f.lineNumbersInTable {
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s:last-child { width: 100%%; }",
chroma.LineTableTD, f.prefix, f.class(chroma.LineTableTD)); err != nil {
return err
}
}
// Special-case line number highlighting when targeted.
if f.lineNumbers || f.lineNumbersInTable {
targetedLineCSS := StyleEntryToCSS(style.Get(chroma.LineHighlight))
for _, tt := range []chroma.TokenType{chroma.LineNumbers, chroma.LineNumbersTable} {
fmt.Fprintf(w, "/* %s targeted by URL anchor */ .%schroma .%s:target { %s }\n", tt, f.prefix, f.class(tt), targetedLineCSS)
}
}
tts := []int{}
for tt := range css {
tts = append(tts, int(tt))
}
sort.Ints(tts)
for _, ti := range tts {
tt := chroma.TokenType(ti)
if tt == chroma.Background {
continue
}
styles := css[tt]
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s { %s }\n", tt, f.prefix, f.class(tt), styles); err != nil {
return err
}
}
return nil
}
func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string {
classes := map[chroma.TokenType]string{}
bg := style.Get(chroma.Background)
// Convert the style.
for t := range chroma.StandardTypes {
entry := style.Get(t)
if t != chroma.Background {
entry = entry.Sub(bg)
}
if entry.IsZero() {
continue
}
classes[t] = StyleEntryToCSS(entry)
}
classes[chroma.Background] += f.tabWidthStyle()
lineNumbersStyle := "margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
// All rules begin with default rules followed by user provided rules
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineHighlight] = "display: block; width: 100%;" + classes[chroma.LineHighlight]
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;" + classes[chroma.LineTable]
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
return classes
}
// StyleEntryToCSS converts a chroma.StyleEntry to CSS attributes.
func StyleEntryToCSS(e chroma.StyleEntry) string {
styles := []string{}
if e.Colour.IsSet() {
styles = append(styles, "color: "+e.Colour.String())
}
if e.Background.IsSet() {
styles = append(styles, "background-color: "+e.Background.String())
}
if e.Bold == chroma.Yes {
styles = append(styles, "font-weight: bold")
}
if e.Italic == chroma.Yes {
styles = append(styles, "font-style: italic")
}
if e.Underline == chroma.Yes {
styles = append(styles, "text-decoration: underline")
}
return strings.Join(styles, "; ")
}
// Compress CSS attributes - remove spaces, transform 6-digit colours to 3.
func compressStyle(s string) string {
parts := strings.Split(s, ";")
out := []string{}
for _, p := range parts {
p = strings.Join(strings.Fields(p), " ")
p = strings.Replace(p, ": ", ":", 1)
if strings.Contains(p, "#") {
c := p[len(p)-6:]
if c[0] == c[1] && c[2] == c[3] && c[4] == c[5] {
p = p[:len(p)-6] + c[0:1] + c[2:3] + c[4:5]
}
}
out = append(out, p)
}
return strings.Join(out, ";")
}

View File

@@ -1,31 +0,0 @@
package formatters
import (
"encoding/json"
"fmt"
"io"
"github.com/alecthomas/chroma"
)
// JSON formatter outputs the raw token structures as JSON.
var JSON = Register("json", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
fmt.Fprintln(w, "[")
i := 0
for t := it(); t != chroma.EOF; t = it() {
if i > 0 {
fmt.Fprintln(w, ",")
}
i++
bytes, err := json.Marshal(t)
if err != nil {
return err
}
if _, err := fmt.Fprint(w, " "+string(bytes)); err != nil {
return err
}
}
fmt.Fprintln(w)
fmt.Fprintln(w, "]")
return nil
}))

File diff suppressed because one or more lines are too long

View File

@@ -1,222 +0,0 @@
// Package svg contains an SVG formatter.
package svg
import (
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"path"
"strings"
"github.com/alecthomas/chroma"
)
// Option sets an option of the SVG formatter.
type Option func(f *Formatter)
// FontFamily sets the font-family.
func FontFamily(fontFamily string) Option { return func(f *Formatter) { f.fontFamily = fontFamily } }
// EmbedFontFile embeds given font file
func EmbedFontFile(fontFamily string, fileName string) (option Option, err error) {
var format FontFormat
switch path.Ext(fileName) {
case ".woff":
format = WOFF
case ".woff2":
format = WOFF2
case ".ttf":
format = TRUETYPE
default:
return nil, errors.New("unexpected font file suffix")
}
var content []byte
if content, err = ioutil.ReadFile(fileName); err == nil {
option = EmbedFont(fontFamily, base64.StdEncoding.EncodeToString(content), format)
}
return
}
// EmbedFont embeds given base64 encoded font
func EmbedFont(fontFamily string, font string, format FontFormat) Option {
return func(f *Formatter) { f.fontFamily = fontFamily; f.embeddedFont = font; f.fontFormat = format }
}
// New SVG formatter.
func New(options ...Option) *Formatter {
f := &Formatter{fontFamily: "Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace"}
for _, option := range options {
option(f)
}
return f
}
// Formatter that generates SVG.
type Formatter struct {
fontFamily string
embeddedFont string
fontFormat FontFormat
}
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
f.writeSVG(w, style, iterator.Tokens())
return err
}
var svgEscaper = strings.NewReplacer(
`&`, "&amp;",
`<`, "&lt;",
`>`, "&gt;",
`"`, "&quot;",
` `, "&#160;",
` `, "&#160;&#160;&#160;&#160;",
)
// EscapeString escapes special characters.
func escapeString(s string) string {
return svgEscaper.Replace(s)
}
func (f *Formatter) writeSVG(w io.Writer, style *chroma.Style, tokens []chroma.Token) { // nolint: gocyclo
svgStyles := f.styleToSVG(style)
lines := chroma.SplitTokensIntoLines(tokens)
fmt.Fprint(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
fmt.Fprint(w, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
fmt.Fprintf(w, "<svg width=\"%dpx\" height=\"%dpx\" xmlns=\"http://www.w3.org/2000/svg\">\n", 8*maxLineWidth(lines), 10+int(16.8*float64(len(lines)+1)))
if f.embeddedFont != "" {
f.writeFontStyle(w)
}
fmt.Fprintf(w, "<rect width=\"100%%\" height=\"100%%\" fill=\"%s\"/>\n", style.Get(chroma.Background).Background.String())
fmt.Fprintf(w, "<g font-family=\"%s\" font-size=\"14px\" fill=\"%s\">\n", f.fontFamily, style.Get(chroma.Text).Colour.String())
f.writeTokenBackgrounds(w, lines, style)
for index, tokens := range lines {
fmt.Fprintf(w, "<text x=\"0\" y=\"%fem\" xml:space=\"preserve\">", 1.2*float64(index+1))
for _, token := range tokens {
text := escapeString(token.String())
attr := f.styleAttr(svgStyles, token.Type)
if attr != "" {
text = fmt.Sprintf("<tspan %s>%s</tspan>", attr, text)
}
fmt.Fprint(w, text)
}
fmt.Fprint(w, "</text>")
}
fmt.Fprint(w, "\n</g>\n")
fmt.Fprint(w, "</svg>\n")
}
func maxLineWidth(lines [][]chroma.Token) int {
maxWidth := 0
for _, tokens := range lines {
length := 0
for _, token := range tokens {
length += len(strings.Replace(token.String(), ` `, " ", -1))
}
if length > maxWidth {
maxWidth = length
}
}
return maxWidth
}
// There is no background attribute for text in SVG so simply calculate the position and text
// of tokens with a background color that differs from the default and add a rectangle for each before
// adding the token.
func (f *Formatter) writeTokenBackgrounds(w io.Writer, lines [][]chroma.Token, style *chroma.Style) {
for index, tokens := range lines {
lineLength := 0
for _, token := range tokens {
length := len(strings.Replace(token.String(), ` `, " ", -1))
tokenBackground := style.Get(token.Type).Background
if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background {
fmt.Fprintf(w, "<rect id=\"%s\" x=\"%dch\" y=\"%fem\" width=\"%dch\" height=\"1.2em\" fill=\"%s\" />\n", escapeString(token.String()), lineLength, 1.2*float64(index)+0.25, length, style.Get(token.Type).Background.String())
}
lineLength += length
}
}
}
type FontFormat int
// https://transfonter.org/formats
const (
WOFF FontFormat = iota
WOFF2
TRUETYPE
)
var fontFormats = [...]string{
"woff",
"woff2",
"truetype",
}
func (f *Formatter) writeFontStyle(w io.Writer) {
fmt.Fprintf(w, `<style>
@font-face {
font-family: '%s';
src: url(data:application/x-font-%s;charset=utf-8;base64,%s) format('%s');'
font-weight: normal;
font-style: normal;
}
</style>`, f.fontFamily, fontFormats[f.fontFormat], f.embeddedFont, fontFormats[f.fontFormat])
}
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType) string {
if _, ok := styles[tt]; !ok {
tt = tt.SubCategory()
if _, ok := styles[tt]; !ok {
tt = tt.Category()
if _, ok := styles[tt]; !ok {
return ""
}
}
}
return styles[tt]
}
func (f *Formatter) styleToSVG(style *chroma.Style) map[chroma.TokenType]string {
converted := map[chroma.TokenType]string{}
bg := style.Get(chroma.Background)
// Convert the style.
for t := range chroma.StandardTypes {
entry := style.Get(t)
if t != chroma.Background {
entry = entry.Sub(bg)
}
if entry.IsZero() {
continue
}
converted[t] = StyleEntryToSVG(entry)
}
return converted
}
// StyleEntryToSVG converts a chroma.StyleEntry to SVG attributes.
func StyleEntryToSVG(e chroma.StyleEntry) string {
var styles []string
if e.Colour.IsSet() {
styles = append(styles, "fill=\""+e.Colour.String()+"\"")
}
if e.Bold == chroma.Yes {
styles = append(styles, "font-weight=\"bold\"")
}
if e.Italic == chroma.Yes {
styles = append(styles, "font-style=\"italic\"")
}
if e.Underline == chroma.Yes {
styles = append(styles, "text-decoration=\"underline\"")
}
return strings.Join(styles, " ")
}

View File

@@ -1,18 +0,0 @@
package formatters
import (
"fmt"
"io"
"github.com/alecthomas/chroma"
)
// Tokens formatter outputs the raw token structures.
var Tokens = Register("tokens", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
for t := it(); t != chroma.EOF; t = it() {
if _, err := fmt.Fprintln(w, t.GoString()); err != nil {
return err
}
}
return nil
}))

View File

@@ -1,260 +0,0 @@
package formatters
import (
"fmt"
"io"
"math"
"github.com/alecthomas/chroma"
)
type ttyTable struct {
foreground map[chroma.Colour]string
background map[chroma.Colour]string
}
var c = chroma.MustParseColour
var ttyTables = map[int]*ttyTable{
8: {
foreground: map[chroma.Colour]string{
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
c("#555555"): "\033[90m", c("#ff0000"): "\033[91m", c("#00ff00"): "\033[92m", c("#ffff00"): "\033[93m",
c("#0000ff"): "\033[94m", c("#ff00ff"): "\033[95m", c("#00ffff"): "\033[96m", c("#ffffff"): "\033[97m",
},
background: map[chroma.Colour]string{
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
c("#555555"): "\033[100m", c("#ff0000"): "\033[101m", c("#00ff00"): "\033[102m", c("#ffff00"): "\033[103m",
c("#0000ff"): "\033[104m", c("#ff00ff"): "\033[105m", c("#00ffff"): "\033[106m", c("#ffffff"): "\033[107m",
},
},
256: {
foreground: map[chroma.Colour]string{
c("#000000"): "\033[38;5;0m", c("#800000"): "\033[38;5;1m", c("#008000"): "\033[38;5;2m", c("#808000"): "\033[38;5;3m",
c("#000080"): "\033[38;5;4m", c("#800080"): "\033[38;5;5m", c("#008080"): "\033[38;5;6m", c("#c0c0c0"): "\033[38;5;7m",
c("#808080"): "\033[38;5;8m", c("#ff0000"): "\033[38;5;9m", c("#00ff00"): "\033[38;5;10m", c("#ffff00"): "\033[38;5;11m",
c("#0000ff"): "\033[38;5;12m", c("#ff00ff"): "\033[38;5;13m", c("#00ffff"): "\033[38;5;14m", c("#ffffff"): "\033[38;5;15m",
c("#000000"): "\033[38;5;16m", c("#00005f"): "\033[38;5;17m", c("#000087"): "\033[38;5;18m", c("#0000af"): "\033[38;5;19m",
c("#0000d7"): "\033[38;5;20m", c("#0000ff"): "\033[38;5;21m", c("#005f00"): "\033[38;5;22m", c("#005f5f"): "\033[38;5;23m",
c("#005f87"): "\033[38;5;24m", c("#005faf"): "\033[38;5;25m", c("#005fd7"): "\033[38;5;26m", c("#005fff"): "\033[38;5;27m",
c("#008700"): "\033[38;5;28m", c("#00875f"): "\033[38;5;29m", c("#008787"): "\033[38;5;30m", c("#0087af"): "\033[38;5;31m",
c("#0087d7"): "\033[38;5;32m", c("#0087ff"): "\033[38;5;33m", c("#00af00"): "\033[38;5;34m", c("#00af5f"): "\033[38;5;35m",
c("#00af87"): "\033[38;5;36m", c("#00afaf"): "\033[38;5;37m", c("#00afd7"): "\033[38;5;38m", c("#00afff"): "\033[38;5;39m",
c("#00d700"): "\033[38;5;40m", c("#00d75f"): "\033[38;5;41m", c("#00d787"): "\033[38;5;42m", c("#00d7af"): "\033[38;5;43m",
c("#00d7d7"): "\033[38;5;44m", c("#00d7ff"): "\033[38;5;45m", c("#00ff00"): "\033[38;5;46m", c("#00ff5f"): "\033[38;5;47m",
c("#00ff87"): "\033[38;5;48m", c("#00ffaf"): "\033[38;5;49m", c("#00ffd7"): "\033[38;5;50m", c("#00ffff"): "\033[38;5;51m",
c("#5f0000"): "\033[38;5;52m", c("#5f005f"): "\033[38;5;53m", c("#5f0087"): "\033[38;5;54m", c("#5f00af"): "\033[38;5;55m",
c("#5f00d7"): "\033[38;5;56m", c("#5f00ff"): "\033[38;5;57m", c("#5f5f00"): "\033[38;5;58m", c("#5f5f5f"): "\033[38;5;59m",
c("#5f5f87"): "\033[38;5;60m", c("#5f5faf"): "\033[38;5;61m", c("#5f5fd7"): "\033[38;5;62m", c("#5f5fff"): "\033[38;5;63m",
c("#5f8700"): "\033[38;5;64m", c("#5f875f"): "\033[38;5;65m", c("#5f8787"): "\033[38;5;66m", c("#5f87af"): "\033[38;5;67m",
c("#5f87d7"): "\033[38;5;68m", c("#5f87ff"): "\033[38;5;69m", c("#5faf00"): "\033[38;5;70m", c("#5faf5f"): "\033[38;5;71m",
c("#5faf87"): "\033[38;5;72m", c("#5fafaf"): "\033[38;5;73m", c("#5fafd7"): "\033[38;5;74m", c("#5fafff"): "\033[38;5;75m",
c("#5fd700"): "\033[38;5;76m", c("#5fd75f"): "\033[38;5;77m", c("#5fd787"): "\033[38;5;78m", c("#5fd7af"): "\033[38;5;79m",
c("#5fd7d7"): "\033[38;5;80m", c("#5fd7ff"): "\033[38;5;81m", c("#5fff00"): "\033[38;5;82m", c("#5fff5f"): "\033[38;5;83m",
c("#5fff87"): "\033[38;5;84m", c("#5fffaf"): "\033[38;5;85m", c("#5fffd7"): "\033[38;5;86m", c("#5fffff"): "\033[38;5;87m",
c("#870000"): "\033[38;5;88m", c("#87005f"): "\033[38;5;89m", c("#870087"): "\033[38;5;90m", c("#8700af"): "\033[38;5;91m",
c("#8700d7"): "\033[38;5;92m", c("#8700ff"): "\033[38;5;93m", c("#875f00"): "\033[38;5;94m", c("#875f5f"): "\033[38;5;95m",
c("#875f87"): "\033[38;5;96m", c("#875faf"): "\033[38;5;97m", c("#875fd7"): "\033[38;5;98m", c("#875fff"): "\033[38;5;99m",
c("#878700"): "\033[38;5;100m", c("#87875f"): "\033[38;5;101m", c("#878787"): "\033[38;5;102m", c("#8787af"): "\033[38;5;103m",
c("#8787d7"): "\033[38;5;104m", c("#8787ff"): "\033[38;5;105m", c("#87af00"): "\033[38;5;106m", c("#87af5f"): "\033[38;5;107m",
c("#87af87"): "\033[38;5;108m", c("#87afaf"): "\033[38;5;109m", c("#87afd7"): "\033[38;5;110m", c("#87afff"): "\033[38;5;111m",
c("#87d700"): "\033[38;5;112m", c("#87d75f"): "\033[38;5;113m", c("#87d787"): "\033[38;5;114m", c("#87d7af"): "\033[38;5;115m",
c("#87d7d7"): "\033[38;5;116m", c("#87d7ff"): "\033[38;5;117m", c("#87ff00"): "\033[38;5;118m", c("#87ff5f"): "\033[38;5;119m",
c("#87ff87"): "\033[38;5;120m", c("#87ffaf"): "\033[38;5;121m", c("#87ffd7"): "\033[38;5;122m", c("#87ffff"): "\033[38;5;123m",
c("#af0000"): "\033[38;5;124m", c("#af005f"): "\033[38;5;125m", c("#af0087"): "\033[38;5;126m", c("#af00af"): "\033[38;5;127m",
c("#af00d7"): "\033[38;5;128m", c("#af00ff"): "\033[38;5;129m", c("#af5f00"): "\033[38;5;130m", c("#af5f5f"): "\033[38;5;131m",
c("#af5f87"): "\033[38;5;132m", c("#af5faf"): "\033[38;5;133m", c("#af5fd7"): "\033[38;5;134m", c("#af5fff"): "\033[38;5;135m",
c("#af8700"): "\033[38;5;136m", c("#af875f"): "\033[38;5;137m", c("#af8787"): "\033[38;5;138m", c("#af87af"): "\033[38;5;139m",
c("#af87d7"): "\033[38;5;140m", c("#af87ff"): "\033[38;5;141m", c("#afaf00"): "\033[38;5;142m", c("#afaf5f"): "\033[38;5;143m",
c("#afaf87"): "\033[38;5;144m", c("#afafaf"): "\033[38;5;145m", c("#afafd7"): "\033[38;5;146m", c("#afafff"): "\033[38;5;147m",
c("#afd700"): "\033[38;5;148m", c("#afd75f"): "\033[38;5;149m", c("#afd787"): "\033[38;5;150m", c("#afd7af"): "\033[38;5;151m",
c("#afd7d7"): "\033[38;5;152m", c("#afd7ff"): "\033[38;5;153m", c("#afff00"): "\033[38;5;154m", c("#afff5f"): "\033[38;5;155m",
c("#afff87"): "\033[38;5;156m", c("#afffaf"): "\033[38;5;157m", c("#afffd7"): "\033[38;5;158m", c("#afffff"): "\033[38;5;159m",
c("#d70000"): "\033[38;5;160m", c("#d7005f"): "\033[38;5;161m", c("#d70087"): "\033[38;5;162m", c("#d700af"): "\033[38;5;163m",
c("#d700d7"): "\033[38;5;164m", c("#d700ff"): "\033[38;5;165m", c("#d75f00"): "\033[38;5;166m", c("#d75f5f"): "\033[38;5;167m",
c("#d75f87"): "\033[38;5;168m", c("#d75faf"): "\033[38;5;169m", c("#d75fd7"): "\033[38;5;170m", c("#d75fff"): "\033[38;5;171m",
c("#d78700"): "\033[38;5;172m", c("#d7875f"): "\033[38;5;173m", c("#d78787"): "\033[38;5;174m", c("#d787af"): "\033[38;5;175m",
c("#d787d7"): "\033[38;5;176m", c("#d787ff"): "\033[38;5;177m", c("#d7af00"): "\033[38;5;178m", c("#d7af5f"): "\033[38;5;179m",
c("#d7af87"): "\033[38;5;180m", c("#d7afaf"): "\033[38;5;181m", c("#d7afd7"): "\033[38;5;182m", c("#d7afff"): "\033[38;5;183m",
c("#d7d700"): "\033[38;5;184m", c("#d7d75f"): "\033[38;5;185m", c("#d7d787"): "\033[38;5;186m", c("#d7d7af"): "\033[38;5;187m",
c("#d7d7d7"): "\033[38;5;188m", c("#d7d7ff"): "\033[38;5;189m", c("#d7ff00"): "\033[38;5;190m", c("#d7ff5f"): "\033[38;5;191m",
c("#d7ff87"): "\033[38;5;192m", c("#d7ffaf"): "\033[38;5;193m", c("#d7ffd7"): "\033[38;5;194m", c("#d7ffff"): "\033[38;5;195m",
c("#ff0000"): "\033[38;5;196m", c("#ff005f"): "\033[38;5;197m", c("#ff0087"): "\033[38;5;198m", c("#ff00af"): "\033[38;5;199m",
c("#ff00d7"): "\033[38;5;200m", c("#ff00ff"): "\033[38;5;201m", c("#ff5f00"): "\033[38;5;202m", c("#ff5f5f"): "\033[38;5;203m",
c("#ff5f87"): "\033[38;5;204m", c("#ff5faf"): "\033[38;5;205m", c("#ff5fd7"): "\033[38;5;206m", c("#ff5fff"): "\033[38;5;207m",
c("#ff8700"): "\033[38;5;208m", c("#ff875f"): "\033[38;5;209m", c("#ff8787"): "\033[38;5;210m", c("#ff87af"): "\033[38;5;211m",
c("#ff87d7"): "\033[38;5;212m", c("#ff87ff"): "\033[38;5;213m", c("#ffaf00"): "\033[38;5;214m", c("#ffaf5f"): "\033[38;5;215m",
c("#ffaf87"): "\033[38;5;216m", c("#ffafaf"): "\033[38;5;217m", c("#ffafd7"): "\033[38;5;218m", c("#ffafff"): "\033[38;5;219m",
c("#ffd700"): "\033[38;5;220m", c("#ffd75f"): "\033[38;5;221m", c("#ffd787"): "\033[38;5;222m", c("#ffd7af"): "\033[38;5;223m",
c("#ffd7d7"): "\033[38;5;224m", c("#ffd7ff"): "\033[38;5;225m", c("#ffff00"): "\033[38;5;226m", c("#ffff5f"): "\033[38;5;227m",
c("#ffff87"): "\033[38;5;228m", c("#ffffaf"): "\033[38;5;229m", c("#ffffd7"): "\033[38;5;230m", c("#ffffff"): "\033[38;5;231m",
c("#080808"): "\033[38;5;232m", c("#121212"): "\033[38;5;233m", c("#1c1c1c"): "\033[38;5;234m", c("#262626"): "\033[38;5;235m",
c("#303030"): "\033[38;5;236m", c("#3a3a3a"): "\033[38;5;237m", c("#444444"): "\033[38;5;238m", c("#4e4e4e"): "\033[38;5;239m",
c("#585858"): "\033[38;5;240m", c("#626262"): "\033[38;5;241m", c("#6c6c6c"): "\033[38;5;242m", c("#767676"): "\033[38;5;243m",
c("#808080"): "\033[38;5;244m", c("#8a8a8a"): "\033[38;5;245m", c("#949494"): "\033[38;5;246m", c("#9e9e9e"): "\033[38;5;247m",
c("#a8a8a8"): "\033[38;5;248m", c("#b2b2b2"): "\033[38;5;249m", c("#bcbcbc"): "\033[38;5;250m", c("#c6c6c6"): "\033[38;5;251m",
c("#d0d0d0"): "\033[38;5;252m", c("#dadada"): "\033[38;5;253m", c("#e4e4e4"): "\033[38;5;254m", c("#eeeeee"): "\033[38;5;255m",
},
background: map[chroma.Colour]string{
c("#000000"): "\033[48;5;0m", c("#800000"): "\033[48;5;1m", c("#008000"): "\033[48;5;2m", c("#808000"): "\033[48;5;3m",
c("#000080"): "\033[48;5;4m", c("#800080"): "\033[48;5;5m", c("#008080"): "\033[48;5;6m", c("#c0c0c0"): "\033[48;5;7m",
c("#808080"): "\033[48;5;8m", c("#ff0000"): "\033[48;5;9m", c("#00ff00"): "\033[48;5;10m", c("#ffff00"): "\033[48;5;11m",
c("#0000ff"): "\033[48;5;12m", c("#ff00ff"): "\033[48;5;13m", c("#00ffff"): "\033[48;5;14m", c("#ffffff"): "\033[48;5;15m",
c("#000000"): "\033[48;5;16m", c("#00005f"): "\033[48;5;17m", c("#000087"): "\033[48;5;18m", c("#0000af"): "\033[48;5;19m",
c("#0000d7"): "\033[48;5;20m", c("#0000ff"): "\033[48;5;21m", c("#005f00"): "\033[48;5;22m", c("#005f5f"): "\033[48;5;23m",
c("#005f87"): "\033[48;5;24m", c("#005faf"): "\033[48;5;25m", c("#005fd7"): "\033[48;5;26m", c("#005fff"): "\033[48;5;27m",
c("#008700"): "\033[48;5;28m", c("#00875f"): "\033[48;5;29m", c("#008787"): "\033[48;5;30m", c("#0087af"): "\033[48;5;31m",
c("#0087d7"): "\033[48;5;32m", c("#0087ff"): "\033[48;5;33m", c("#00af00"): "\033[48;5;34m", c("#00af5f"): "\033[48;5;35m",
c("#00af87"): "\033[48;5;36m", c("#00afaf"): "\033[48;5;37m", c("#00afd7"): "\033[48;5;38m", c("#00afff"): "\033[48;5;39m",
c("#00d700"): "\033[48;5;40m", c("#00d75f"): "\033[48;5;41m", c("#00d787"): "\033[48;5;42m", c("#00d7af"): "\033[48;5;43m",
c("#00d7d7"): "\033[48;5;44m", c("#00d7ff"): "\033[48;5;45m", c("#00ff00"): "\033[48;5;46m", c("#00ff5f"): "\033[48;5;47m",
c("#00ff87"): "\033[48;5;48m", c("#00ffaf"): "\033[48;5;49m", c("#00ffd7"): "\033[48;5;50m", c("#00ffff"): "\033[48;5;51m",
c("#5f0000"): "\033[48;5;52m", c("#5f005f"): "\033[48;5;53m", c("#5f0087"): "\033[48;5;54m", c("#5f00af"): "\033[48;5;55m",
c("#5f00d7"): "\033[48;5;56m", c("#5f00ff"): "\033[48;5;57m", c("#5f5f00"): "\033[48;5;58m", c("#5f5f5f"): "\033[48;5;59m",
c("#5f5f87"): "\033[48;5;60m", c("#5f5faf"): "\033[48;5;61m", c("#5f5fd7"): "\033[48;5;62m", c("#5f5fff"): "\033[48;5;63m",
c("#5f8700"): "\033[48;5;64m", c("#5f875f"): "\033[48;5;65m", c("#5f8787"): "\033[48;5;66m", c("#5f87af"): "\033[48;5;67m",
c("#5f87d7"): "\033[48;5;68m", c("#5f87ff"): "\033[48;5;69m", c("#5faf00"): "\033[48;5;70m", c("#5faf5f"): "\033[48;5;71m",
c("#5faf87"): "\033[48;5;72m", c("#5fafaf"): "\033[48;5;73m", c("#5fafd7"): "\033[48;5;74m", c("#5fafff"): "\033[48;5;75m",
c("#5fd700"): "\033[48;5;76m", c("#5fd75f"): "\033[48;5;77m", c("#5fd787"): "\033[48;5;78m", c("#5fd7af"): "\033[48;5;79m",
c("#5fd7d7"): "\033[48;5;80m", c("#5fd7ff"): "\033[48;5;81m", c("#5fff00"): "\033[48;5;82m", c("#5fff5f"): "\033[48;5;83m",
c("#5fff87"): "\033[48;5;84m", c("#5fffaf"): "\033[48;5;85m", c("#5fffd7"): "\033[48;5;86m", c("#5fffff"): "\033[48;5;87m",
c("#870000"): "\033[48;5;88m", c("#87005f"): "\033[48;5;89m", c("#870087"): "\033[48;5;90m", c("#8700af"): "\033[48;5;91m",
c("#8700d7"): "\033[48;5;92m", c("#8700ff"): "\033[48;5;93m", c("#875f00"): "\033[48;5;94m", c("#875f5f"): "\033[48;5;95m",
c("#875f87"): "\033[48;5;96m", c("#875faf"): "\033[48;5;97m", c("#875fd7"): "\033[48;5;98m", c("#875fff"): "\033[48;5;99m",
c("#878700"): "\033[48;5;100m", c("#87875f"): "\033[48;5;101m", c("#878787"): "\033[48;5;102m", c("#8787af"): "\033[48;5;103m",
c("#8787d7"): "\033[48;5;104m", c("#8787ff"): "\033[48;5;105m", c("#87af00"): "\033[48;5;106m", c("#87af5f"): "\033[48;5;107m",
c("#87af87"): "\033[48;5;108m", c("#87afaf"): "\033[48;5;109m", c("#87afd7"): "\033[48;5;110m", c("#87afff"): "\033[48;5;111m",
c("#87d700"): "\033[48;5;112m", c("#87d75f"): "\033[48;5;113m", c("#87d787"): "\033[48;5;114m", c("#87d7af"): "\033[48;5;115m",
c("#87d7d7"): "\033[48;5;116m", c("#87d7ff"): "\033[48;5;117m", c("#87ff00"): "\033[48;5;118m", c("#87ff5f"): "\033[48;5;119m",
c("#87ff87"): "\033[48;5;120m", c("#87ffaf"): "\033[48;5;121m", c("#87ffd7"): "\033[48;5;122m", c("#87ffff"): "\033[48;5;123m",
c("#af0000"): "\033[48;5;124m", c("#af005f"): "\033[48;5;125m", c("#af0087"): "\033[48;5;126m", c("#af00af"): "\033[48;5;127m",
c("#af00d7"): "\033[48;5;128m", c("#af00ff"): "\033[48;5;129m", c("#af5f00"): "\033[48;5;130m", c("#af5f5f"): "\033[48;5;131m",
c("#af5f87"): "\033[48;5;132m", c("#af5faf"): "\033[48;5;133m", c("#af5fd7"): "\033[48;5;134m", c("#af5fff"): "\033[48;5;135m",
c("#af8700"): "\033[48;5;136m", c("#af875f"): "\033[48;5;137m", c("#af8787"): "\033[48;5;138m", c("#af87af"): "\033[48;5;139m",
c("#af87d7"): "\033[48;5;140m", c("#af87ff"): "\033[48;5;141m", c("#afaf00"): "\033[48;5;142m", c("#afaf5f"): "\033[48;5;143m",
c("#afaf87"): "\033[48;5;144m", c("#afafaf"): "\033[48;5;145m", c("#afafd7"): "\033[48;5;146m", c("#afafff"): "\033[48;5;147m",
c("#afd700"): "\033[48;5;148m", c("#afd75f"): "\033[48;5;149m", c("#afd787"): "\033[48;5;150m", c("#afd7af"): "\033[48;5;151m",
c("#afd7d7"): "\033[48;5;152m", c("#afd7ff"): "\033[48;5;153m", c("#afff00"): "\033[48;5;154m", c("#afff5f"): "\033[48;5;155m",
c("#afff87"): "\033[48;5;156m", c("#afffaf"): "\033[48;5;157m", c("#afffd7"): "\033[48;5;158m", c("#afffff"): "\033[48;5;159m",
c("#d70000"): "\033[48;5;160m", c("#d7005f"): "\033[48;5;161m", c("#d70087"): "\033[48;5;162m", c("#d700af"): "\033[48;5;163m",
c("#d700d7"): "\033[48;5;164m", c("#d700ff"): "\033[48;5;165m", c("#d75f00"): "\033[48;5;166m", c("#d75f5f"): "\033[48;5;167m",
c("#d75f87"): "\033[48;5;168m", c("#d75faf"): "\033[48;5;169m", c("#d75fd7"): "\033[48;5;170m", c("#d75fff"): "\033[48;5;171m",
c("#d78700"): "\033[48;5;172m", c("#d7875f"): "\033[48;5;173m", c("#d78787"): "\033[48;5;174m", c("#d787af"): "\033[48;5;175m",
c("#d787d7"): "\033[48;5;176m", c("#d787ff"): "\033[48;5;177m", c("#d7af00"): "\033[48;5;178m", c("#d7af5f"): "\033[48;5;179m",
c("#d7af87"): "\033[48;5;180m", c("#d7afaf"): "\033[48;5;181m", c("#d7afd7"): "\033[48;5;182m", c("#d7afff"): "\033[48;5;183m",
c("#d7d700"): "\033[48;5;184m", c("#d7d75f"): "\033[48;5;185m", c("#d7d787"): "\033[48;5;186m", c("#d7d7af"): "\033[48;5;187m",
c("#d7d7d7"): "\033[48;5;188m", c("#d7d7ff"): "\033[48;5;189m", c("#d7ff00"): "\033[48;5;190m", c("#d7ff5f"): "\033[48;5;191m",
c("#d7ff87"): "\033[48;5;192m", c("#d7ffaf"): "\033[48;5;193m", c("#d7ffd7"): "\033[48;5;194m", c("#d7ffff"): "\033[48;5;195m",
c("#ff0000"): "\033[48;5;196m", c("#ff005f"): "\033[48;5;197m", c("#ff0087"): "\033[48;5;198m", c("#ff00af"): "\033[48;5;199m",
c("#ff00d7"): "\033[48;5;200m", c("#ff00ff"): "\033[48;5;201m", c("#ff5f00"): "\033[48;5;202m", c("#ff5f5f"): "\033[48;5;203m",
c("#ff5f87"): "\033[48;5;204m", c("#ff5faf"): "\033[48;5;205m", c("#ff5fd7"): "\033[48;5;206m", c("#ff5fff"): "\033[48;5;207m",
c("#ff8700"): "\033[48;5;208m", c("#ff875f"): "\033[48;5;209m", c("#ff8787"): "\033[48;5;210m", c("#ff87af"): "\033[48;5;211m",
c("#ff87d7"): "\033[48;5;212m", c("#ff87ff"): "\033[48;5;213m", c("#ffaf00"): "\033[48;5;214m", c("#ffaf5f"): "\033[48;5;215m",
c("#ffaf87"): "\033[48;5;216m", c("#ffafaf"): "\033[48;5;217m", c("#ffafd7"): "\033[48;5;218m", c("#ffafff"): "\033[48;5;219m",
c("#ffd700"): "\033[48;5;220m", c("#ffd75f"): "\033[48;5;221m", c("#ffd787"): "\033[48;5;222m", c("#ffd7af"): "\033[48;5;223m",
c("#ffd7d7"): "\033[48;5;224m", c("#ffd7ff"): "\033[48;5;225m", c("#ffff00"): "\033[48;5;226m", c("#ffff5f"): "\033[48;5;227m",
c("#ffff87"): "\033[48;5;228m", c("#ffffaf"): "\033[48;5;229m", c("#ffffd7"): "\033[48;5;230m", c("#ffffff"): "\033[48;5;231m",
c("#080808"): "\033[48;5;232m", c("#121212"): "\033[48;5;233m", c("#1c1c1c"): "\033[48;5;234m", c("#262626"): "\033[48;5;235m",
c("#303030"): "\033[48;5;236m", c("#3a3a3a"): "\033[48;5;237m", c("#444444"): "\033[48;5;238m", c("#4e4e4e"): "\033[48;5;239m",
c("#585858"): "\033[48;5;240m", c("#626262"): "\033[48;5;241m", c("#6c6c6c"): "\033[48;5;242m", c("#767676"): "\033[48;5;243m",
c("#808080"): "\033[48;5;244m", c("#8a8a8a"): "\033[48;5;245m", c("#949494"): "\033[48;5;246m", c("#9e9e9e"): "\033[48;5;247m",
c("#a8a8a8"): "\033[48;5;248m", c("#b2b2b2"): "\033[48;5;249m", c("#bcbcbc"): "\033[48;5;250m", c("#c6c6c6"): "\033[48;5;251m",
c("#d0d0d0"): "\033[48;5;252m", c("#dadada"): "\033[48;5;253m", c("#e4e4e4"): "\033[48;5;254m", c("#eeeeee"): "\033[48;5;255m",
},
},
}
func entryToEscapeSequence(table *ttyTable, entry chroma.StyleEntry) string {
out := ""
if entry.Bold == chroma.Yes {
out += "\033[1m"
}
if entry.Underline == chroma.Yes {
out += "\033[4m"
}
if entry.Italic == chroma.Yes {
out += "\033[3m"
}
if entry.Colour.IsSet() {
out += table.foreground[findClosest(table, entry.Colour)]
}
if entry.Background.IsSet() {
out += table.background[findClosest(table, entry.Background)]
}
return out
}
func findClosest(table *ttyTable, seeking chroma.Colour) chroma.Colour {
closestColour := chroma.Colour(0)
closest := float64(math.MaxFloat64)
for colour := range table.foreground {
distance := colour.Distance(seeking)
if distance < closest {
closest = distance
closestColour = colour
}
}
return closestColour
}
func styleToEscapeSequence(table *ttyTable, style *chroma.Style) map[chroma.TokenType]string {
style = clearBackground(style)
out := map[chroma.TokenType]string{}
for _, ttype := range style.Types() {
entry := style.Get(ttype)
out[ttype] = entryToEscapeSequence(table, entry)
}
return out
}
// Clear the background colour.
func clearBackground(style *chroma.Style) *chroma.Style {
builder := style.Builder()
bg := builder.Get(chroma.Background)
bg.Background = 0
bg.NoInherit = true
builder.AddEntry(chroma.Background, bg)
style, _ = builder.Build()
return style
}
type indexedTTYFormatter struct {
table *ttyTable
}
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
theme := styleToEscapeSequence(c.table, style)
for token := it(); token != chroma.EOF; token = it() {
// TODO: Cache token lookups?
clr, ok := theme[token.Type]
if !ok {
clr, ok = theme[token.Type.SubCategory()]
if !ok {
clr = theme[token.Type.Category()]
// if !ok {
// clr = theme[chroma.InheritStyle]
// }
}
}
if clr != "" {
fmt.Fprint(w, clr)
}
fmt.Fprint(w, token.Value)
if clr != "" {
fmt.Fprintf(w, "\033[0m")
}
}
return nil
}
// TTY8 is an 8-colour terminal formatter.
//
// The Lab colour space is used to map RGB values to the most appropriate index colour.
var TTY8 = Register("terminal", &indexedTTYFormatter{ttyTables[8]})
// TTY256 is a 256-colour terminal formatter.
//
// The Lab colour space is used to map RGB values to the most appropriate index colour.
var TTY256 = Register("terminal256", &indexedTTYFormatter{ttyTables[256]})

View File

@@ -1,42 +0,0 @@
package formatters
import (
"fmt"
"io"
"github.com/alecthomas/chroma"
)
// TTY16m is a true-colour terminal formatter.
var TTY16m = Register("terminal16m", chroma.FormatterFunc(trueColourFormatter))
func trueColourFormatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
style = clearBackground(style)
for token := it(); token != chroma.EOF; token = it() {
entry := style.Get(token.Type)
if !entry.IsZero() {
out := ""
if entry.Bold == chroma.Yes {
out += "\033[1m"
}
if entry.Underline == chroma.Yes {
out += "\033[4m"
}
if entry.Italic == chroma.Yes {
out += "\033[3m"
}
if entry.Colour.IsSet() {
out += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
}
if entry.Background.IsSet() {
out += fmt.Sprintf("\033[48;2;%d;%d;%dm", entry.Background.Red(), entry.Background.Green(), entry.Background.Blue())
}
fmt.Fprint(w, out)
}
fmt.Fprint(w, token.Value)
if !entry.IsZero() {
fmt.Fprint(w, "\033[0m")
}
}
return nil
}

View File

@@ -1,17 +0,0 @@
module github.com/alecthomas/chroma
go 1.13
require (
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
github.com/dlclark/regexp2 v1.1.6
github.com/mattn/go-colorable v0.0.9
github.com/mattn/go-isatty v0.0.4
github.com/sergi/go-diff v1.0.0 // indirect
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
)

View File

@@ -1,33 +0,0 @@
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae h1:C4Q9m+oXOxcSWwYk9XzzafY2xAVAaeubZbUHJkw3PlY=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

Some files were not shown because too many files have changed in this diff Show More