Explorar el Código

init imports

master
jimzhan hace 9 años
padre
commit
f204dc024c
Se han modificado 5 ficheros con 689 adiciones y 1 borrados
  1. +108
    -1
      README.md
  2. +5
    -0
      deps.go
  3. +134
    -0
      doc.go
  4. +243
    -0
      env.go
  5. +199
    -0
      env_test.go

+ 108
- 1
README.md Ver fichero

@@ -1,2 +1,109 @@
# env
env
===
[![Build Status](https://travis-ci.org/goanywhere/env.svg?branch=master)](https://travis-ci.org/goanywhere/env) [![GoDoc](https://godoc.org/github.com/goanywhere/env?status.svg)](http://godoc.org/github.com/goanywhere/env)

Ease of Accessing Environment Varaibles in Golang

### Installation

```shell
$ go get -v github.com/goanywhere/env
```

### Usage


```shell
PORT=9394
SECRET_KEY=YOURSECRETKEY
```

You can double/single quote string values:

```shell
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----HkVN9…-----END DSA PRIVATE KEY-----"
```

You can use `export` in front of each line just like your shell settings, so that you can `source` the file in your terminal directly:

```shell
export USERNAME=account@goanywhere.io
export PASSWORD=AccountPasswordGoesHere
```

Export these values, you can then access them using env now.

``` go
package main

import (
"github.com/goanywhere/env"
)


func main() {
env.Int("PORT") // 9394
env.String("SECRET_KEY") // YOURSECERTKEY
.......
}
```


`env` also supports custom struct for you to access the reflected values.

``` go
package main

import (
"fmt"
"github.com/goanywhere/env"
)

type Spec struct {
App string
SecretKey string `env:"SECRET_KEY"`
}

func main() {
var spec Spec

env.Set("App", "myapplication")
env.Set("SECRET_KEY", "wGv7ELIx8P8qsUit9OuWw2zwPEF0nXtvjIKZQOioAVuI5GnHSwBAeWZ6l4-SpIPT")
env.Map(&spec)

fmt.Printf("App: %s", spec.App) // output: "App: myapplication"
fmt.Printf("Secret: %s", spec.SecretKey) // output: "Secret: wGv7ELIx8P8qsUit9OuWw2zwPEF0nXtvjIKZQOioAVuI5GnHSwBAeWZ6l4-SpIPT"
}
```

We also includes dotenv supports, simply add the application settings to file `.env` right under the root of your project:

``` text
test1 = value1
test2 = 'value2'
test3 = "value3"
export test4=value4
```

``` go
package main

import (
"fmt"

"github.com/goanywhere/env"
)

func main() {
// Load '.env' from current working directory.
env.Load()

fmt.Printf("<test1: %s>", env.String("test1")) // output: "value1"
fmt.Printf("<test2: %s>", env.String("test2")) // output: "value2"
}
```


### NOTES

Sensitive settings should **ONLY** be accessible on the machines that need access to them. **NEVER** commit them to a repository (even a private one) that is not needed by every development machine and server.

+ 5
- 0
deps.go Ver fichero

@@ -0,0 +1,5 @@
package env

import (
_ "github.com/smartystreets/goconvey/convey"
)

+ 134
- 0
doc.go Ver fichero

@@ -0,0 +1,134 @@
package env

/*
env
===

Ease of Accessing Environment Varaibles.

## Installation

```shell
$ go get -v github.com/goanywhere/env
```

## Usage

Add the application settings to file `.env` right under the root of your project:

```shell
MY_SECRET_KEY=YOURSECRETKEY
Case_Will_Be_IgNoreD=YOURSECRETKEYGOESHERE
```

You can double/single quote string values:

```shell
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----HkVN9…-----END DSA PRIVATE KEY-----"
```

You can use `export` in front of each line just like your shell settings, so that you can `source` the file in your terminal directly:

```shell
export USERNAME=account@goanywhere.io
export PASSWORD=AccountPasswordGoesHere
```

All set now, you are good to Go :-)

``` go
package main

import (
"github.com/goanywhere/env"
"github.com/goanywhere/rex"
)

func index (ctx *rex.Context) {
ctx.HTML("index.html")
}

func main() {
// Override default 5000 port here.
env.Set("port", "9394")

app := rex.New()
app.Get("/", index)
app.Serve()
}
```

You will now have the HTTP server running on `0.0.0.0:9394`.


`env` supports namespace (case-insensitive, same as the key).

``` go
import (
"fmt"
"github.com/goanywhere/env"
)

func main() {
env.Set("debug", "false", "production")

fmt.Printf("debug: %s", env.Get("debug", "production"))
}
```

`env` also supports custom struct for you to access the reflected values (the key is case-insensitive).

``` go
package main

import (
"fmt"
"github.com/goanywhere/env"
)

type Spec struct {
App string
}

func main() {
var spec Spec

env.Set("app", "myapplication")
env.Map(&spec)

fmt.Printf("App: %s", spec.App) // output: "App: myapplication"
}
```

We also includes dotenv supports:

``` text
test1 = value1
test2 = 'value2'
test3 = "value3"
export test4=value4
```

``` go
package main

import (
"fmt"

"github.com/goanywhere/env"
)

func main() {
// Load '.env' from current working directory.
env.Load(".env")

fmt.Printf("<test: %s>", env.Get("test")) // output: "value"
fmt.Printf("<test2: %s>", env.Get("test2")) // output: "value2"
}
```


## NOTES

Sensitive settings should **ONLY** be accessible on the machines that need access to them. **NEVER** commit them to a repository (even a private one) that is not needed by every development machine and server.
*/

+ 243
- 0
env.go Ver fichero

@@ -0,0 +1,243 @@
package env

import (
"bufio"
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"

log "github.com/Sirupsen/logrus"
"github.com/goanywhere/x/fs"
)

const tag string = "env"

var pattern = regexp.MustCompile(`(export\s*)?(?P<key>\w+)\s*(=)\s*(?P<value>("|')?[\w\s-$,:/\.\+]+)("|')?`)

// findKeyValue finds '=' separated key/value pair from the given string.
func findKeyValue(line string) (key, value string) {
// Intentionally insert a linebreak here to avoid non-stop characters on [[:graph:]].
line = fmt.Sprintf("%s\n", line)
match := pattern.FindStringSubmatch(line)
if len(match) != 0 {
result := make(map[string]string)
for index, name := range pattern.SubexpNames() {
result[name] = match[index]
}
// preserve those quoted spaces for value.
key, value = result["key"], strings.TrimSpace(result["value"])
value = strings.Trim(value, "'")
value = strings.Trim(value, "\"")
}
return
}

// Load parses & set the values in the given files into os environment.
func Load(dotenv string) {
if fs.Exists(dotenv) {
dotenv, _ = filepath.Abs(dotenv)

if file, err := os.Open(dotenv); err == nil {
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
k, v := findKeyValue(scanner.Text())
if k != "" && v != "" {
Set(k, v)
} else {
continue
}
}
}
}
}

// Get retrieves the string value of the environment variable named by the key.
// It returns the value, which will be empty if the variable is not present.
func Get(key string) (value string, exists bool) {
if v := os.Getenv(key); v != "" {
value, exists = v, true
}
return
}

// Set stores the value of the environment variable named by the key. It returns an error, if any.
func Set(key string, value interface{}) error {
var sv string

switch T := value.(type) {
case bool:
sv = strconv.FormatBool(T)

case float32, float64:
sv = strconv.FormatFloat(reflect.ValueOf(value).Float(), 'g', -1, 64)

case int, int8, int16, int32, int64:
sv = strconv.FormatInt(reflect.ValueOf(value).Int(), 10)

case uint, uint8, uint16, uint32, uint64:
sv = strconv.FormatUint(reflect.ValueOf(value).Uint(), 10)

case string:
sv = value.(string)

case []string:
sv = strings.Join(value.([]string), ",")

default:
return fmt.Errorf("Unsupported type: %v", T)
}
return os.Setenv(key, sv)
}

// String retrieves the string value from environment named by the key.
func String(key string, fallback ...string) (value string) {
if str, exists := Get(key); exists {
value = str
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Strings retrieves the string values separated by comma from the environment.
func Strings(key string, fallback ...[]string) (value []string) {
if v, exists := Get(key); exists {
for _, item := range strings.Split(strings.TrimSpace(v), ",") {
value = append(value, strings.TrimSpace(item))
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Int retrieves the integer values separated by comma from the environment.
func Int(key string, fallback ...int) (value int) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseInt(str, 10, 0); e == nil {
value = int(v)
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Int retrieves the 64-bit integer values separated by comma from the environment.
func Int64(key string, fallback ...int64) (value int64) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseInt(str, 10, 64); e == nil {
value = v
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Uint retrieves the unsigned integer values separated by comma from the environment.
func Uint(key string, fallback ...uint) (value uint) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseUint(str, 10, 0); e == nil {
value = uint(v)
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Uint64 retrieves the 64-bit unsigned integer values separated by comma from the environment.
func Uint64(key string, fallback ...uint64) (value uint64) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseUint(str, 10, 64); e == nil {
value = v
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Bool retrieves boolean value from the environment.
func Bool(key string, fallback ...bool) (value bool) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseBool(str); e == nil {
value = v
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Float retrieves float (64) value from the environment.
func Float(key string, fallback ...float64) (value float64) {
if str, exists := Get(key); exists {
if v, e := strconv.ParseFloat(str, 64); e == nil {
value = v
}
} else if len(fallback) > 0 {
value = fallback[0]
}
return
}

// Map fetches the key/value pair from os.Environ into the given spec.
// Tags are supported via `env:ACTUAL_OS_KEY`.
func Map(spec interface{}) error {
value := reflect.ValueOf(spec)
s := value.Elem()

var stype reflect.Type = s.Type()
var field reflect.Value

for index := 0; index < s.NumField(); index++ {
field = s.Field(index)
if field.CanSet() {

key := stype.Field(index).Tag.Get(tag)
if key == "" {
key = stype.Field(index).Name
}

value, exists := Get(key)
if !exists {
continue
}
// converts the environmental value from string to its real type.
// Supports: String | Strings | Bool | Float | Integer | Unsiged Integer
switch field.Kind() {
case reflect.String:
field.SetString(value)

case reflect.Bool:
field.SetBool(Bool(key))

case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
field.SetInt(Int64(key))

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
field.SetUint(Uint64(key))

case reflect.Float32, reflect.Float64:
field.SetFloat(Float(key))

case reflect.Slice:
switch field.Interface().(type) {
case []string:
field.Set(reflect.ValueOf(Strings(key)))

default:
log.Fatalf("Only string slice is supported")
}
}
}
}
return nil
}

+ 199
- 0
env_test.go Ver fichero

@@ -0,0 +1,199 @@
package env

import (
"bufio"
"fmt"
"os"
"testing"

"github.com/goanywhere/x/crypto"
. "github.com/smartystreets/goconvey/convey"
)

func TestFindKeyValue(t *testing.T) {
Convey("env.findKeyValue", t, func() {
k, v := findKeyValue(" test= value")
So(k, ShouldEqual, "test")
So(v, ShouldEqual, "value")

k, v = findKeyValue(" test= value")
So(k, ShouldEqual, "test")
So(v, ShouldEqual, "value")

k, v = findKeyValue("\ttest=\tvalue\t\n")
So(k, ShouldEqual, "test")
So(v, ShouldEqual, "value")

k, v = findKeyValue("export Test=\"Example\"")
So(k, ShouldEqual, "Test")
So(v, ShouldEqual, "Example")

k, v = findKeyValue(`export Secret_Keys=IK-vyX7OuiftwyasT6NXnEYyPMj8fEDssJZdppKOs8Y4hZTtWfUILer73RbsG78Q`)
So(k, ShouldEqual, "Secret_Keys")
So(v, ShouldEqual, "IK-vyX7OuiftwyasT6NXnEYyPMj8fEDssJZdppKOs8Y4hZTtWfUILer73RbsG78Q")
})
}

func TestLoad(t *testing.T) {
filename := "/tmp/.env"
// plain value without quote
if dotenv, err := os.Create(filename); err == nil {
defer dotenv.Close()
defer os.Remove(filename)
secret := crypto.Random(64)
buffer := bufio.NewWriter(dotenv)
buffer.WriteString(fmt.Sprintf("secret=%s\n", secret))
buffer.WriteString("app=myapp\n")
buffer.WriteString("export exportation=myexports")
buffer.Flush()

Convey("env.Load (without quoting)", t, func() {
Set("root", "/tmp")
Load("/tmp/.env")
So(String("secret"), ShouldEqual, secret)
So(String("app"), ShouldEqual, "myapp")
So(String("exportation"), ShouldEqual, "myexports")
})
}
// value with `` quote
if dotenv, err := os.Create(filename); err == nil {
defer dotenv.Close()
defer os.Remove(filename)
secret := crypto.Random(64)
buffer := bufio.NewWriter(dotenv)
buffer.WriteString(fmt.Sprintf("secret='%s'\n", secret))
buffer.WriteString("app='myapp'\n")
buffer.WriteString("export account='username'\n")
buffer.Flush()

Convey("env.Load (with quoting)", t, func() {
Set("root", "/tmp")
Load("/tmp/.env")
So(String("secret"), ShouldEqual, secret)
So(String("app"), ShouldEqual, "myapp")
So(String("account"), ShouldEqual, "username")
})
}
// value with `"` quote
if dotenv, err := os.Create(filename); err == nil {
defer dotenv.Close()
defer os.Remove(filename)
secret := crypto.Random(64)
buffer := bufio.NewWriter(dotenv)
buffer.WriteString(fmt.Sprintf("secret=\"%s\"\n", secret))
buffer.WriteString("app=\"myapp\"\n")
buffer.WriteString("export account=\"username\"\n")
buffer.Flush()

Convey("env.Load (with single quoting)", t, func() {
Set("root", "/tmp")
Load("/tmp/.env")
So(String("secret"), ShouldEqual, secret)
So(String("app"), ShouldEqual, "myapp")
So(String("account"), ShouldEqual, "username")
})
}
}

func TestMap(t *testing.T) {
type Person struct {
Username string
Age uint
Kids int
Checked bool
Money float64
FirstName string `env:"FIRST_NAME"`
Names []string
}
var person Person
Convey("env.Map", t, func() {
Set("Username", "abc")
Set("Age", 100)
Set("Kids", 2)
Set("Checked", true)
Set("Money", 1234567890.0987654321)
Set("FIRST_NAME", "abc")
Set("Names", "a,b,c,d,e,f,g")
Map(&person)

So(person.Username, ShouldEqual, "abc")
So(person.Age, ShouldEqual, 100)
So(person.Kids, ShouldEqual, 2)
So(person.Checked, ShouldBeTrue)
So(person.Money, ShouldEqual, 1234567890.0987654321)
So(person.FirstName, ShouldEqual, "abc")
So(person.Names, ShouldResemble, []string{"a", "b", "c", "d", "e", "f", "g"})
})
}

func TestString(t *testing.T) {
Convey("env.String", t, func() {
So(String("NotFound"), ShouldEqual, "")
Set("Found", "something")
So(String("Found"), ShouldEqual, "something")
So(String("NotFound", "default"), ShouldEqual, "default")
})
}

func TestStrings(t *testing.T) {
Convey("env.Strings", t, func() {
So(Strings("StringList"), ShouldBeNil)
Set("StringList", "a,b,c")
So(Strings("StringList"), ShouldResemble, []string{"a", "b", "c"})
So(Strings("NotFound", []string{"a", "b", "c"}), ShouldResemble, []string{"a", "b", "c"})
})
}

func TestInt(t *testing.T) {
Convey("env.Int", t, func() {
So(Int("integer"), ShouldEqual, 0)
Set("integer", 123)
So(Int("integer"), ShouldEqual, 123)
So(Int("NotFound", 123), ShouldEqual, 123)
})
}

func TestInt64(t *testing.T) {
Convey("env.Int", t, func() {
So(Int64("int64"), ShouldEqual, 0)
Set("int64", 123)
So(Int64("int64"), ShouldEqual, 123)
So(Int64("NotFound", 123), ShouldEqual, 123)
})
}

func TestUint(t *testing.T) {
Convey("env.Uint", t, func() {
So(Uint("uint"), ShouldEqual, 0)
Set("uint", 123)
So(Uint("uint"), ShouldEqual, 123)
So(Uint("NotFound", 123), ShouldEqual, 123)
})
}

func TestUint64(t *testing.T) {
Convey("env.Uint64", t, func() {
So(Uint64("uint64"), ShouldEqual, 0)
Set("uint64", 123)
So(Uint64("uint64"), ShouldEqual, 123)
So(Uint64("NotFound", 123), ShouldEqual, 123)
})
}

func TestBool(t *testing.T) {
Convey("env.Bool", t, func() {
So(Bool("bool"), ShouldBeFalse)
Set("bool", true)
So(Bool("bool"), ShouldBeTrue)
So(Bool("NotFound", true), ShouldBeTrue)
})
}

func TestFloat(t *testing.T) {
Convey("env.Float", t, func() {
So(Float("float64"), ShouldEqual, 0.0)
Set("float64", 12345678990.0987654321)
So(Float("float64"), ShouldEqual, 12345678990.0987654321)
So(Float("NotFound", 12345678990.0987654321), ShouldEqual, 12345678990.0987654321)
})
}

Cargando…
Cancelar
Guardar