Initial commit
Some checks failed
Codespell / Check for spelling errors (push) Failing after 1m26s

Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
This commit is contained in:
Sagi Dayan 2024-11-22 17:29:25 +02:00
commit d5d1eecb89
Signed by: sagi
GPG key ID: FAB96BFC63B46458
1804 changed files with 3967683 additions and 0 deletions

View file

@ -0,0 +1,27 @@
name: Codespell
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Annotate locations with typos
uses: "https://github.com/codespell-project/codespell-problem-matcher@v1"
- name: Codespell
uses: "https://github.com/codespell-project/actions-codespell@v2"
with:
check_filenames: true
check_hidden: true
skip: ./vendor

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
build/
# test secrets
.secrets

46
Makefile Normal file
View file

@ -0,0 +1,46 @@
BINARY_NAME=envoid
VERSION=1.0.0-alpha
BUILD_FOLDER=build
GO_BUILD_LD_FLAGS=-ldflags="-s -w -X 'git.dayanhub.com/sagi/envoid/internal/variables.Commit=$(shell git rev-parse --short HEAD)' \
-X 'git.dayanhub.com/sagi/envoid/internal/variables.Version=${VERSION}'"
.PHONY: build
build:
GOARCH=amd64 GOOS=linux go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-linux main.go
GOARCH=amd64 GOOS=darwin go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-darwin main.go
GOARCH=amd64 GOOS=windows go build ${GO_BUILD_LD_FLAGS} -o ${BUILD_FOLDER}/${BINARY_NAME}-windows main.go
.PHONY: clean
clean:
go clean
rm -rf ${BUILD_FOLDER}
rm -rf ./docs
.PHONY: test
test:
go test ./...
.PHONY: test_coverage
test_coverage:
go test ./... -coverprofile=coverage.out
.PHONY: dep
dep:
go mod tidy
go mod vendor
.PHONY: vet
vet:
go vet
.PHONY: lint
lint:
golangci-lint run --enable-all
.PHONY: doc-gen
doc-gen:
rm -rf ./docs
mkdir ./docs
go run . doc

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Envoid
##### The `.env`(s) manager you never knew you needed
⚠ WIP !
[Documentation](docs/envoid.md)

145
cmd/env.go Normal file
View file

@ -0,0 +1,145 @@
package cmd
import (
"fmt"
"strings"
"git.dayanhub.com/sagi/envoid/internal/datastore"
"git.dayanhub.com/sagi/envoid/internal/errors"
"github.com/spf13/cobra"
)
type envCmdFlags struct {
rmEnvName *string
addEnvName *string
baseEnvName *string
}
var envFlags = &envCmdFlags{}
var envCmd = &cobra.Command{
Use: "env",
Short: "manage environments",
Long: "",
}
var rmEnvCmd = &cobra.Command{
Use: "rm",
Short: "removes an environment",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
if err := initProject(); err != nil {
return err
}
ds, err := datastore.NewDataStore()
if err != nil {
return err
}
defer ds.Close()
err = ds.RemoveEnv(*envFlags.rmEnvName)
if err != nil {
return err
}
project.RemoveEnv(*envFlags.rmEnvName)
configuration.Save()
return nil
},
}
var addEnvCmd = &cobra.Command{
Use: "add",
Short: "adds an environment",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
if err := initProject(); err != nil {
return err
}
if len(strings.TrimSpace(*envFlags.addEnvName)) == 0 {
return errors.NewInvalidFlagValueError("environment", *envFlags.addEnvName)
}
if _, err := project.GetEnv(*envFlags.addEnvName); err == nil {
return errors.NewEnvironmentExistsError(*envFlags.addEnvName)
}
ds, err := datastore.NewDataStore()
if err != nil {
return err
}
defer ds.Close()
if len(*envFlags.baseEnvName) != 0 {
_, err = project.GetEnv(*envFlags.baseEnvName)
if err != nil {
return err
}
err = ds.CreateEnvOffExsisting(*envFlags.addEnvName, *envFlags.baseEnvName)
if err != nil {
return err
}
} else {
err = ds.CreateEnv(*envFlags.addEnvName)
if err != nil {
return err
}
}
err = project.NewEnv(*envFlags.addEnvName)
if err != nil {
return err
}
configuration.Save()
return nil
},
}
var lsEnvCmd = &cobra.Command{
Use: "ls",
Short: "list all environments in this project",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
if err := initProject(); err != nil {
return err
}
if project.IsEmpty() {
return errors.NewProjectEmptyError(project.Name)
}
for _, env := range project.Environments {
fmt.Printf("%s\n", env.Name)
}
return nil
},
}
func init() {
// add
envFlags.addEnvName = addEnvCmd.Flags().StringP("environment", "e", "", "environment name")
err := addEnvCmd.MarkFlagRequired("environment")
if err != nil {
panic(err)
}
envFlags.baseEnvName = addEnvCmd.Flags().StringP("base-environment", "b", "", "base environment name (copy data from base)")
err = addEnvCmd.RegisterFlagCompletionFunc("base-environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
//rm
envFlags.rmEnvName = rmEnvCmd.Flags().StringP("environment", "e", "", "environment name")
err = rmEnvCmd.MarkFlagRequired("environment")
if err != nil {
panic(err)
}
err = rmEnvCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
// ls
envCmd.AddCommand(addEnvCmd)
envCmd.AddCommand(rmEnvCmd)
envCmd.AddCommand(lsEnvCmd)
}

81
cmd/get.go Normal file
View file

@ -0,0 +1,81 @@
package cmd
import (
"fmt"
"os"
"errors"
"git.dayanhub.com/sagi/envoid/internal/datastore"
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
"github.com/spf13/cobra"
)
type getCmdFlags struct {
envName *string
}
var getFlags = getCmdFlags{}
var getCmd = &cobra.Command{
Use: "get <key>",
Short: "Gets a variable",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return intErrors.NewProjectEmptyError(project.Name)
}
if len(args) != 1 {
return intErrors.NewInvalidCommandError("expected 1 args. <key>")
}
key := args[0]
env := project.Environments[0]
err = checkAmbiguousEnv(*getFlags.envName)
if err != nil {
return err
}
if len(*getFlags.envName) > 0 {
e, err := project.GetEnv(*getFlags.envName)
if err != nil {
return err
}
env = e
}
ds, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer ds.Close()
envVar, err := ds.GetVar(env.Name, key)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
if errors.Is(err, &intErrors.NoKeyFoundError{}) {
os.Exit(1)
} else if errors.Is(err, intErrors.NewInvalidPasswordError()) {
os.Exit(1)
} else {
os.Exit(1)
}
}
fmt.Println(envVar.Value)
return nil
},
}
func init() {
getFlags.envName = getCmd.Flags().StringP("environment", "e", "", "environment name")
err := getCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
}

77
cmd/import.go Normal file
View file

@ -0,0 +1,77 @@
package cmd
import (
"fmt"
"os"
"git.dayanhub.com/sagi/envoid/internal/datastore"
"git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"github.com/joho/godotenv"
"github.com/spf13/cobra"
)
type importCmdFlags struct {
envName *string
}
var importFlags = importCmdFlags{}
var importCmd = &cobra.Command{
Use: "import <file>",
Short: "import a .env file into environment(s)",
Long: "This will not encrypt any value. You can then use `set encrypt KEY_NAME` to encrypt",
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return errors.NewProjectEmptyError(project.Name)
}
envs := project.Environments
if len(*importFlags.envName) != 0 {
e, err := project.GetEnv(*importFlags.envName)
if err != nil {
return err
}
envs = []*types.Environment{e}
}
if len(args) != 1 {
return fmt.Errorf("Needs a file to parse")
}
file, err := os.Open(args[0])
if err != nil {
return err
}
m, err := godotenv.Parse(file)
if err != nil {
return err
}
ds, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer ds.Close()
for k, v := range m {
err = ds.SetValue(k, v, nil, envs)
if err != nil {
return err
}
}
return nil
},
}
func init() {
importFlags.envName = importCmd.Flags().StringP("environment", "e", "", "environments name")
err := importCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
}

79
cmd/init.go Normal file
View file

@ -0,0 +1,79 @@
package cmd
import (
"fmt"
"strings"
"git.dayanhub.com/sagi/envoid/internal/datastore"
"git.dayanhub.com/sagi/envoid/internal/prompt"
"git.dayanhub.com/sagi/envoid/internal/variables"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "creates a new project for this path. default env is set to `local`",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
_, err := configuration.GetProject(workingDir)
if err == nil {
fmt.Printf("Project already exists. please remove if you wish to override\n")
return nil
}
ds, err := datastore.NewDataStore()
if err != nil {
return err
}
defer ds.Close()
var name string
var pass string
var envNames []string
//check if we are importing an existing file
if ds.DoesFileExists() {
fmt.Printf("Project was not found locally. But found %s file in path.\n", variables.DBFileName)
fmt.Printf("Importing Project for %s\n", workingDir)
name = prompt.StringPrompt("Project Name:")
pass = prompt.PasswordPrompt("Project Password (Same password that was used originally):")
envNames, err = ds.ListEnvironments()
if err != nil {
return err
}
fmt.Printf("Found %d environments. importing %v\n", len(envNames), envNames)
} else {
// New Project
fmt.Printf("Creating new Project @ %s\n", workingDir)
name = prompt.StringPrompt("Project Name:")
pass = prompt.PasswordPrompt("Project Password:")
envs := strings.TrimSpace(prompt.StringPrompt("Please provide environment names separated by `,`. (stage,prod):"))
if len(envs) == 0 {
envs = "local"
}
envNames = strings.Split(envs, ",")
}
project, err = configuration.NewProject(name, workingDir, pass)
if err != nil {
return err
}
for _, eName := range envNames {
if err := project.NewEnv(eName); err != nil {
return err
}
if err := ds.CreateEnv(eName); err != nil {
return err
}
}
configuration.Save()
fmt.Println("✅ Done")
return nil
},
}
func init() {
}

81
cmd/printenv.go Normal file
View file

@ -0,0 +1,81 @@
package cmd
import (
"fmt"
"strings"
"git.dayanhub.com/sagi/envoid/internal/datastore"
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
"github.com/spf13/cobra"
)
type printenvCmdFlags struct {
envName *string
}
var printenvFlags = printenvCmdFlags{}
var printenvCmd = &cobra.Command{
Use: "printenv",
Short: "prints the whole environment in a .env format",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return intErrors.NewProjectEmptyError(project.Name)
}
if len(args) != 0 {
return intErrors.NewInvalidCommandError("expected 0 args.")
}
err = checkAmbiguousEnv(*printenvFlags.envName)
if err != nil {
return err
}
env := project.Environments[0]
if len(*printenvFlags.envName) > 0 {
e, err := project.GetEnv(*printenvFlags.envName)
if err != nil {
return err
}
env = e
}
datastore, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer datastore.Close()
vars, err := datastore.GetAll(env.Name)
if err != nil {
return err
}
for _, v := range vars {
if v.Encrypted {
fmt.Println("# SENSITIVE VAR BELOW")
}
if len(v.Value) != len(strings.ReplaceAll(v.Value, " ", "")) {
// value contain spaces. need to wrap with ""
fmt.Printf("%s=\"%s\"\n", v.Key, v.Value)
} else {
fmt.Printf("%s=%s\n", v.Key, v.Value)
}
if v.Encrypted {
fmt.Println("###")
}
}
return nil
},
}
func init() {
printenvFlags.envName = printenvCmd.Flags().StringP("environment", "e", "", "environments name")
err := printenvCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
}

57
cmd/project.go Normal file
View file

@ -0,0 +1,57 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
type projectCmdFlags struct {
projectPath *string
}
var projectFlags = projectCmdFlags{}
var projectCmd = &cobra.Command{
Use: "project",
Short: "manage project",
Long: "",
}
var lsProjectCmd = &cobra.Command{
Use: "ls",
Short: "list all projects for this user",
RunE: func(cmd *cobra.Command, args []string) error {
for _, proj := range configuration.Projects {
fmt.Printf("%s\t%s\n", proj.Name, proj.Path)
}
return nil
},
}
var rmProjectCmd = &cobra.Command{
Use: "rm",
Short: "remove a project definition. the `.envoid` file will not be removed",
RunE: func(cmd *cobra.Command, args []string) error {
project, err := configuration.GetProject(*projectFlags.projectPath)
if err != nil {
return err
}
configuration.RemoveProject(project)
configuration.Save()
return nil
},
}
func init() {
projectFlags.projectPath = rmProjectCmd.Flags().StringP("project-path", "p", "", "project path to remove")
err := rmProjectCmd.MarkFlagRequired("project-path")
if err != nil {
panic(err)
}
err = rmProjectCmd.RegisterFlagCompletionFunc("project-path", validProjectPathComplete)
if err != nil {
panic(err)
}
projectCmd.AddCommand(lsProjectCmd)
projectCmd.AddCommand(rmProjectCmd)
}

62
cmd/rm.go Normal file
View file

@ -0,0 +1,62 @@
package cmd
import (
"fmt"
"git.dayanhub.com/sagi/envoid/internal/datastore"
"git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"github.com/spf13/cobra"
)
type rmCmdFlags struct {
envName *string
}
var rmFlags = rmCmdFlags{}
var rmCmd = &cobra.Command{
Use: "rm <key>",
Short: "removes a variable from environment(s)",
Long: "",
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return errors.NewProjectEmptyError(project.Name)
}
if len(args) != 1 {
return errors.NewInvalidCommandError("expected 1 args. <key>")
}
key := args[0]
ds, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer ds.Close()
envs := project.Environments
if len(*rmFlags.envName) != 0 {
e, err := project.GetEnv(*rmFlags.envName)
if err != nil {
return err
}
envs = []*types.Environment{e}
}
ds.RemoveVar(key, envs)
return nil
},
}
func init() {
rmFlags.envName = rmCmd.Flags().StringP("environment", "e", "", "environments name")
err := rmCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
}

99
cmd/root.go Normal file
View file

@ -0,0 +1,99 @@
package cmd
import (
"fmt"
"os"
"git.dayanhub.com/sagi/envoid/internal/config"
"git.dayanhub.com/sagi/envoid/internal/types"
"git.dayanhub.com/sagi/envoid/internal/variables"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
var configuration *config.Config = nil
var workingDir string
var project *types.Project = nil
var rootCmd = &cobra.Command{
Use: "envoid [command]",
Short: "envoid is an easy to use .env manager for personal (non production) use",
Long: `envoid is an easy to use .env manager for personal (non production) use.
envoid works offline and creates different encrypted environments for each project. It's mainly used to store,encrypt (with a passphrase) and share
.env variables`,
Version: fmt.Sprintf("%s commit %s", variables.Version, variables.Commit),
}
var docCmd = &cobra.Command{
Use: "doc",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
err := doc.GenMarkdownTree(rootCmd, "./docs")
if err != nil {
return err
}
return nil
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func checkAmbiguousEnv(envName string) error {
if len(envName) == 0 && len(project.Environments) > 1 {
return fmt.Errorf("You have more than 1 environment. please provide environment name")
}
return nil
}
func initProject() error {
p, err := configuration.GetProject(workingDir)
if err != nil {
return err
}
project = p
return nil
}
func validEnvironmentNamesComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
envs := []string{}
if err := initProject(); err == nil {
for _, e := range project.Environments {
envs = append(envs, e.Name)
}
}
return envs, cobra.ShellCompDirectiveDefault
}
func validProjectPathComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
paths := []string{}
for _, p := range configuration.Projects {
paths = append(paths, fmt.Sprintf("%s\t%s", p.Path, p.Name))
}
return paths, cobra.ShellCompDirectiveDefault
}
func init() {
rootCmd.AddCommand(docCmd)
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(setCmd)
rootCmd.AddCommand(rmCmd)
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(envCmd)
rootCmd.AddCommand(printenvCmd)
rootCmd.AddCommand(importCmd)
rootCmd.AddCommand(projectCmd)
configuration = config.GetConfig()
pwd, err := os.Getwd()
if err != nil {
fmt.Printf("[error] %s. please run 'ssecret init'\n", err.Error())
os.Exit(1)
}
workingDir = pwd
}

125
cmd/set.go Normal file
View file

@ -0,0 +1,125 @@
package cmd
import (
"fmt"
"git.dayanhub.com/sagi/envoid/internal/common"
"git.dayanhub.com/sagi/envoid/internal/datastore"
"git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"github.com/spf13/cobra"
)
type setCmdFlags struct {
envName *string
encrypt *bool
}
var setFlags = setCmdFlags{}
var setCmd = &cobra.Command{
Use: "set [flags] <key> <value>",
Short: "sets a variable in environment(s)",
Long: "",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.NewInvalidCommandError("expected 2 args. <key> <value>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return errors.NewProjectEmptyError(project.Name)
}
key := args[0]
val := args[1]
ds, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer ds.Close()
envs := project.Environments
if len(*setFlags.envName) != 0 {
e, err := project.GetEnv(*setFlags.envName)
if err != nil {
return err
}
envs = []*types.Environment{e}
}
err = ds.SetValue(key, val, setFlags.encrypt, envs)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
}
return nil
},
}
var setEncryptCmd = &cobra.Command{
Use: "encrypt <key>",
Short: "encrypts an existing variable in environment(s)",
Long: "",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.NewInvalidCommandError("expected 1 args. <key>")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
err := initProject()
if err != nil {
return err
}
if project.IsEmpty() {
return errors.NewProjectEmptyError(project.Name)
}
key := args[0]
ds, err := datastore.NewDataStore()
if err != nil {
fmt.Printf("Error: %e", err)
}
defer ds.Close()
envs := project.Environments
if len(*setFlags.envName) != 0 {
e, err := project.GetEnv(*setFlags.envName)
if err != nil {
return err
}
envs = []*types.Environment{e}
}
for _, env := range envs {
// Get value
v, err := ds.GetVar(env.Name, key)
if err != nil {
return err
}
if !v.Encrypted {
err := ds.SetValue(v.Key, v.Value, common.BoolP(true), []*types.Environment{env})
if err != nil {
return err
}
}
}
return nil
},
}
func init() {
setFlags.envName = setCmd.PersistentFlags().StringP("environment", "e", "", "environments name")
setFlags.encrypt = setCmd.Flags().BoolP("secret", "s", false, "value is a secret. encrypt this value")
err := setCmd.RegisterFlagCompletionFunc("environment", validEnvironmentNamesComplete)
if err != nil {
panic(err)
}
setCmd.AddCommand(setEncryptCmd)
}

30
docs/envoid.md Normal file
View file

@ -0,0 +1,30 @@
## envoid
envoid is an easy to use .env manager for personal (non production) use
### Synopsis
envoid is an easy to use .env manager for personal (non production) use.
envoid works offline and creates different encrypted environments for each project. It's mainly used to store,encrypt (with a passphrase) and share
.env variables
### Options
```
-h, --help help for envoid
```
### SEE ALSO
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
* [envoid env](envoid_env.md) - manage environments
* [envoid get](envoid_get.md) - Gets a variable
* [envoid import](envoid_import.md) - import a .env file into environment(s)
* [envoid init](envoid_init.md) - creates a new project for this path. default env is set to `local`
* [envoid printenv](envoid_printenv.md) - prints the whole environment in a .env format
* [envoid project](envoid_project.md) - manage project
* [envoid rm](envoid_rm.md) - removes a variable from environment(s)
* [envoid set](envoid_set.md) - sets a variable in environment(s)
###### Auto generated by spf13/cobra on 12-Dec-2024

25
docs/envoid_completion.md Normal file
View file

@ -0,0 +1,25 @@
## envoid completion
Generate the autocompletion script for the specified shell
### Synopsis
Generate the autocompletion script for envoid for the specified shell.
See each sub-command's help for details on how to use the generated script.
### Options
```
-h, --help help for completion
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
* [envoid completion bash](envoid_completion_bash.md) - Generate the autocompletion script for bash
* [envoid completion fish](envoid_completion_fish.md) - Generate the autocompletion script for fish
* [envoid completion powershell](envoid_completion_powershell.md) - Generate the autocompletion script for powershell
* [envoid completion zsh](envoid_completion_zsh.md) - Generate the autocompletion script for zsh
###### Auto generated by spf13/cobra on 12-Dec-2024

View file

@ -0,0 +1,44 @@
## envoid completion bash
Generate the autocompletion script for bash
### Synopsis
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(envoid completion bash)
To load completions for every new session, execute once:
#### Linux:
envoid completion bash > /etc/bash_completion.d/envoid
#### macOS:
envoid completion bash > $(brew --prefix)/etc/bash_completion.d/envoid
You will need to start a new shell for this setup to take effect.
```
envoid completion bash
```
### Options
```
-h, --help help for bash
--no-descriptions disable completion descriptions
```
### SEE ALSO
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 12-Dec-2024

View file

@ -0,0 +1,35 @@
## envoid completion fish
Generate the autocompletion script for fish
### Synopsis
Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
envoid completion fish | source
To load completions for every new session, execute once:
envoid completion fish > ~/.config/fish/completions/envoid.fish
You will need to start a new shell for this setup to take effect.
```
envoid completion fish [flags]
```
### Options
```
-h, --help help for fish
--no-descriptions disable completion descriptions
```
### SEE ALSO
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 12-Dec-2024

View file

@ -0,0 +1,32 @@
## envoid completion powershell
Generate the autocompletion script for powershell
### Synopsis
Generate the autocompletion script for powershell.
To load completions in your current shell session:
envoid completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
```
envoid completion powershell [flags]
```
### Options
```
-h, --help help for powershell
--no-descriptions disable completion descriptions
```
### SEE ALSO
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 12-Dec-2024

View file

@ -0,0 +1,46 @@
## envoid completion zsh
Generate the autocompletion script for zsh
### Synopsis
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(envoid completion zsh)
To load completions for every new session, execute once:
#### Linux:
envoid completion zsh > "${fpath[1]}/_envoid"
#### macOS:
envoid completion zsh > $(brew --prefix)/share/zsh/site-functions/_envoid
You will need to start a new shell for this setup to take effect.
```
envoid completion zsh [flags]
```
### Options
```
-h, --help help for zsh
--no-descriptions disable completion descriptions
```
### SEE ALSO
* [envoid completion](envoid_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 12-Dec-2024

18
docs/envoid_env.md Normal file
View file

@ -0,0 +1,18 @@
## envoid env
manage environments
### Options
```
-h, --help help for env
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
* [envoid env add](envoid_env_add.md) - adds an environment
* [envoid env ls](envoid_env_ls.md) - list all environments in this project
* [envoid env rm](envoid_env_rm.md) - removes an environment
###### Auto generated by spf13/cobra on 12-Dec-2024

21
docs/envoid_env_add.md Normal file
View file

@ -0,0 +1,21 @@
## envoid env add
adds an environment
```
envoid env add [flags]
```
### Options
```
-b, --base-environment string base environment name (copy data from base)
-e, --environment string environment name
-h, --help help for add
```
### SEE ALSO
* [envoid env](envoid_env.md) - manage environments
###### Auto generated by spf13/cobra on 12-Dec-2024

19
docs/envoid_env_ls.md Normal file
View file

@ -0,0 +1,19 @@
## envoid env ls
list all environments in this project
```
envoid env ls [flags]
```
### Options
```
-h, --help help for ls
```
### SEE ALSO
* [envoid env](envoid_env.md) - manage environments
###### Auto generated by spf13/cobra on 12-Dec-2024

20
docs/envoid_env_rm.md Normal file
View file

@ -0,0 +1,20 @@
## envoid env rm
removes an environment
```
envoid env rm [flags]
```
### Options
```
-e, --environment string environment name
-h, --help help for rm
```
### SEE ALSO
* [envoid env](envoid_env.md) - manage environments
###### Auto generated by spf13/cobra on 12-Dec-2024

20
docs/envoid_get.md Normal file
View file

@ -0,0 +1,20 @@
## envoid get
Gets a variable
```
envoid get <key> [flags]
```
### Options
```
-e, --environment string environment name
-h, --help help for get
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
###### Auto generated by spf13/cobra on 12-Dec-2024

24
docs/envoid_import.md Normal file
View file

@ -0,0 +1,24 @@
## envoid import
import a .env file into environment(s)
### Synopsis
This will not encrypt any value. You can then use `set encrypt KEY_NAME` to encrypt
```
envoid import <file> [flags]
```
### Options
```
-e, --environment string environments name
-h, --help help for import
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
###### Auto generated by spf13/cobra on 12-Dec-2024

19
docs/envoid_init.md Normal file
View file

@ -0,0 +1,19 @@
## envoid init
creates a new project for this path. default env is set to `local`
```
envoid init [flags]
```
### Options
```
-h, --help help for init
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
###### Auto generated by spf13/cobra on 12-Dec-2024

20
docs/envoid_printenv.md Normal file
View file

@ -0,0 +1,20 @@
## envoid printenv
prints the whole environment in a .env format
```
envoid printenv [flags]
```
### Options
```
-e, --environment string environments name
-h, --help help for printenv
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
###### Auto generated by spf13/cobra on 12-Dec-2024

17
docs/envoid_project.md Normal file
View file

@ -0,0 +1,17 @@
## envoid project
manage project
### Options
```
-h, --help help for project
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
* [envoid project ls](envoid_project_ls.md) - list all projects for this user
* [envoid project rm](envoid_project_rm.md) - remove a project definition. the `.envoid` file will not be removed
###### Auto generated by spf13/cobra on 12-Dec-2024

19
docs/envoid_project_ls.md Normal file
View file

@ -0,0 +1,19 @@
## envoid project ls
list all projects for this user
```
envoid project ls [flags]
```
### Options
```
-h, --help help for ls
```
### SEE ALSO
* [envoid project](envoid_project.md) - manage project
###### Auto generated by spf13/cobra on 12-Dec-2024

20
docs/envoid_project_rm.md Normal file
View file

@ -0,0 +1,20 @@
## envoid project rm
remove a project definition. the `.envoid` file will not be removed
```
envoid project rm [flags]
```
### Options
```
-h, --help help for rm
-p, --project-path string project path to remove
```
### SEE ALSO
* [envoid project](envoid_project.md) - manage project
###### Auto generated by spf13/cobra on 12-Dec-2024

20
docs/envoid_rm.md Normal file
View file

@ -0,0 +1,20 @@
## envoid rm
removes a variable from environment(s)
```
envoid rm <key> [flags]
```
### Options
```
-e, --environment string environments name
-h, --help help for rm
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
###### Auto generated by spf13/cobra on 12-Dec-2024

22
docs/envoid_set.md Normal file
View file

@ -0,0 +1,22 @@
## envoid set
sets a variable in environment(s)
```
envoid set [flags] <key> <value>
```
### Options
```
-e, --environment string environments name
-h, --help help for set
-s, --secret value is a secret. encrypt this value
```
### SEE ALSO
* [envoid](envoid.md) - envoid is an easy to use .env manager for personal (non production) use
* [envoid set encrypt](envoid_set_encrypt.md) - encrypts an existing variable in environment(s)
###### Auto generated by spf13/cobra on 12-Dec-2024

View file

@ -0,0 +1,25 @@
## envoid set encrypt
encrypts an existing variable in environment(s)
```
envoid set encrypt <key> [flags]
```
### Options
```
-h, --help help for encrypt
```
### Options inherited from parent commands
```
-e, --environment string environments name
```
### SEE ALSO
* [envoid set](envoid_set.md) - sets a variable in environment(s)
###### Auto generated by spf13/cobra on 12-Dec-2024

30
go.mod Normal file
View file

@ -0,0 +1,30 @@
module git.dayanhub.com/sagi/envoid
go 1.23.3
require (
github.com/creasty/defaults v1.8.0
github.com/glebarez/go-sqlite v1.22.0
github.com/joho/godotenv v1.5.1
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.29.0
golang.org/x/sync v0.9.0
golang.org/x/term v0.26.0
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.27.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.37.6 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.28.0 // indirect
)

47
go.sum Normal file
View file

@ -0,0 +1,47 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw=
modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=

View file

@ -0,0 +1,9 @@
package common
func BoolP(val bool) *bool {
return &val
}
func StringP(val string) *string {
return &val
}

View file

@ -0,0 +1,10 @@
package common
import "strings"
func StrToSnakeCase(s string) string {
snake := strings.ToLower(s)
snake = strings.ReplaceAll(snake, " ", "_")
snake = strings.ReplaceAll(snake, "\t", "_")
return snake
}

145
internal/config/config.go Normal file
View file

@ -0,0 +1,145 @@
package config
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path"
"git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"github.com/creasty/defaults"
)
type Config struct {
PWD string `json:"-"`
Projects []*types.Project `json:"projects" default:"[]"`
}
var configPath string
var configStruct *Config
func (c *Config) GetProject(projectPath string) (*types.Project, error) {
var p *types.Project
for _, project := range configStruct.Projects {
if project.Path == projectPath {
p = project
break
}
}
if p == nil {
return nil, errors.NewProjectNotFoundError(projectPath)
}
return p, nil
}
func (c *Config) RemoveProject(project *types.Project) {
projects := []*types.Project{}
for _, p := range c.Projects {
if p.Name != project.Name && p.Path != project.Path {
projects = append(projects, p)
}
}
c.Projects = projects
}
func (c *Config) NewProject(name string, path string, password string) (*types.Project, error) {
encPass := base64.RawStdEncoding.EncodeToString([]byte(password))
p := &types.Project{
Name: name,
Path: path,
Password: encPass,
Environments: []*types.Environment{},
}
configStruct.Projects = append(configStruct.Projects, p)
SaveConfig()
return p, nil
}
func GetConfig() *Config {
return configStruct
}
func (c *Config) Save() {
SaveConfig()
}
func CreateConfigIfNotExists() error {
return nil
}
func init() {
userConfigDir, err := os.UserConfigDir()
configDir := path.Join(userConfigDir, "envoid")
configPath = path.Join(configDir, "config.json")
if err != nil {
fmt.Printf("[ERROR] Failed to fetch user config directory. %e\n", err)
os.Exit(1)
}
if _, err := os.Stat(configDir); os.IsNotExist(err) {
err := os.MkdirAll(configDir, 0700)
if err != nil {
panic(err)
}
}
var configFile *os.File
if _, err := os.Stat(configPath); os.IsNotExist(err) {
configFile, err = os.Create(configPath)
defer func() {
err = configFile.Close()
if err != nil {
panic(err)
}
}()
if err != nil {
fmt.Printf("[ERROR] Failed to create config file @ %s. %e\n", configPath, err)
os.Exit(1)
}
}
configStruct, err = loadConfig()
if err != nil {
fmt.Printf("[ERROR] Failed to load config file @ %s. %e\n", configPath, err)
os.Exit(1)
}
}
func loadConfig() (*Config, error) {
c := &Config{}
err := defaults.Set(c)
if err != nil {
panic(err)
}
file, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
if len(file) == 0 {
return c, nil
}
if err = json.Unmarshal(file, c); err != nil {
return nil, err
}
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
c.PWD = pwd
return c, nil
}
func SaveConfig() {
yml, err := json.Marshal(configStruct)
if err != nil {
fmt.Printf("[ERROR] Failed to convert config to json. %e\n", err)
os.Exit(1)
}
err = os.WriteFile(configPath, yml, 0600)
if err != nil {
fmt.Printf("[ERROR] Failed to save config file @ %s. %e\n", configPath, err)
os.Exit(1)
}
}

View file

@ -0,0 +1,223 @@
package datastore
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"os"
"sort"
"git.dayanhub.com/sagi/envoid/internal/common"
"git.dayanhub.com/sagi/envoid/internal/config"
intErrors "git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"git.dayanhub.com/sagi/envoid/internal/variables"
"golang.org/x/crypto/scrypt"
"golang.org/x/sync/errgroup"
)
type datastore struct {
db *db
}
func NewDataStore() (*datastore, error) {
db, err := newDB()
if err != nil {
return nil, err
}
return &datastore{
db: db,
}, nil
}
func (d *datastore) CreateEnv(name string) error {
table_name := envNameToTableName(name)
return d.db.createTableIfNotExists(table_name)
}
func (d *datastore) DoesFileExists() bool {
pwd := config.GetConfig().PWD
filePath := fmt.Sprintf("%s/%s", pwd, variables.DBFileName)
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
func (d *datastore) ListEnvironments() ([]string, error) {
return d.db.listTables()
}
func (d *datastore) CreateEnvOffExsisting(new_env string, base_env string) error {
table_name_new := envNameToTableName(new_env)
table_name_base := envNameToTableName(base_env)
err := d.CreateEnv(new_env)
if err != nil {
return err
}
err = d.db.copyContentFromTo(table_name_base, table_name_new)
if err != nil {
return err
}
return nil
}
func (d *datastore) Close() error {
return d.db.close()
}
func (d *datastore) SetValue(key string, value string, encrypted *bool, envs []*types.Environment) error {
if encrypted == nil {
encrypted = common.BoolP(false)
}
if *encrypted {
v, err := enc(value)
if err != nil {
return err
}
value = *v
}
for _, env := range envs {
table_name := envNameToTableName(env.Name)
if err := d.db.setVar(table_name, key, value, *encrypted); err != nil {
return err
}
}
return nil
}
func (d *datastore) GetAll(envName string) ([]*types.EnvVar, error) {
table_name := envNameToTableName(envName)
vars, err := d.db.getAll(table_name)
if err != nil {
return vars, err
}
g := new(errgroup.Group)
for _, v := range vars {
g.Go(func() error {
if v.Encrypted {
if v.Value, err = dec(v.Value); err != nil {
return &intErrors.InvalidPasswordError{}
}
}
return nil
})
}
if err := g.Wait(); err != nil {
return vars, err
}
sort.SliceStable(vars, func(i, j int) bool {
return vars[i].Key < vars[j].Key
})
return vars, nil
}
func (d *datastore) RemoveVar(key string, envs []*types.Environment) {
for _, env := range envs {
table_name := envNameToTableName(env.Name)
d.db.rmVar(table_name, key)
}
}
func (d *datastore) GetVar(envName string, key string) (*types.EnvVar, error) {
table_name := envNameToTableName(envName)
v, err := d.db.getVar(table_name, key)
if err != nil {
return v, err
}
if v.Encrypted {
if v.Value, err = dec(v.Value); err != nil {
return v, &intErrors.InvalidPasswordError{}
}
}
return v, err
}
func (d *datastore) RemoveEnv(envName string) error {
table_name := envNameToTableName(envName)
return d.db.deleteTable(table_name)
}
func enc(s string) (*string, error) {
conf := config.GetConfig()
proj, _ := conf.GetProject(conf.PWD)
key, salt, err := deriveKey([]byte(proj.Password), nil)
data := []byte(s)
if err != nil {
return nil, err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
ciphertext = append(ciphertext, salt...)
str := string(ciphertext)
return &str, nil
}
func dec(s string) (string, error) {
data := []byte(s)
salt, data := data[len(data)-32:], data[:len(data)-32]
conf := config.GetConfig()
proj, _ := conf.GetProject(conf.PWD)
key, _, err := deriveKey([]byte(proj.Password), salt)
if err != nil {
return "", err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return "", err
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
str := string(plaintext)
return str, nil
}
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
if salt == nil {
salt = make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return nil, nil, err
}
}
key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32)
if err != nil {
return nil, nil, err
}
return key, salt, nil
}

135
internal/datastore/db.go Normal file
View file

@ -0,0 +1,135 @@
package datastore
import (
"fmt"
"database/sql"
"git.dayanhub.com/sagi/envoid/internal/common"
"git.dayanhub.com/sagi/envoid/internal/errors"
"git.dayanhub.com/sagi/envoid/internal/types"
"git.dayanhub.com/sagi/envoid/internal/variables"
_ "github.com/glebarez/go-sqlite"
)
type db struct {
con *sql.DB
}
func newDB() (*db, error) {
con, err := sql.Open("sqlite", variables.DBFileName)
if err != nil {
fmt.Printf("%v\n", err)
return nil, err
}
return &db{
con: con,
}, nil
}
func (d *db) close() error {
return d.con.Close()
}
func (d *db) createTableIfNotExists(table_name string) error {
_, err := d.con.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY NOT NULL, value BLOB NOT NULL, encrypted BOOL NOT NULL);", table_name))
if err != nil {
return err
}
return nil
}
func (d *db) listTables() ([]string, error) {
tables := []string{}
q := "SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%' AND name LIKE ?"
rows, err := d.con.Query(q, fmt.Sprintf("%s_%%", variables.DBTablePrefix))
if err != nil {
return tables, err
}
for rows.Next() {
var table string
if err := rows.Scan(&table); err != nil {
return tables, err
}
tables = append(tables, tableNameToEnvName(table))
}
return tables, nil
}
func (d *db) copyContentFromTo(table_name_target string, table_name_dest string) error {
_, err := d.con.Exec(fmt.Sprintf("INSERT INTO %s (key, value, encrypted) SELECT * FROM %s", table_name_dest, table_name_target))
if err != nil {
return err
}
return nil
}
func (d *db) deleteTable(table_name string) error {
_, err := d.con.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", table_name))
return err
}
func (d *db) getVar(table_name string, key string) (*types.EnvVar, error) {
q := fmt.Sprintf("SELECT * FROM %s WHERE key=?", table_name)
row := d.con.QueryRow(q, key)
envVar := &types.EnvVar{}
err := row.Scan(&envVar.Key, &envVar.Value, &envVar.Encrypted)
if err != nil {
return envVar, errors.NewNoKeyFoundError(key)
}
return envVar, nil
}
func (d *db) getAll(table_name string) ([]*types.EnvVar, error) {
entries := []*types.EnvVar{}
rows, err := d.con.Query(fmt.Sprintf("SELECT * FROM %s", table_name))
if err != nil {
return entries, err
}
for rows.Next() {
key := ""
value := ""
enc := common.BoolP(false)
err := rows.Scan(&key, &value, enc)
if err != nil {
return entries, err
}
e := &types.EnvVar{
Key: key,
Value: value,
Encrypted: *enc,
}
entries = append(entries, e)
}
return entries, nil
}
func (d *db) setVar(table_name string, key string, value string, encrypted bool) error {
q := fmt.Sprintf("INSERT INTO %s (key, value, encrypted) values ( ? , ? , ? ) ON CONFLICT (key) DO UPDATE SET value = ?, encrypted = ?", table_name)
_, err := d.con.Exec(q, key, value, encrypted, value, encrypted)
if err != nil {
fmt.Println(err)
return errors.NewSecretExsistsError(key)
}
return nil
}
func (d *db) rmVar(table_name string, key string) {
q := fmt.Sprintf("DELETE FROM %s WHERE key = ?", table_name)
_, _ = d.con.Exec(q, key)
}
func envNameToTableName(envName string) string {
envSanke := common.StrToSnakeCase(envName)
return fmt.Sprintf("%s_%s", variables.DBTablePrefix, envSanke)
}
func tableNameToEnvName(tableName string) string {
// remove '<prefix>_'
return tableName[len(variables.DBTablePrefix)+1:]
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type EnvironmentExistsError struct {
name string
}
func (e *EnvironmentExistsError) Error() string {
return fmt.Sprintf("environment %s already exists", e.name)
}
func NewEnvironmentExistsError(name string) *EnvironmentExistsError {
return &EnvironmentExistsError{name: name}
}

View file

@ -0,0 +1,16 @@
package errors
import "fmt"
type EnvironmentNotFoundError struct {
path string
name string
}
func (e *EnvironmentNotFoundError) Error() string {
return fmt.Sprintf("No env '%s' definition for path %s. Did you initialize?", e.name, e.path)
}
func NewEnvironmentNotFoundError(path string, name string) *EnvironmentNotFoundError {
return &EnvironmentNotFoundError{path: path, name: name}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type InvalidCommandError struct {
msg string
}
func (e *InvalidCommandError) Error() string {
return fmt.Sprintf("invalid command. %v", e.msg)
}
func NewInvalidCommandError(msg string) *InvalidCommandError {
return &InvalidCommandError{msg: msg}
}

View file

@ -0,0 +1,16 @@
package errors
import "fmt"
type InvalidFlagValueError struct {
flag string
invalidValue string
}
func (e *InvalidFlagValueError) Error() string {
return fmt.Sprintf("invalid value '%s' for flag --%s", e.invalidValue, e.flag)
}
func NewInvalidFlagValueError(flag string, invalidValue string) *InvalidFlagValueError {
return &InvalidFlagValueError{flag: flag, invalidValue: invalidValue}
}

View file

@ -0,0 +1,12 @@
package errors
type InvalidPasswordError struct {
}
func (e *InvalidPasswordError) Error() string {
return "invalid password. is your environment set correctly?"
}
func NewInvalidPasswordError() *InvalidPasswordError {
return &InvalidPasswordError{}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type NoKeyFoundError struct {
key string
}
func (e *NoKeyFoundError) Error() string {
return fmt.Sprintf("key %s not found", e.key)
}
func NewNoKeyFoundError(key string) *NoKeyFoundError {
return &NoKeyFoundError{key: key}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type NoConfigFoundError struct {
path string
}
func (e *NoConfigFoundError) Error() string {
return fmt.Sprintf("no config file found in %s", e.path)
}
func NewNoConfigFoundError(path string) *NoConfigFoundError {
return &NoConfigFoundError{path: path}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type ProjectEmptyError struct {
name string
}
func (e *ProjectEmptyError) Error() string {
return fmt.Sprintf("Project %s does not have any environments.", e.name)
}
func NewProjectEmptyError(name string) *ProjectEmptyError {
return &ProjectEmptyError{name: name}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type ProjectNotFoundError struct {
path string
}
func (e *ProjectNotFoundError) Error() string {
return fmt.Sprintf("No project found for path '%s'. Did you initialize?", e.path)
}
func NewProjectNotFoundError(path string) *ProjectNotFoundError {
return &ProjectNotFoundError{path: path}
}

View file

@ -0,0 +1,15 @@
package errors
import "fmt"
type SecretExistsError struct {
secret string
}
func (e *SecretExistsError) Error() string {
return fmt.Sprintf("secret %s already exists", e.secret)
}
func NewSecretExsistsError(secret string) *SecretExistsError {
return &SecretExistsError{secret: secret}
}

View file

@ -0,0 +1,23 @@
package prompt
import (
"fmt"
"os"
"syscall"
"golang.org/x/term"
)
func PasswordPrompt(label string) string {
var s string
for {
fmt.Fprint(os.Stderr, label+" ")
b, _ := term.ReadPassword(int(syscall.Stdin))
s = string(b)
if s != "" {
break
}
}
fmt.Println()
return s
}

21
internal/prompt/string.go Normal file
View file

@ -0,0 +1,21 @@
package prompt
import (
"bufio"
"fmt"
"os"
"strings"
)
func StringPrompt(label string) string {
var s string
r := bufio.NewReader(os.Stdin)
for {
fmt.Fprint(os.Stderr, label+" ")
s, _ = r.ReadString('\n')
if s != "" {
break
}
}
return strings.TrimSpace(s)
}

View file

@ -0,0 +1,7 @@
package types
type EnvVar struct {
Key string
Value string
Encrypted bool
}

View file

@ -0,0 +1,6 @@
package types
type Environment struct {
Name string `json:"name"`
Password string `json:"-"`
}

58
internal/types/project.go Normal file
View file

@ -0,0 +1,58 @@
package types
import (
"encoding/base64"
"fmt"
"os"
"git.dayanhub.com/sagi/envoid/internal/errors"
)
type Project struct {
Path string `json:"path"`
Name string `json:"name"`
Password string `json:"password"`
Environments []*Environment `json:"envorinments" default:"[]"`
}
func (p *Project) GetEnv(name string) (*Environment, error) {
var e *Environment
for _, env := range p.Environments {
if env.Name == name {
passByte, err := base64.RawStdEncoding.DecodeString(p.Password)
if err != nil {
fmt.Printf("[ERROR] Failed to decode project password @ %s. %e\n", p.Path, err)
os.Exit(1)
}
e = &Environment{
Name: env.Name,
Password: string(passByte),
}
}
}
if e == nil {
return nil, errors.NewEnvironmentNotFoundError(p.Path, name)
}
return e, nil
}
func (p *Project) NewEnv(name string) error {
e := &Environment{
Name: name,
}
p.Environments = append(p.Environments, e)
return nil
}
func (p *Project) RemoveEnv(name string) {
environments := []*Environment{}
for _, env := range p.Environments {
if env.Name != name {
environments = append(environments, env)
}
}
p.Environments = environments
}
func (p *Project) IsEmpty() bool {
return len(p.Environments) == 0
}

View file

@ -0,0 +1,7 @@
package variables
const (
DBFileName = ".envoid"
DBTablePrefix = "envoid"
ConfigFolder = "envoid"
)

View file

@ -0,0 +1,6 @@
package variables
var (
Commit string = "HEAD"
Version string = "development"
)

9
main.go Normal file
View file

@ -0,0 +1,9 @@
package main
import (
"git.dayanhub.com/sagi/envoid/cmd"
)
func main() {
cmd.Execute()
}

21
vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Brian Goff
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

@ -0,0 +1,16 @@
package md2man
import (
"github.com/russross/blackfriday/v2"
)
// Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte {
renderer := NewRoffRenderer()
return blackfriday.Run(doc,
[]blackfriday.Option{
blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(renderer.GetExtensions()),
}...)
}

382
vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go generated vendored Normal file
View file

@ -0,0 +1,382 @@
package md2man
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/russross/blackfriday/v2"
)
// roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text
type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int
firstHeader bool
firstDD bool
listDepth int
}
const (
titleHeader = ".TH "
topLevelHeader = "\n\n.SH "
secondLevelHdr = "\n.SH "
otherHeader = "\n.SS "
crTag = "\n"
emphTag = "\\fI"
emphCloseTag = "\\fP"
strongTag = "\\fB"
strongCloseTag = "\\fP"
breakTag = "\n.br\n"
paraTag = "\n.PP\n"
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
linkTag = "\n\\[la]"
linkCloseTag = "\\[ra]"
codespanTag = "\\fB"
codespanCloseTag = "\\fR"
codeTag = "\n.EX\n"
codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on).
quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n"
listCloseTag = "\n.RE\n"
dtTag = "\n.TP\n"
dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n"
tableCellStart = "T{\n"
tableCellEnd = "\nT}\n"
tablePreprocessor = `'\" t`
)
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint
var extensions blackfriday.Extensions
extensions |= blackfriday.NoIntraEmphasis
extensions |= blackfriday.Tables
extensions |= blackfriday.FencedCode
extensions |= blackfriday.SpaceHeadings
extensions |= blackfriday.Footnotes
extensions |= blackfriday.Titleblock
extensions |= blackfriday.DefinitionLists
return &roffRenderer{
extensions: extensions,
}
}
// GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions
}
// RenderHeader handles outputting the header at document start
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
// We need to walk the tree to check if there are any tables.
// If there are, we need to enable the roff table preprocessor.
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if node.Type == blackfriday.Table {
out(w, tablePreprocessor+"\n")
return blackfriday.Terminate
}
return blackfriday.GoToNext
})
// disable hyphenation
out(w, ".nh\n")
}
// RenderFooter handles outputting the footer at the document end; the roff
// renderer has no footer information
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
}
// RenderNode is called for each node in a markdown document; based on the node
// type the equivalent roff output is sent to the writer
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
walkAction := blackfriday.GoToNext
switch node.Type {
case blackfriday.Text:
escapeSpecialChars(w, node.Literal)
case blackfriday.Softbreak:
out(w, crTag)
case blackfriday.Hardbreak:
out(w, breakTag)
case blackfriday.Emph:
if entering {
out(w, emphTag)
} else {
out(w, emphCloseTag)
}
case blackfriday.Strong:
if entering {
out(w, strongTag)
} else {
out(w, strongCloseTag)
}
case blackfriday.Link:
// Don't render the link text for automatic links, because this
// will only duplicate the URL in the roff output.
// See https://daringfireball.net/projects/markdown/syntax#autolink
if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
out(w, string(node.FirstChild.Literal))
}
// Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
out(w, linkTag+escapedLink+linkCloseTag)
walkAction = blackfriday.SkipChildren
case blackfriday.Image:
// ignore images
walkAction = blackfriday.SkipChildren
case blackfriday.Code:
out(w, codespanTag)
escapeSpecialChars(w, node.Literal)
out(w, codespanCloseTag)
case blackfriday.Document:
break
case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering {
out(w, paraTag)
} else {
out(w, crTag)
}
case blackfriday.BlockQuote:
if entering {
out(w, quoteTag)
} else {
out(w, quoteCloseTag)
}
case blackfriday.Heading:
r.handleHeading(w, node, entering)
case blackfriday.HorizontalRule:
out(w, hruleTag)
case blackfriday.List:
r.handleList(w, node, entering)
case blackfriday.Item:
r.handleItem(w, node, entering)
case blackfriday.CodeBlock:
out(w, codeTag)
escapeSpecialChars(w, node.Literal)
out(w, codeCloseTag)
case blackfriday.Table:
r.handleTable(w, node, entering)
case blackfriday.TableHead:
case blackfriday.TableBody:
case blackfriday.TableRow:
// no action as cell entries do all the nroff formatting
return blackfriday.GoToNext
case blackfriday.TableCell:
r.handleTableCell(w, node, entering)
case blackfriday.HTMLSpan:
// ignore other HTML tags
case blackfriday.HTMLBlock:
if bytes.HasPrefix(node.Literal, []byte("<!--")) {
break // ignore comments, no warning
}
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
default:
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
}
return walkAction
}
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
switch node.Level {
case 1:
if !r.firstHeader {
out(w, titleHeader)
r.firstHeader = true
break
}
out(w, topLevelHeader)
case 2:
out(w, secondLevelHdr)
default:
out(w, otherHeader)
}
}
}
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag
closeTag := listCloseTag
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node
openTag = ""
closeTag = ""
}
if entering {
r.listDepth++
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = append(r.listCounters, 1)
}
out(w, openTag)
} else {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = r.listCounters[:len(r.listCounters)-1]
}
out(w, closeTag)
r.listDepth--
}
}
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
r.listCounters[len(r.listCounters)-1]++
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below).
out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// DD (definition description): line that starts with ": ".
//
// We have to distinguish between the first DD and the
// subsequent ones, as there should be no vertical
// whitespace between the DT and the first DD.
if r.firstDD {
r.firstDD = false
} else {
out(w, dd2Tag)
}
} else {
out(w, ".IP \\(bu 2\n")
}
} else {
out(w, "\n")
}
}
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
out(w, tableStart)
// call walker to count cells (and rows?) so format section can be produced
columns := countColumns(node)
out(w, strings.Repeat("l ", columns)+"\n")
out(w, strings.Repeat("l ", columns)+".\n")
} else {
out(w, tableEnd)
}
}
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
var start string
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
start = "\t"
}
if node.IsHeader {
start += strongTag
} else if nodeLiteralSize(node) > 30 {
start += tableCellStart
}
out(w, start)
} else {
var end string
if node.IsHeader {
end = strongCloseTag
} else if nodeLiteralSize(node) > 30 {
end = tableCellEnd
}
if node.Next == nil && end != tableCellEnd {
// Last cell: need to carriage return if we are at the end of the
// header row and content isn't wrapped in a "tablecell"
end += crTag
}
out(w, end)
}
}
func nodeLiteralSize(node *blackfriday.Node) int {
total := 0
for n := node.FirstChild; n != nil; n = n.FirstChild {
total += len(n.Literal)
}
return total
}
// because roff format requires knowing the column count before outputting any table
// data we need to walk a table tree and count the columns
func countColumns(node *blackfriday.Node) int {
var columns int
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.TableRow:
if !entering {
return blackfriday.Terminate
}
case blackfriday.TableCell:
if entering {
columns++
}
default:
}
return blackfriday.GoToNext
})
return columns
}
func out(w io.Writer, output string) {
io.WriteString(w, output) // nolint: errcheck
}
func escapeSpecialChars(w io.Writer, text []byte) {
scanner := bufio.NewScanner(bytes.NewReader(text))
// count the number of lines in the text
// we need to know this to avoid adding a newline after the last line
n := bytes.Count(text, []byte{'\n'})
idx := 0
for scanner.Scan() {
dt := scanner.Bytes()
if idx < n {
idx++
dt = append(dt, '\n')
}
escapeSpecialCharsLine(w, dt)
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func escapeSpecialCharsLine(w io.Writer, text []byte) {
for i := 0; i < len(text); i++ {
// escape initial apostrophe or period
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
out(w, "\\&")
}
// directly copy normal characters
org := i
for i < len(text) && text[i] != '\\' {
i++
}
if i > org {
w.Write(text[org:i]) // nolint: errcheck
}
// escape a character
if i >= len(text) {
break
}
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
}
}

1
vendor/github.com/creasty/defaults/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
.DS_Store

22
vendor/github.com/creasty/defaults/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2017-present Yuki Iwanaga
MIT License
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.

29
vendor/github.com/creasty/defaults/Makefile generated vendored Normal file
View file

@ -0,0 +1,29 @@
SHELL := /bin/bash -eu -o pipefail
GO_TEST_FLAGS := -v
PACKAGE_DIRS := $(shell go list ./... 2> /dev/null | grep -v /vendor/)
SRC_FILES := $(shell find . -name '*.go' -not -path './vendor/*')
# Tasks
#-----------------------------------------------
.PHONY: lint
lint:
@gofmt -e -d -s $(SRC_FILES) | awk '{ e = 1; print $0 } END { if (e) exit(1) }'
@golangci-lint --disable errcheck,unused run
.PHONY: test
test: lint
@go test $(GO_TEST_FLAGS) $(PACKAGE_DIRS)
.PHONY: ci-test
ci-test: lint
@echo > coverage.txt
@for d in $(PACKAGE_DIRS); do \
go test -coverprofile=profile.out -covermode=atomic -race -v $$d; \
if [ -f profile.out ]; then \
cat profile.out >> coverage.txt; \
rm profile.out; \
fi; \
done

160
vendor/github.com/creasty/defaults/README.md generated vendored Normal file
View file

@ -0,0 +1,160 @@
defaults
========
[![CircleCI](https://circleci.com/gh/creasty/defaults/tree/master.svg?style=svg)](https://circleci.com/gh/creasty/defaults/tree/master)
[![codecov](https://codecov.io/gh/creasty/defaults/branch/master/graph/badge.svg)](https://codecov.io/gh/creasty/defaults)
[![GitHub release](https://img.shields.io/github/release/creasty/defaults.svg)](https://github.com/creasty/defaults/releases)
[![License](https://img.shields.io/github/license/creasty/defaults.svg)](./LICENSE)
Initialize structs with default values
- Supports almost all kind of types
- Scalar types
- `int/8/16/32/64`, `uint/8/16/32/64`, `float32/64`
- `uintptr`, `bool`, `string`
- Complex types
- `map`, `slice`, `struct`
- Nested types
- `map[K1]map[K2]Struct`, `[]map[K1]Struct[]`
- Aliased types
- `time.Duration`
- e.g., `type Enum string`
- Pointer types
- e.g., `*SampleStruct`, `*int`
- Recursively initializes fields in a struct
- Dynamically sets default values by [`defaults.Setter`](./setter.go) interface
- Preserves non-initial values from being reset with a default value
Usage
-----
```go
package main
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/creasty/defaults"
)
type Gender string
type Sample struct {
Name string `default:"John Smith"`
Age int `default:"27"`
Gender Gender `default:"m"`
Working bool `default:"true"`
SliceInt []int `default:"[1, 2, 3]"`
SlicePtr []*int `default:"[1, 2, 3]"`
SliceString []string `default:"[\"a\", \"b\"]"`
MapNull map[string]int `default:"{}"`
Map map[string]int `default:"{\"key1\": 123}"`
MapOfStruct map[string]OtherStruct `default:"{\"Key2\": {\"Foo\":123}}"`
MapOfPtrStruct map[string]*OtherStruct `default:"{\"Key3\": {\"Foo\":123}}"`
MapOfStructWithTag map[string]OtherStruct `default:"{\"Key4\": {\"Foo\":123}}"`
Struct OtherStruct `default:"{\"Foo\": 123}"`
StructPtr *OtherStruct `default:"{\"Foo\": 123}"`
NoTag OtherStruct // Recurses into a nested struct by default
NoOption OtherStruct `default:"-"` // no option
}
type OtherStruct struct {
Hello string `default:"world"` // Tags in a nested struct also work
Foo int `default:"-"`
Random int `default:"-"`
}
// SetDefaults implements defaults.Setter interface
func (s *OtherStruct) SetDefaults() {
if defaults.CanUpdate(s.Random) { // Check if it's a zero value (recommended)
s.Random = rand.Int() // Set a dynamic value
}
}
func main() {
obj := &Sample{}
if err := defaults.Set(obj); err != nil {
panic(err)
}
out, err := json.MarshalIndent(obj, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(out))
// Output:
// {
// "Name": "John Smith",
// "Age": 27,
// "Gender": "m",
// "Working": true,
// "SliceInt": [
// 1,
// 2,
// 3
// ],
// "SlicePtr": [
// 1,
// 2,
// 3
// ],
// "SliceString": [
// "a",
// "b"
// ],
// "MapNull": {},
// "Map": {
// "key1": 123
// },
// "MapOfStruct": {
// "Key2": {
// "Hello": "world",
// "Foo": 123,
// "Random": 5577006791947779410
// }
// },
// "MapOfPtrStruct": {
// "Key3": {
// "Hello": "world",
// "Foo": 123,
// "Random": 8674665223082153551
// }
// },
// "MapOfStructWithTag": {
// "Key4": {
// "Hello": "world",
// "Foo": 123,
// "Random": 6129484611666145821
// }
// },
// "Struct": {
// "Hello": "world",
// "Foo": 123,
// "Random": 4037200794235010051
// },
// "StructPtr": {
// "Hello": "world",
// "Foo": 123,
// "Random": 3916589616287113937
// },
// "NoTag": {
// "Hello": "world",
// "Foo": 0,
// "Random": 6334824724549167320
// },
// "NoOption": {
// "Hello": "",
// "Foo": 0,
// "Random": 0
// }
// }
}
```

244
vendor/github.com/creasty/defaults/defaults.go generated vendored Normal file
View file

@ -0,0 +1,244 @@
package defaults
import (
"encoding"
"encoding/json"
"errors"
"reflect"
"strconv"
"time"
)
var (
errInvalidType = errors.New("not a struct pointer")
)
const (
fieldName = "default"
)
// Set initializes members in a struct referenced by a pointer.
// Maps and slices are initialized by `make` and other primitive types are set with default values.
// `ptr` should be a struct pointer
func Set(ptr interface{}) error {
if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
return errInvalidType
}
v := reflect.ValueOf(ptr).Elem()
t := v.Type()
if t.Kind() != reflect.Struct {
return errInvalidType
}
for i := 0; i < t.NumField(); i++ {
if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" {
if err := setField(v.Field(i), defaultVal); err != nil {
return err
}
}
}
callSetter(ptr)
return nil
}
// MustSet function is a wrapper of Set function
// It will call Set and panic if err not equals nil.
func MustSet(ptr interface{}) {
if err := Set(ptr); err != nil {
panic(err)
}
}
func setField(field reflect.Value, defaultVal string) error {
if !field.CanSet() {
return nil
}
if !shouldInitializeField(field, defaultVal) {
return nil
}
isInitial := isInitialValue(field)
if isInitial {
if unmarshalByInterface(field, defaultVal) {
return nil
}
switch field.Kind() {
case reflect.Bool:
if val, err := strconv.ParseBool(defaultVal); err == nil {
field.Set(reflect.ValueOf(val).Convert(field.Type()))
}
case reflect.Int:
if val, err := strconv.ParseInt(defaultVal, 0, strconv.IntSize); err == nil {
field.Set(reflect.ValueOf(int(val)).Convert(field.Type()))
}
case reflect.Int8:
if val, err := strconv.ParseInt(defaultVal, 0, 8); err == nil {
field.Set(reflect.ValueOf(int8(val)).Convert(field.Type()))
}
case reflect.Int16:
if val, err := strconv.ParseInt(defaultVal, 0, 16); err == nil {
field.Set(reflect.ValueOf(int16(val)).Convert(field.Type()))
}
case reflect.Int32:
if val, err := strconv.ParseInt(defaultVal, 0, 32); err == nil {
field.Set(reflect.ValueOf(int32(val)).Convert(field.Type()))
}
case reflect.Int64:
if val, err := time.ParseDuration(defaultVal); err == nil {
field.Set(reflect.ValueOf(val).Convert(field.Type()))
} else if val, err := strconv.ParseInt(defaultVal, 0, 64); err == nil {
field.Set(reflect.ValueOf(val).Convert(field.Type()))
}
case reflect.Uint:
if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil {
field.Set(reflect.ValueOf(uint(val)).Convert(field.Type()))
}
case reflect.Uint8:
if val, err := strconv.ParseUint(defaultVal, 0, 8); err == nil {
field.Set(reflect.ValueOf(uint8(val)).Convert(field.Type()))
}
case reflect.Uint16:
if val, err := strconv.ParseUint(defaultVal, 0, 16); err == nil {
field.Set(reflect.ValueOf(uint16(val)).Convert(field.Type()))
}
case reflect.Uint32:
if val, err := strconv.ParseUint(defaultVal, 0, 32); err == nil {
field.Set(reflect.ValueOf(uint32(val)).Convert(field.Type()))
}
case reflect.Uint64:
if val, err := strconv.ParseUint(defaultVal, 0, 64); err == nil {
field.Set(reflect.ValueOf(val).Convert(field.Type()))
}
case reflect.Uintptr:
if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil {
field.Set(reflect.ValueOf(uintptr(val)).Convert(field.Type()))
}
case reflect.Float32:
if val, err := strconv.ParseFloat(defaultVal, 32); err == nil {
field.Set(reflect.ValueOf(float32(val)).Convert(field.Type()))
}
case reflect.Float64:
if val, err := strconv.ParseFloat(defaultVal, 64); err == nil {
field.Set(reflect.ValueOf(val).Convert(field.Type()))
}
case reflect.String:
field.Set(reflect.ValueOf(defaultVal).Convert(field.Type()))
case reflect.Slice:
ref := reflect.New(field.Type())
ref.Elem().Set(reflect.MakeSlice(field.Type(), 0, 0))
if defaultVal != "" && defaultVal != "[]" {
if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil {
return err
}
}
field.Set(ref.Elem().Convert(field.Type()))
case reflect.Map:
ref := reflect.New(field.Type())
ref.Elem().Set(reflect.MakeMap(field.Type()))
if defaultVal != "" && defaultVal != "{}" {
if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil {
return err
}
}
field.Set(ref.Elem().Convert(field.Type()))
case reflect.Struct:
if defaultVal != "" && defaultVal != "{}" {
if err := json.Unmarshal([]byte(defaultVal), field.Addr().Interface()); err != nil {
return err
}
}
case reflect.Ptr:
field.Set(reflect.New(field.Type().Elem()))
}
}
switch field.Kind() {
case reflect.Ptr:
if isInitial || field.Elem().Kind() == reflect.Struct {
setField(field.Elem(), defaultVal)
callSetter(field.Interface())
}
case reflect.Struct:
if err := Set(field.Addr().Interface()); err != nil {
return err
}
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
if err := setField(field.Index(j), ""); err != nil {
return err
}
}
case reflect.Map:
for _, e := range field.MapKeys() {
var v = field.MapIndex(e)
switch v.Kind() {
case reflect.Ptr:
switch v.Elem().Kind() {
case reflect.Struct, reflect.Slice, reflect.Map:
if err := setField(v.Elem(), ""); err != nil {
return err
}
}
case reflect.Struct, reflect.Slice, reflect.Map:
ref := reflect.New(v.Type())
ref.Elem().Set(v)
if err := setField(ref.Elem(), ""); err != nil {
return err
}
field.SetMapIndex(e, ref.Elem().Convert(v.Type()))
}
}
}
return nil
}
func unmarshalByInterface(field reflect.Value, defaultVal string) bool {
asText, ok := field.Addr().Interface().(encoding.TextUnmarshaler)
if ok && defaultVal != "" {
// if field implements encode.TextUnmarshaler, try to use it before decode by kind
if err := asText.UnmarshalText([]byte(defaultVal)); err == nil {
return true
}
}
asJSON, ok := field.Addr().Interface().(json.Unmarshaler)
if ok && defaultVal != "" && defaultVal != "{}" && defaultVal != "[]" {
// if field implements json.Unmarshaler, try to use it before decode by kind
if err := asJSON.UnmarshalJSON([]byte(defaultVal)); err == nil {
return true
}
}
return false
}
func isInitialValue(field reflect.Value) bool {
return reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface())
}
func shouldInitializeField(field reflect.Value, tag string) bool {
switch field.Kind() {
case reflect.Struct:
return true
case reflect.Ptr:
if !field.IsNil() && field.Elem().Kind() == reflect.Struct {
return true
}
case reflect.Slice:
return field.Len() > 0 || tag != ""
case reflect.Map:
return field.Len() > 0 || tag != ""
}
return tag != ""
}
// CanUpdate returns true when the given value is an initial value of its type
func CanUpdate(v interface{}) bool {
return isInitialValue(reflect.ValueOf(v))
}

12
vendor/github.com/creasty/defaults/setter.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
package defaults
// Setter is an interface for setting default values
type Setter interface {
SetDefaults()
}
func callSetter(v interface{}) {
if ds, ok := v.(Setter); ok {
ds.SetDefaults()
}
}

21
vendor/github.com/dustin/go-humanize/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,21 @@
sudo: false
language: go
go_import_path: github.com/dustin/go-humanize
go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x
- stable
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- diff -u <(echo -n) <(gofmt -d -s .)
- go vet .
- go install -v -race ./...
- go test -v -race ./...

21
vendor/github.com/dustin/go-humanize/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
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.
<http://www.opensource.org/licenses/mit-license.php>

124
vendor/github.com/dustin/go-humanize/README.markdown generated vendored Normal file
View file

@ -0,0 +1,124 @@
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
Just a few functions for helping humanize times and sizes.
`go get` it as `github.com/dustin/go-humanize`, import it as
`"github.com/dustin/go-humanize"`, use it as `humanize`.
See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for
complete documentation.
## Sizes
This lets you take numbers like `82854982` and convert them to useful
strings like, `83 MB` or `79 MiB` (whichever you prefer).
Example:
```go
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
```
## Times
This lets you take a `time.Time` and spit it out in relative terms.
For example, `12 seconds ago` or `3 days from now`.
Example:
```go
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
```
Thanks to Kyle Lemons for the time implementation from an IRC
conversation one day. It's pretty neat.
## Ordinals
From a [mailing list discussion][odisc] where a user wanted to be able
to label ordinals.
0 -> 0th
1 -> 1st
2 -> 2nd
3 -> 3rd
4 -> 4th
[...]
Example:
```go
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
```
## Commas
Want to shove commas into numbers? Be my guest.
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Example:
```go
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
```
## Ftoa
Nicer float64 formatter that removes trailing zeros.
```go
fmt.Printf("%f", 2.24) // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0) // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
```
## SI notation
Format numbers with [SI notation][sinotation].
Example:
```go
humanize.SI(0.00000000223, "M") // 2.23 nM
```
## English-specific functions
The following functions are in the `humanize/english` subpackage.
### Plurals
Simple English pluralization
```go
english.PluralWord(1, "object", "") // object
english.PluralWord(42, "object", "") // objects
english.PluralWord(2, "bus", "") // buses
english.PluralWord(99, "locus", "loci") // loci
english.Plural(1, "object", "") // 1 object
english.Plural(42, "object", "") // 42 objects
english.Plural(2, "bus", "") // 2 buses
english.Plural(99, "locus", "loci") // 99 loci
```
### Word series
Format comma-separated words lists with conjuctions:
```go
english.WordSeries([]string{"foo"}, "and") // foo
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
```
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix

31
vendor/github.com/dustin/go-humanize/big.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package humanize
import (
"math/big"
)
// order of magnitude (to a max order)
func oomm(n, b *big.Int, maxmag int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
if mag == maxmag && maxmag >= 0 {
break
}
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}
// total order of magnitude
// (same as above, but with no upper limit)
func oom(n, b *big.Int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}

189
vendor/github.com/dustin/go-humanize/bigbytes.go generated vendored Normal file
View file

@ -0,0 +1,189 @@
package humanize
import (
"fmt"
"math/big"
"strings"
"unicode"
)
var (
bigIECExp = big.NewInt(1024)
// BigByte is one byte in bit.Ints
BigByte = big.NewInt(1)
// BigKiByte is 1,024 bytes in bit.Ints
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
// BigMiByte is 1,024 k bytes in bit.Ints
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
// BigGiByte is 1,024 m bytes in bit.Ints
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
// BigTiByte is 1,024 g bytes in bit.Ints
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
// BigPiByte is 1,024 t bytes in bit.Ints
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
// BigEiByte is 1,024 p bytes in bit.Ints
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
// BigZiByte is 1,024 e bytes in bit.Ints
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
// BigYiByte is 1,024 z bytes in bit.Ints
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
// BigRiByte is 1,024 y bytes in bit.Ints
BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp)
// BigQiByte is 1,024 r bytes in bit.Ints
BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp)
)
var (
bigSIExp = big.NewInt(1000)
// BigSIByte is one SI byte in big.Ints
BigSIByte = big.NewInt(1)
// BigKByte is 1,000 SI bytes in big.Ints
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
// BigMByte is 1,000 SI k bytes in big.Ints
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
// BigGByte is 1,000 SI m bytes in big.Ints
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
// BigTByte is 1,000 SI g bytes in big.Ints
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
// BigPByte is 1,000 SI t bytes in big.Ints
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
// BigEByte is 1,000 SI p bytes in big.Ints
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
// BigZByte is 1,000 SI e bytes in big.Ints
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
// BigYByte is 1,000 SI z bytes in big.Ints
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
// BigRByte is 1,000 SI y bytes in big.Ints
BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp)
// BigQByte is 1,000 SI r bytes in big.Ints
BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp)
)
var bigBytesSizeTable = map[string]*big.Int{
"b": BigByte,
"kib": BigKiByte,
"kb": BigKByte,
"mib": BigMiByte,
"mb": BigMByte,
"gib": BigGiByte,
"gb": BigGByte,
"tib": BigTiByte,
"tb": BigTByte,
"pib": BigPiByte,
"pb": BigPByte,
"eib": BigEiByte,
"eb": BigEByte,
"zib": BigZiByte,
"zb": BigZByte,
"yib": BigYiByte,
"yb": BigYByte,
"rib": BigRiByte,
"rb": BigRByte,
"qib": BigQiByte,
"qb": BigQByte,
// Without suffix
"": BigByte,
"ki": BigKiByte,
"k": BigKByte,
"mi": BigMiByte,
"m": BigMByte,
"gi": BigGiByte,
"g": BigGByte,
"ti": BigTiByte,
"t": BigTByte,
"pi": BigPiByte,
"p": BigPByte,
"ei": BigEiByte,
"e": BigEByte,
"z": BigZByte,
"zi": BigZiByte,
"y": BigYByte,
"yi": BigYiByte,
"r": BigRByte,
"ri": BigRiByte,
"q": BigQByte,
"qi": BigQiByte,
}
var ten = big.NewInt(10)
func humanateBigBytes(s, base *big.Int, sizes []string) string {
if s.Cmp(ten) < 0 {
return fmt.Sprintf("%d B", s)
}
c := (&big.Int{}).Set(s)
val, mag := oomm(c, base, len(sizes)-1)
suffix := sizes[mag]
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// BigBytes produces a human readable representation of an SI size.
//
// See also: ParseBigBytes.
//
// BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"}
return humanateBigBytes(s, bigSIExp, sizes)
}
// BigIBytes produces a human readable representation of an IEC size.
//
// See also: ParseBigBytes.
//
// BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"}
return humanateBigBytes(s, bigIECExp, sizes)
}
// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See also: BigBytes, BigIBytes.
//
// ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
val := &big.Rat{}
_, err := fmt.Sscanf(num, "%f", val)
if err != nil {
return nil, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bigBytesSizeTable[extra]; ok {
mv := (&big.Rat{}).SetInt(m)
val.Mul(val, mv)
rv := &big.Int{}
rv.Div(val.Num(), val.Denom())
return rv, nil
}
return nil, fmt.Errorf("unhandled size name: %v", extra)
}

143
vendor/github.com/dustin/go-humanize/bytes.go generated vendored Normal file
View file

@ -0,0 +1,143 @@
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// Bytes produces a human readable representation of an SI size.
//
// See also: ParseBytes.
//
// Bytes(82854982) -> 83 MB
func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes)
}
// IBytes produces a human readable representation of an IEC size.
//
// See also: ParseBytes.
//
// IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes)
}
// ParseBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See Also: Bytes, IBytes.
//
// ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bytesSizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}

116
vendor/github.com/dustin/go-humanize/comma.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
package humanize
import (
"bytes"
"math"
"math/big"
"strconv"
"strings"
)
// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 {
sign = "-"
v = 0 - v
}
parts := []string{"", "", "", "", "", "", ""}
j := len(parts) - 1
for v > 999 {
parts[j] = strconv.FormatInt(v%1000, 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
v = v / 1000
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:], ",")
}
// Commaf produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Commaf(834142.32) -> 834,142.32
func Commaf(v float64) string {
buf := &bytes.Buffer{}
if v < 0 {
buf.Write([]byte{'-'})
v = 0 - v
}
comma := []byte{','}
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
// CommafWithDigits works like the Commaf but limits the resulting
// string to the given number of decimal places.
//
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
func CommafWithDigits(f float64, decimals int) string {
return stripTrailingDigits(Commaf(f), decimals)
}
// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
sign := ""
if b.Sign() < 0 {
sign = "-"
b.Abs(b)
}
athousand := big.NewInt(1000)
c := (&big.Int{}).Set(b)
_, m := oom(c, athousand)
parts := make([]string, m+1)
j := len(parts) - 1
mod := &big.Int{}
for b.Cmp(athousand) >= 0 {
b.DivMod(b, athousand, mod)
parts[j] = strconv.FormatInt(mod.Int64(), 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
j--
}
parts[j] = strconv.Itoa(int(b.Int64()))
return sign + strings.Join(parts[j:], ",")
}

41
vendor/github.com/dustin/go-humanize/commaf.go generated vendored Normal file
View file

@ -0,0 +1,41 @@
//go:build go1.6
// +build go1.6
package humanize
import (
"bytes"
"math/big"
"strings"
)
// BigCommaf produces a string form of the given big.Float in base 10
// with commas after every three orders of magnitude.
func BigCommaf(v *big.Float) string {
buf := &bytes.Buffer{}
if v.Sign() < 0 {
buf.Write([]byte{'-'})
v.Abs(v)
}
comma := []byte{','}
parts := strings.Split(v.Text('f', -1), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}

49
vendor/github.com/dustin/go-humanize/ftoa.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package humanize
import (
"strconv"
"strings"
)
func stripTrailingZeros(s string) string {
if !strings.ContainsRune(s, '.') {
return s
}
offset := len(s) - 1
for offset > 0 {
if s[offset] == '.' {
offset--
break
}
if s[offset] != '0' {
break
}
offset--
}
return s[:offset+1]
}
func stripTrailingDigits(s string, digits int) string {
if i := strings.Index(s, "."); i >= 0 {
if digits <= 0 {
return s[:i]
}
i++
if i+digits >= len(s) {
return s
}
return s[:i+digits]
}
return s
}
// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
}
// FtoaWithDigits converts a float to a string but limits the resulting string
// to the given number of decimal places, and no trailing zeros.
func FtoaWithDigits(num float64, digits int) string {
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
}

8
vendor/github.com/dustin/go-humanize/humanize.go generated vendored Normal file
View file

@ -0,0 +1,8 @@
/*
Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83 MB" or
"79 MiB" (whichever you prefer).
*/
package humanize

192
vendor/github.com/dustin/go-humanize/number.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
package humanize
/*
Slightly adapted from the source to fit go-humanize.
Author: https://github.com/gorhill
Source: https://gist.github.com/gorhill/5285193
*/
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < (0.0 - math.MaxFloat64) {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}

25
vendor/github.com/dustin/go-humanize/ordinals.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
package humanize
import "strconv"
// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
suffix := "th"
switch x % 10 {
case 1:
if x%100 != 11 {
suffix = "st"
}
case 2:
if x%100 != 12 {
suffix = "nd"
}
case 3:
if x%100 != 13 {
suffix = "rd"
}
}
return strconv.Itoa(x) + suffix
}

127
vendor/github.com/dustin/go-humanize/si.go generated vendored Normal file
View file

@ -0,0 +1,127 @@
package humanize
import (
"errors"
"math"
"regexp"
"strconv"
)
var siPrefixTable = map[float64]string{
-30: "q", // quecto
-27: "r", // ronto
-24: "y", // yocto
-21: "z", // zepto
-18: "a", // atto
-15: "f", // femto
-12: "p", // pico
-9: "n", // nano
-6: "µ", // micro
-3: "m", // milli
0: "",
3: "k", // kilo
6: "M", // mega
9: "G", // giga
12: "T", // tera
15: "P", // peta
18: "E", // exa
21: "Z", // zetta
24: "Y", // yotta
27: "R", // ronna
30: "Q", // quetta
}
var revSIPrefixTable = revfmap(siPrefixTable)
// revfmap reverses the map and precomputes the power multiplier
func revfmap(in map[float64]string) map[string]float64 {
rv := map[string]float64{}
for k, v := range in {
rv[v] = math.Pow(10, k)
}
return rv
}
var riParseRegex *regexp.Regexp
func init() {
ri := `^([\-0-9.]+)\s?([`
for _, v := range siPrefixTable {
ri += v
}
ri += `]?)(.*)`
riParseRegex = regexp.MustCompile(ri)
}
// ComputeSI finds the most appropriate SI prefix for the given number
// and returns the prefix along with the value adjusted to be within
// that prefix.
//
// See also: SI, ParseSI.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
if input == 0 {
return 0, ""
}
mag := math.Abs(input)
exponent := math.Floor(logn(mag, 10))
exponent = math.Floor(exponent/3) * 3
value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0
// Should return 1 M instead of 1000 k
if value == 1000.0 {
exponent += 3
value = mag / math.Pow(10, exponent)
}
value = math.Copysign(value, input)
prefix := siPrefixTable[exponent]
return value, prefix
}
// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// See also: ComputeSI, ParseSI.
//
// e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string {
value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit
}
// SIWithDigits works like SI but limits the resulting string to the
// given number of decimal places.
//
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
func SIWithDigits(input float64, decimals int, unit string) string {
value, prefix := ComputeSI(input)
return FtoaWithDigits(value, decimals) + " " + prefix + unit
}
var errInvalid = errors.New("invalid input")
// ParseSI parses an SI string back into the number and unit.
//
// See also: SI, ComputeSI.
//
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 {
return 0, "", errInvalid
}
mag := revSIPrefixTable[found[2]]
unit := found[3]
base, err := strconv.ParseFloat(found[1], 64)
return base * mag, unit, err
}

117
vendor/github.com/dustin/go-humanize/times.go generated vendored Normal file
View file

@ -0,0 +1,117 @@
package humanize
import (
"fmt"
"math"
"sort"
"time"
)
// Seconds-based time units
const (
Day = 24 * time.Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
LongTime = 37 * Year
)
// Time formats a time into a relative string.
//
// Time(someT) -> "3 weeks ago"
func Time(then time.Time) string {
return RelTime(then, time.Now(), "ago", "from now")
}
// A RelTimeMagnitude struct contains a relative time point at which
// the relative format of time will switch to a new format string. A
// slice of these in ascending order by their "D" field is passed to
// CustomRelTime to format durations.
//
// The Format field is a string that may contain a "%s" which will be
// replaced with the appropriate signed label (e.g. "ago" or "from
// now") and a "%d" that will be replaced by the quantity.
//
// The DivBy field is the amount of time the time difference must be
// divided by in order to display correctly.
//
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
// DivBy should be time.Minute so whatever the duration is will be
// expressed in minutes.
type RelTimeMagnitude struct {
D time.Duration
Format string
DivBy time.Duration
}
var defaultMagnitudes = []RelTimeMagnitude{
{time.Second, "now", time.Second},
{2 * time.Second, "1 second %s", 1},
{time.Minute, "%d seconds %s", time.Second},
{2 * time.Minute, "1 minute %s", 1},
{time.Hour, "%d minutes %s", time.Minute},
{2 * time.Hour, "1 hour %s", 1},
{Day, "%d hours %s", time.Hour},
{2 * Day, "1 day %s", 1},
{Week, "%d days %s", Day},
{2 * Week, "1 week %s", 1},
{Month, "%d weeks %s", Week},
{2 * Month, "1 month %s", 1},
{Year, "%d months %s", Month},
{18 * Month, "1 year %s", 1},
{2 * Year, "2 years %s", 1},
{LongTime, "%d years %s", Year},
{math.MaxInt64, "a long while %s", 1},
}
// RelTime formats a time into a relative string.
//
// It takes two times and two labels. In addition to the generic time
// delta string (e.g. 5 minutes), the labels are used applied so that
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
}
// CustomRelTime formats a time into a relative string.
//
// It takes two times two labels and a table of relative time formats.
// In addition to the generic time delta string (e.g. 5 minutes), the
// labels are used applied so that the label corresponding to the
// smaller time is applied.
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
lbl := albl
diff := b.Sub(a)
if a.After(b) {
lbl = blbl
diff = a.Sub(b)
}
n := sort.Search(len(magnitudes), func(i int) bool {
return magnitudes[i].D > diff
})
if n >= len(magnitudes) {
n = len(magnitudes) - 1
}
mag := magnitudes[n]
args := []interface{}{}
escaped := false
for _, ch := range mag.Format {
if escaped {
switch ch {
case 's':
args = append(args, lbl)
case 'd':
args = append(args, diff/mag.DivBy)
}
escaped = false
} else {
escaped = ch == '%'
}
}
return fmt.Sprintf(mag.Format, args...)
}

23
vendor/github.com/glebarez/go-sqlite/AUTHORS generated vendored Normal file
View file

@ -0,0 +1,23 @@
# This file lists authors for copyright purposes. This file is distinct from
# the CONTRIBUTORS files. See the latter for an explanation.
#
# Names should be added to this file as:
# Name or Organization <email address>
#
# The email address is not required for organizations.
#
# Please keep the list sorted.
Artyom Pervukhin <github@artyom.dev>
Dan Peterson <danp@danp.net>
David Walton <david@davidwalton.com>
Davsk Ltd Co <skinner.david@gmail.com>
Jaap Aarts <jaap.aarts1@gmail.com>
Jan Mercl <0xjnml@gmail.com>
Josh Bleecher Snyder <josharian@gmail.com>
Logan Snow <logansnow@protonmail.com>
Michael Hoffmann <mhoffm@posteo.de>
Ross Light <ross@zombiezen.com>
Saed SayedAhmed <saadmtsa@gmail.com>
Steffen Butzer <steffen(dot)butzer@outlook.com>
Michael Rykov <mrykov@gmail.com>

28
vendor/github.com/glebarez/go-sqlite/CONTRIBUTORS generated vendored Normal file
View file

@ -0,0 +1,28 @@
# This file lists people who contributed code to this repository. The AUTHORS
# file lists the copyright holders; this file lists people.
#
# Names should be added to this file like so:
# Name <email address>
#
# Please keep the list sorted.
Alexander Menzhinsky <amenzhinsky@gmail.com>
Artyom Pervukhin <github@artyom.dev>
Dan Peterson <danp@danp.net>
David Skinner <skinner.david@gmail.com>
David Walton <david@davidwalton.com>
Elle Mouton <elle.mouton@gmail.com>
FlyingOnion <731677080@qq.com>
Gleb Sakhnov <gleb.sakhnov@gmail.com>
Jaap Aarts <jaap.aarts1@gmail.com>
Jan Mercl <0xjnml@gmail.com>
Josh Bleecher Snyder <josharian@gmail.com>
Logan Snow <logansnow@protonmail.com>
Matthew Gabeler-Lee <fastcat@gmail.com>
Michael Hoffmann <mhoffm@posteo.de>
Ross Light <ross@zombiezen.com>
Saed SayedAhmed <saadmtsa@gmail.com>
Steffen Butzer <steffen(dot)butzer@outlook.com>
Yaacov Akiba Slama <ya@slamail.org>
Saed SayedAhmed <saadmtsa@gmail.com>
Michael Rykov <mrykov@gmail.com>

26
vendor/github.com/glebarez/go-sqlite/LICENSE generated vendored Normal file
View file

@ -0,0 +1,26 @@
Copyright (c) 2017 The Sqlite Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

45
vendor/github.com/glebarez/go-sqlite/README.md generated vendored Normal file
View file

@ -0,0 +1,45 @@
[![Tests](https://github.com/glebarez/go-sqlite/actions/workflows/tests.yml/badge.svg)](https://github.com/glebarez/go-sqlite/actions/workflows/tests.yml)
![badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/glebarez/0fd7561eb29baf31d5362ffee1ae1702/raw/badge-sqlite-version-with-date.json)
# go-sqlite
This is a pure-Go SQLite driver for Golang's native [database/sql](https://pkg.go.dev/database/sql) package.
The driver has [Go-based implementation of SQLite](https://gitlab.com/cznic/sqlite) embedded in itself (so, you don't need to install SQLite separately)
# Usage
## Example
```go
package main
import (
"database/sql"
"log"
_ "github.com/glebarez/go-sqlite"
)
func main() {
// connect
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
log.Fatal(err)
}
// get SQLite version
_ := db.QueryRow("select sqlite_version()")
}
```
## Connection string examples
- in-memory SQLite: ```":memory:"```
- on-disk SQLite: ```"path/to/some.db"```
- Foreign-key constraint activation: ```":memory:?_pragma=foreign_keys(1)"```
## Settings PRAGMAs in connection string
Any SQLIte pragma can be preset for a Database connection using ```_pragma``` query parameter. Examples:
- [journal mode](https://www.sqlite.org/pragma.html#pragma_journal_mode): ```path/to/some.db?_pragma=journal_mode(WAL)```
- [busy timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout): ```:memory:?_pragma=busy_timeout(5000)```
Multiple PRAGMAs can be specified, e.g.:<br>
```path/to/some.db?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)```

25
vendor/github.com/glebarez/go-sqlite/SQLITE-LICENSE generated vendored Normal file
View file

@ -0,0 +1,25 @@
SQLite Is Public Domain
All of the code and documentation in SQLite has been dedicated to the public
domain by the authors. All code authors, and representatives of the companies
they work for, have signed affidavits dedicating their contributions to the
public domain and originals of those signed affidavits are stored in a firesafe
at the main offices of Hwaci. Anyone is free to copy, modify, publish, use,
compile, sell, or distribute the original SQLite code, either in source code
form or as a compiled binary, for any purpose, commercial or non-commercial,
and by any means.
The previous paragraph applies to the deliverable code and documentation in
SQLite - those parts of the SQLite library that you actually bundle and ship
with a larger application. Some scripts used as part of the build process (for
example the "configure" scripts generated by autoconf) might fall under other
open-source licenses. Nothing from these build scripts ever reaches the final
deliverable SQLite library, however, and so the licenses associated with those
scripts should not be a factor in assessing your rights to copy and use the
SQLite library.
All of the deliverable code in SQLite has been written from scratch. No code
has been taken from other projects or from the open internet. Every line of
code can be traced back to its original author, and all of those authors have
public domain dedications on file. So the SQLite code base is clean and is
uncontaminated with licensed code from other projects.

BIN
vendor/github.com/glebarez/go-sqlite/embed.db generated vendored Normal file

Binary file not shown.

BIN
vendor/github.com/glebarez/go-sqlite/embed2.db generated vendored Normal file

Binary file not shown.

23
vendor/github.com/glebarez/go-sqlite/mutex.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2019 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sqlite // import "modernc.org/sqlite"
import (
"sync"
"unsafe"
"modernc.org/libc"
"modernc.org/libc/sys/types"
)
type mutex struct {
sync.Mutex
}
func mutexAlloc(tls *libc.TLS) uintptr {
return libc.Xcalloc(tls, 1, types.Size_t(unsafe.Sizeof(mutex{})))
}
func mutexFree(tls *libc.TLS, m uintptr) { libc.Xfree(tls, m) }

10
vendor/github.com/glebarez/go-sqlite/norlimit.go generated vendored Normal file
View file

@ -0,0 +1,10 @@
// Copyright 2021 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package sqlite // import "modernc.org/sqlite"
func setMaxOpenFiles(n int) error { return nil }

19
vendor/github.com/glebarez/go-sqlite/rlimit.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2021 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build freebsd
// +build freebsd
package sqlite // import "modernc.org/sqlite"
import (
"golang.org/x/sys/unix"
)
func setMaxOpenFiles(n int64) error {
var rLimit unix.Rlimit
rLimit.Max = n
rLimit.Cur = n
return unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit)
}

19
vendor/github.com/glebarez/go-sqlite/rulimit.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2021 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin || netbsd || openbsd
// +build linux darwin netbsd openbsd
package sqlite // import "modernc.org/sqlite"
import (
"golang.org/x/sys/unix"
)
func setMaxOpenFiles(n int64) error {
var rLimit unix.Rlimit
rLimit.Max = uint64(n)
rLimit.Cur = uint64(n)
return unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit)
}

1747
vendor/github.com/glebarez/go-sqlite/sqlite.go generated vendored Normal file

File diff suppressed because it is too large Load diff

49
vendor/github.com/glebarez/go-sqlite/sqlite_go18.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2017 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.8
// +build go1.8
package sqlite // import "modernc.org/sqlite"
import (
"context"
"database/sql/driver"
)
// Ping implements driver.Pinger
func (c *conn) Ping(ctx context.Context) error {
_, err := c.ExecContext(ctx, "select 1", nil)
return err
}
// BeginTx implements driver.ConnBeginTx
func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
return c.begin(ctx, opts)
}
// PrepareContext implements driver.ConnPrepareContext
func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
return c.prepare(ctx, query)
}
// ExecContext implements driver.ExecerContext
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
return c.exec(ctx, query, args)
}
// QueryContext implements driver.QueryerContext
func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
return c.query(ctx, query, args)
}
// ExecContext implements driver.StmtExecContext
func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
return s.exec(ctx, args)
}
// QueryContext implements driver.StmtQueryContext
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
return s.query(ctx, args)
}

28
vendor/github.com/google/uuid/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,28 @@
# Changelog
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
### Features
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
### Features
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
### Fixes
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
### Bug Fixes
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
## Changelog

26
vendor/github.com/google/uuid/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,26 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Tips
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
Always try to include a test case! If it is not possible or not necessary,
please explain why in the pull request description.
### Releasing
Commits that would precipitate a SemVer change, as described in the Conventional
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
to create a release candidate pull request. Once submitted, `release-please`
will create a release.
For tips on how to work with `release-please`, see its documentation.
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

9
vendor/github.com/google/uuid/CONTRIBUTORS generated vendored Normal file
View file

@ -0,0 +1,9 @@
Paul Borman <borman@google.com>
bmatsuo
shawnps
theory
jboverfelt
dsymonds
cd1
wallclockbuilder
dansouza

27
vendor/github.com/google/uuid/LICENSE generated vendored Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/github.com/google/uuid/README.md generated vendored Normal file
View file

@ -0,0 +1,21 @@
# uuid
The uuid package generates and inspects UUIDs based on
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
```sh
go get github.com/google/uuid
```
###### Documentation
[![Go Reference](https://pkg.go.dev/badge/github.com/google/uuid.svg)](https://pkg.go.dev/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://pkg.go.dev/github.com/google/uuid

80
vendor/github.com/google/uuid/dce.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

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