envish

package module
v4.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 3, 2021 License: BSD-3-Clause Imports: 6 Imported by: 1

README

Welcome to envish!

envish is a library to help you emulate UNIX-like program environments in Golang packages.

It is released under the 3-clause New BSD license. See LICENSE.md for details.

import envish "github.com/ganbarodigital/go_envish/v4"

env := envish.NewLocalEnv()

// add to this temporary environment
// WITHOUT changing your program's environment
env.Setenv("EXAMPLE_KEY", "EXAMPLE VALUE")

// pass it into run a child process
cmd := exec.Command('example-cmd')
cmd.Env = env.Environ()
cmd.Start()

Why Use Envish

We've built Envish for anyone who needs to emulate a UNIX-like environment in their own Golang packages. Or anyone who just needs a simple key/value store with a familiar API.

We're using it ourselves for our Pipe and Scriptish packages.

Why A Separate Package

Golang's os package provides support for working with your program's environment. But what if you want to make temporary changes to that environment, just to pass environment variables into child processes?

This is a very common pattern used in UNIX shell script programming:

DEBIAN_FRONTEND=noninteractive apt-get install -y mysql

In the example above, the environment variable DEBIAN_FRONTEND is only set for the child process apt-get.

Getting Started

Import Envish into your Golang code:

import envish "github.com/ganbarodigital/go_envish/v4"

Create a copy of your program's environment:

localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

or simply start with an empty environment store:

localVars := envish.NewLocalEnv()

Get and set variables in the environment store as needed:

home := localEnv.Getenv()
localEnv.Setenv("DEBIAN_FRONTEND", "noninteractive")

Functions

func CopyProgramEnv

func CopyProgramEnv(e *LocalEnv)

CopyProgramEnv copies your program's environment into the given environment store.

It replaces any existing variables in the environment store.

func GetKeyFromPair

func GetKeyFromPair(pair string) string

GetKeyFromPair returns the KEY from a string KEY=VALUE.

func GetValueFromPair

func GetValueFromPair(pair string, key string) string

GetValueFromPair returns the VALUE from a string KEY=VALUE.

func LookupHomeDir

func LookupHomeDir(username string) (string, bool)

LookupHomeDir retrieves the given user's home directory, or false if that cannot be found.

It does not use the value of $HOME at all.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// ask the operating system where your home directory is
	homedir, ok := envish.LookupHomeDir("")

	// print the results
	fmt.Printf("ok is %v", ok)
	fmt.Printf("your homedir is: %s", homedir)
}

func SetAsExporter

func SetAsExporter(e *LocalEnv)

SetAsExporter sets a flag so that the EnvStack will include its contents when building an environ to export to Golang's exec package.

Types

type ErrEmptyKey

type ErrEmptyKey struct{ ... }

ErrEmptyKey is returned whenever we're given a key that is zero-length or only contains whitespace

func (ErrEmptyKey) Error

func (e ErrEmptyKey) Error() string

type ErrEmptyOverlayEnv

type ErrEmptyOverlayEnv struct { ... }

ErrEmptyOverlayEnv is returned whenever you call a method on an empty EnvStack

func (ErrEmptyOverlayEnv) Error

func (e ErrEmptyOverlayEnv) Error() string

type ErrNilPointer

type ErrNilPointer struct { ... }

ErrNilPointer is returned whenever you call a method on the Env struct with a nil pointer

func (ErrNilPointer) Error

func (e ErrNilPointer) Error() string

type ErrNoExporterEnv

type ErrNoExporterEnv struct { ... }

func (ErrNoExporterEnv) Error

func (e ErrNoExporterEnv) Error() string

type Expander

type Expander interface { ... }

Expander is the interface that wraps a key/value store that also supports string expansion.

type LocalEnv

type LocalEnv struct { ... }

LocalEnv holds a list key/value pairs.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	localEnv := envish.NewLocalEnv()

	// it starts as an empty environment
	fmt.Print(localEnv.Getenv("$USER"))
}

func NewLocalEnv

func NewLocalEnv(options ...func(*LocalEnv)) *LocalEnv

NewLocalEnv creates an empty environment store.

You can pass functional options into NewLocalEnv to change the environment store before it is returned to you.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	localEnv := envish.NewLocalEnv()

	// it starts as an empty environment
	fmt.Print(localEnv.Getenv("$USER"))
}

WithFunctionalOptions1

CopyProgramEnv is a functional option that will populate the environment store with a copy of your program's environment.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	//
	// it will start with a copy of your program's environment
	localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

	// on UNIX-like systems, this will print the name of the user
	// who is running the program
	fmt.Print(localEnv.Getenv("$USER"))
}

WithFunctionalOptions2

SetAsExporter is a functional option that will tell the OverlayEnv to include your LocalEnv's contents in any call to the OverlayEnv's Environ function.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	//
	// if you add this to an OverlayEnv, the OverlayEnv will include
	// its contents when you call the OverlayEnv's Environ method.
	localEnv := envish.NewLocalEnv(envish.SetAsExporter)

	// this environment is now an exporter
	fmt.Print(localEnv.IsExporter())
}

Output:


true
func (*LocalEnv) Clearenv

func (e *LocalEnv) Clearenv()

Clearenv deletes all entries from the given LocalEnv. The program's environment remains unchanged.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a environment store
	localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

	// empty the environment store completely
	localEnv.Clearenv()
}

func (*LocalEnv) Environ

func (e *LocalEnv) Environ() []string

Environ returns a copy of all entries in the form "key=value". This is compatible with any Golang standard library, such as os/exec.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
	"os/exec"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// get a copy to pass into `os/exec`
	cmd := exec.Command("go", "doc")
	cmd.Env = localEnv.Environ()
}

func (*LocalEnv) Expand

func (e *LocalEnv) Expand(fmt string) string

Expand replaces ${var} or $var in the input string.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the expansion.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// show what we have
	fmt.Print(localEnv.Expand("USER is ${USER}\n"))
}

func (*LocalEnv) Getenv

func (e *LocalEnv) Getenv(key string) string

Getenv returns the value of the variable named by the key.

If the key is not found, an empty string is returned.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// get a variable from the environment store
	user := localEnv.Getenv("USER")
	fmt.Print(user)
}

func (*LocalEnv) IsExporter

func (e *LocalEnv) IsExporter() bool

IsExporter returns true if this backing store holds variables that should be exported to external programs.

It is used by OverlayEnv.Environ to work out which keys and values the OverlayEnv should include in its output.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// by default, the environment store is NOT an exporter
	//
	// if you want to change this hint, use envish.SetAsExporter
	exporting := localEnv.IsExporter()
	fmt.Print(exporting)
}

Output:


false
func (*LocalEnv) Length

func (e *LocalEnv) Length() int

Length returns the number of key/value pairs stored in the LocalEnv.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find out how many variables it contains
	//
	// a new LocalEnv starts with no entries
	fmt.Printf("environment has %d entries\n", localEnv.Length())
}

Output:

environment has 0 entries
func (*LocalEnv) LookupEnv

func (e *LocalEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the variable named by the key.

If the key is not found, an empty string is returned, and the returned boolean is false.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find out if a key exists
	value, ok := localEnv.LookupEnv("USER")
	fmt.Printf("key exists: %v", ok)
	fmt.Printf("value of key: %s", value)
}

func (*LocalEnv) MatchVarNames

func (e *LocalEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for ${!prefix*} string expansion syntax.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find all variables that begin with 'ANSIBLE_'
	for _, key := range localEnv.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, localEnv.Getenv(key))
	}
}

func (*LocalEnv) Setenv

func (e *LocalEnv) Setenv(key, value string) error

Setenv sets the value of the variable named by the key. The program's environment remains unchanged.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// set a value in the environment store
	localEnv.Setenv("DEBIAN_FRONTEND", "noninteractive")
}

ErrEmptyKey

Setenv will return a envish.ErrEmptyKey error if you pass in a key that is either empty, or contains only whitespace.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// try to create an invalid environment variable
	err := localEnv.Setenv("", "key-is-invalid")
	fmt.Print(err)
}

Output:

zero-length key, or key only contains whitespace
ErrNilPointer
package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	var localEnv *envish.LocalEnv = nil
	err := localEnv.Setenv("valid-key", "valid-value")
	fmt.Print(err)
}

Output:

nil pointer to environment store passed to LocalEnv.Setenv
func (*LocalEnv) Unsetenv

func (e *LocalEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// delete an entry from the environment store
	localEnv.Unsetenv("$#")
}

type OverlayEnv

type OverlayEnv struct { ... }

OverlayEnv works on a collection of variable backing stores.

Use an OverlayEnv to combine one (or more) LocalEnv and a single ProgramEnv into a single logical environment.

We do this in https://github.com/ganbarodigital/go_scriptish to emulate local variable support.

func NewOverlayEnv

func NewOverlayEnv(envs []Expander) *OverlayEnv

NewOverlayEnv builds a single logical environments from the given set of underlying environments.

NewOverlayEnv returns a pointer to an OverlayEnv struct. You can use the methods of the OverlayStruct to read from and write to each of these environments.

The order of the arguments to NewOverlayEnv matters. The returned OverlayEnv's methods will read from / write to the underlying environments in the order you've given.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// you can now treat them as a single environment
	env.Setenv("$1", "go")
}

func (*OverlayEnv) Clearenv

func (e *OverlayEnv) Clearenv()

Clearenv deletes all variables in every environment in the OverlayEnv. If your overlay env includes a ProgramEnv, this WILL delete all of your program's environment variables.

Use with extreme caution!

func (*OverlayEnv) Environ

func (e *OverlayEnv) Environ() []string

Environ() returns a copy of all of the variables in your OverlayEnv in the form key=value. This format is compatible with Golang's built-in packages.

When it builds the list, it follows these rules:

  • it searches the environments in the order you provided them to NewOverlayEnv

  • it only includes variables from environments where the IsExporter method returns true

  • if the same variable is set in multiple environments, it uses the first value it finds

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
	"os/exec"
)

func main() {
	// create our independent environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// export their variables
	//
	// it will pick up variables from:
	//
	// - progVars (because it was called with the SetAsExporter functional option)
	// - progEnv (because ProgramEnv.IsExporter() ALWAYS returns `true`)
	//
	// if a variable is set in both `progVars` and `progEnv`, it will use the
	// value from `progVars`
	environ := env.Environ()

	// pass it into run a child process
	cmd := exec.Command("godoc")
	cmd.Env = environ

	// you can now call cmd.Start()
}

func (*OverlayEnv) Expand

func (e *OverlayEnv) Expand(fmt string) string

Expand will replace ${key} and $key entries in a format string, by looking up values from the environments contained within the OverlayEnv.

  • it uses the given OverlayEnv's LookupEnv to find the values of variables

  • it uses the given OverlayEnv's Setenv to set the values of variables

  • it uses the given OverlayEnv's MatchVarNames to expand variable name prefixes

  • it uses the given OverlayEnv's LookupHomeDir to expand ~ (tilde)

Hopefully, we've got the logic right, and you'll find that your expansions just work the way you'd naturally expect.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the shell expansion. It supports the vast majority of UNIX shell string expansion operations.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// use UNIX shell expansion to see what we have
	fmt.Print(env.Expand("USER is ${USER}\n"))
}

func (*OverlayEnv) Export

func (e *OverlayEnv) Export(key, value string) error

Export emulates UNIX shell export XXX=YYY behaviour. It returns an error if the environment variable cannot be set.

  • It makes no changes at all if the given OverlayEnv does not have any environments that are exporters.

  • It searches each environment inside the given OverlayEnv in the order that you passed them into NewOverlayEnv.

  • It overwrites any existing value for the given key, in every environment that it searches (including environments that are not exporters). This should ensure consistent results whenever you call Getenv on the given OverlayEnv.

  • It stops once it has set the environment variable inside an environment that is an exporter.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// set a variable that we want to see appear in the Environ
	// results
	//
	// this will be set in the second environment that we passed
	// into NewOverlayEnv above
	env.Export("DEBIAN_FRONTEND", "noninteractive")
}

func (*OverlayEnv) GetEnvByID

func (e *OverlayEnv) GetEnvByID(id int) (Expander, bool)

GetEnvByID returns the requested environment from the given OverlayEnv. ID 0 is the first environment you passed into NewOverlayEnv, ID 1 is the second environment, and so on.

If you request an ID that the given OverlayEnv does not have, it returns nil, false.

GetEnvByID is handy if you don't want to keep separate references to the environments after you've combined them into the OverlayEnv.

// Envish is a library to help you emulate UNIX-like program environments
// in Golang packages
//
// Copyright 2019-present Ganbaro Digital Ltd
// 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 names of the copyright holders nor the names of his
//     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.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

const (
	LocalVars = iota
	ProgramVars
	ProgramEnv
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// NOTE how we use the constant defined earlier to find the right
	// environment to set this variable in
	localVars, _ := env.GetEnvByID(LocalVars)

	// this will now be available to read and update in the `env` elsewhere
	localVars.Setenv("$#", "2")
}

func (*OverlayEnv) GetTopMostEnv

func (e *OverlayEnv) GetTopMostEnv() (Expander, error)

GetTopMostEnv returns the envish environment that's at the top of the overlay stack.

If we don't have that environment, we return a suitable error.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// now, imagine we want to set some local variables
	localVars, _ := env.GetTopMostEnv()
	localVars.Setenv("$#", "2")
}

func (*OverlayEnv) Getenv

func (e *OverlayEnv) Getenv(key string) string

Getenv returns the value of the given variable. If the variable does not exist, it returns "" (empty string).

  • it searches the environments in the order you provided them to NewOverlayEnv

  • if the same variable is set in multiple environments, it uses the first value it finds

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// show what we have
	fmt.Printf("USER is %s`n", env.Getenv("USER"))
}

func (*OverlayEnv) IsExporter

func (e *OverlayEnv) IsExporter() bool

IsExporter returns true if (and only if) any of the environments in the overlay env hold variables that should be exported to external programs.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}

Output:

true
NoExporters

If none of the environments passed to NewOverlayEnv are exporters, IsExporter will return false.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}

Output:

false
ProgramEnvIsAlwaysAnExporter

If you have a ProgramEnv anywhere in your OverlayEnv, IsExporter will always return true.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewProgramEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}

Output:

true
func (*OverlayEnv) LookupEnv

func (e *OverlayEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the given variable. If the variable does not exist, it returns "", false.

  • it searches the environments in the order you provided them to NewOverlayEnv

  • if the same variable is set in multiple environments, it uses the first value it finds

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	home, ok := env.LookupEnv("HOME")
	fmt.Printf("did we find $HOME?: %v", ok)
	fmt.Printf("what value did we find?: %v", home)
}

func (*OverlayEnv) MatchVarNames

func (e *OverlayEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for ${!prefix*} string expansion syntax.

  • it searches the environments in the order you provided them to NewOverlayEnv

  • if the same key is found in multiple environments, it only returns the key once (ie, results are deduped before they are returned)

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// print out all the variables with the prefix ANSIBLE_
	for _, key := range env.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, env.Getenv(key))
	}
}

func (*OverlayEnv) Setenv

func (e *OverlayEnv) Setenv(key, value string) error

Setenv creates a variable (if it doesn't already exist) or updates its value (if it does exist).

  • it searches the environments in the order you provided to NewOverlayEnv

  • if the same variable is set in multiple environments, it updates the first variable it finds

  • if the variable does not exist, it is always created in the first environment you provided to NewOverlayEnv

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// some example data to show how Setenv() works
	localVars.Setenv("LOCAL", "100")
	progVars.Setenv("PROG", "200")
	progEnv.Setenv("ENV", "300")

	// this will update the variable "PROG" in the `progVars` environment,
	// because it already exists
	env.Setenv("PROG", "250")

	// this will create the variable "NEWVAR" in the `localVars` environment,
	// because:
	//
	// a) NEWVAR does not yet exist, and
	// b) `localVars` was the first environment passed into `NewOverlayEnv()`
	env.Setenv("NEWVAR", "101")
}

func (*OverlayEnv) Unsetenv

func (e *OverlayEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

It will be deleted from all the environments in the stack.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// some example data to show how Unsetenv() works
	localVars.Setenv("VAR", "100")
	progVars.Setenv("VAR", "200")
	progEnv.Setenv("VAR", "300")

	// `value` is "100"
	value := env.Getenv("VAR")
	fmt.Printf("value is: %s\n", value)

	// delete it
	env.Unsetenv("VAR")

	// `ok1` is false; "VAR" has been deleted from here
	_, ok1 := localVars.LookupEnv("VAR")
	fmt.Printf("ok1 is: %v\n", ok1)

	// `ok2` is also false; "VAR" has been deleted from here as well
	_, ok2 := progVars.LookupEnv("VAR")
	fmt.Printf("ok2 is: %v\n", ok2)

	// `ok3` is also false; "VAR" has been deleted from here as well
	_, ok3 := progEnv.LookupEnv("VAR")
	fmt.Printf("ok3 is: %v\n", ok3)

}

Output:

value is: 100
ok1 is: false
ok2 is: false
ok3 is: false
type ProgramEnv

type ProgramEnv struct { ... }

ProgramEnv puts helper wrapper functions around your program's environment.

func NewProgramEnv

func NewProgramEnv() *ProgramEnv

NewProgramEnv returns an envish environment that works directly with your program's environment.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	env := envish.NewProgramEnv()

	fmt.Printf("USER is %s", env.Getenv("USER"))
}

func (*ProgramEnv) Clearenv

func (e *ProgramEnv) Clearenv()

Clearenv deletes all entries from your program's environment. Use with extreme caution!

func (*ProgramEnv) Environ

func (e *ProgramEnv) Environ() []string

Environ returns a copy of all entries in the form "key=value". This format is compatible with Golang's built-in packages.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
	"os/exec"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// get a list of all entries in the environment
	environ := env.Environ()

	// pass it into run a child process
	cmd := exec.Command("godoc")
	cmd.Env = environ

	// you can now call cmd.Start()
}

func (*ProgramEnv) Expand

func (e *ProgramEnv) Expand(fmt string) string

Expand replaces ${var} or $var in the input string, by looking up values from your program's environment.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the shell expansion. It supports the vast majority of UNIX shell string expansion operations.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// show what we have
	fmt.Print(env.Expand("USER is ${USER}"))
}

func (*ProgramEnv) Getenv

func (e *ProgramEnv) Getenv(key string) string

Getenv returns the value of the variable named by the key.

If the key is not found, an empty string is returned.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// show what we have
	fmt.Printf("USER is %s", env.Getenv("USER"))
}

func (*ProgramEnv) IsExporter

func (e *ProgramEnv) IsExporter() bool

IsExporter always returns true.

It is used by OverlayEnv.Environ to work out which keys and values the OverlayEnv should include in its output.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// a ProgramEnv is always an environment exporter
	fmt.Printf("env.IsExporter() is %v", env.IsExporter())

}

Output:

env.IsExporter() is true
func (*ProgramEnv) LookupEnv

func (e *ProgramEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the variable named by the key.

If the key is not found, an empty string is returned, and the returned boolean is false.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	value, ok := env.LookupEnv("USER")

	fmt.Printf("ok is: %v\n", ok)
	fmt.Printf("value is: %v\n", value)
}

func (*ProgramEnv) MatchVarNames

func (e *ProgramEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for ${!prefix*} string expansion syntax.

package main

import (
	"fmt"
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// print out all the variables with the prefix ANSIBLE_
	for _, key := range env.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, env.Getenv(key))
	}
}

func (*ProgramEnv) RestoreEnvironment

func (e *ProgramEnv) RestoreEnvironment(pairs []string)

RestoreEnvironment writes the given "key=value" pairs into your program's environment.

It does not empty your program's environment first!

It was originally added so that our unit tests could put the 'go test' program environment back in place after each test had run.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// take a backup of the whole environment
	backup := env.Environ()

	// nuke the environment
	env.Clearenv()

	// restore from backup
	env.RestoreEnvironment(backup)
}

func (*ProgramEnv) Setenv

func (e *ProgramEnv) Setenv(key, value string) error

Setenv sets the value of the variable named by the key.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
	"os/exec"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// set a new environment variable
	//
	// this will be picked up the next time you call a child process
	env.Setenv("DEBIAN_FRONTEND", "noninteractive")

	// pass it into run a child process
	cmd := exec.Command("apt-get", "install", "mysql-server")
	cmd.Env = env.Environ()

	// you can now call cmd.Start()
}

func (*ProgramEnv) Unsetenv

func (e *ProgramEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

This will remove the given variable from your program's environment. Use with caution!

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
	"os/exec"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// set a new environment variable
	//
	// this will be picked up the next time you call a child process
	env.Setenv("DEBIAN_FRONTEND", "noninteractive")

	// pass it into run a child process
	cmd := exec.Command("apt-get", "install", "mysql-server")
	cmd.Env = env.Environ()

	// you can now call cmd.Start()

	// afterwards, undo what you originally set
	env.Unsetenv("DEBIAN_FRONTEND")
}

type Reader

type Reader interface { ... }

Reader is the interface that wraps a basic, read-only key/value store.

type ReaderWriter

type ReaderWriter interface { ... }

ReaderWriter is the interface that groups the basic Read and Write methods for a key/value backing store.

type Writer

type Writer interface { ... }

Writer is the interface that wraps a basic, write-only key/value store.


Readme created from Go doc with goreadme

Documentation

Overview

envish is a library to help you emulate UNIX-like program environments in Golang packages.

It is released under the 3-clause New BSD license. See LICENSE.md for details.

import envish "github.com/ganbarodigital/go_envish/v4"

env := envish.NewLocalEnv()

// add to this temporary environment
// WITHOUT changing your program's environment
env.Setenv("EXAMPLE_KEY", "EXAMPLE VALUE")

// pass it into run a child process
cmd := exec.Command('example-cmd')
cmd.Env = env.Environ()
cmd.Start()

Why Use Envish

We've built Envish for anyone who needs to emulate a UNIX-like environment in their own Golang packages. Or anyone who just needs a simple key/value store with a familiar API.

We're using it ourselves for our (Pipe) https://github.com/ganbarodigital/go_pipe and (Scriptish) https://github.com/ganbarodigital/go_scriptish packages.

Why A Separate Package

Golang's `os` package provides support for working with your program's environment. But what if you want to make temporary changes to that environment, just to pass environment variables into child processes?

This is a very common pattern used in UNIX shell script programming:

DEBIAN_FRONTEND=noninteractive apt-get install -y mysql

In the example above, the environment variable `DEBIAN_FRONTEND` is only set for the child process `apt-get`.

Getting Started

Import Envish into your Golang code:

import envish "github.com/ganbarodigital/go_envish/v4"

Create a copy of your program's environment:

localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

or simply start with an empty environment store:

localVars := envish.NewLocalEnv()

Get and set variables in the environment store as needed:

home := localEnv.Getenv()
localEnv.Setenv("DEBIAN_FRONTEND", "noninteractive")

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CopyProgramEnv

func CopyProgramEnv(e *LocalEnv)

CopyProgramEnv copies your program's environment into the given environment store.

It replaces any existing variables in the environment store.

func GetKeyFromPair

func GetKeyFromPair(pair string) string

GetKeyFromPair returns the `KEY` from a string `KEY=VALUE`.

func GetValueFromPair

func GetValueFromPair(pair string, key string) string

GetValueFromPair returns the `VALUE` from a string `KEY=VALUE`.

func LookupHomeDir

func LookupHomeDir(username string) (string, bool)

LookupHomeDir retrieves the given user's home directory, or false if that cannot be found.

It does not use the value of $HOME at all.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// ask the operating system where your home directory is
	homedir, ok := envish.LookupHomeDir("")

	// print the results
	fmt.Printf("ok is %v", ok)
	fmt.Printf("your homedir is: %s", homedir)
}
Output:

func SetAsExporter

func SetAsExporter(e *LocalEnv)

SetAsExporter sets a flag so that the EnvStack will include its contents when building an environ to export to Golang's exec package.

Types

type ErrEmptyKey

type ErrEmptyKey struct{}

ErrEmptyKey is returned whenever we're given a key that is zero-length or only contains whitespace

func (ErrEmptyKey) Error

func (e ErrEmptyKey) Error() string

type ErrEmptyOverlayEnv

type ErrEmptyOverlayEnv struct {
	Method string
}

ErrEmptyOverlayEnv is returned whenever you call a method on an empty EnvStack

func (ErrEmptyOverlayEnv) Error

func (e ErrEmptyOverlayEnv) Error() string

type ErrNilPointer

type ErrNilPointer struct {
	Method string
}

ErrNilPointer is returned whenever you call a method on the Env struct with a nil pointer

func (ErrNilPointer) Error

func (e ErrNilPointer) Error() string

type ErrNoExporterEnv

type ErrNoExporterEnv struct {
	Method string
}

func (ErrNoExporterEnv) Error

func (e ErrNoExporterEnv) Error() string

type Expander

type Expander interface {
	ReaderWriter

	// Expand replaces ${var} or $var in the input string.
	Expand(fmt string) string
}

Expander is the interface that wraps a key/value store that also supports string expansion.

type LocalEnv

type LocalEnv struct {
	// contains filtered or unexported fields
}

LocalEnv holds a list key/value pairs.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	localEnv := envish.NewLocalEnv()

	// it starts as an empty environment
	fmt.Print(localEnv.Getenv("$USER"))
}
Output:

func NewLocalEnv

func NewLocalEnv(options ...func(*LocalEnv)) *LocalEnv

NewLocalEnv creates an empty environment store.

You can pass (functional options) https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis into NewLocalEnv to change the environment store before it is returned to you.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	localEnv := envish.NewLocalEnv()

	// it starts as an empty environment
	fmt.Print(localEnv.Getenv("$USER"))
}
Output:

Example (WithFunctionalOptions1)

CopyProgramEnv is a functional option that will populate the environment store with a copy of your program's environment.

package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	//
	// it will start with a copy of your program's environment
	localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

	// on UNIX-like systems, this will print the name of the user
	// who is running the program
	fmt.Print(localEnv.Getenv("$USER"))
}
Output:

Example (WithFunctionalOptions2)

SetAsExporter is a functional option that will tell the OverlayEnv to include your LocalEnv's contents in any call to the OverlayEnv's Environ function.

package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a local environment
	//
	// if you add this to an OverlayEnv, the OverlayEnv will include
	// its contents when you call the OverlayEnv's Environ method.
	localEnv := envish.NewLocalEnv(envish.SetAsExporter)

	// this environment is now an exporter
	fmt.Print(localEnv.IsExporter())
}
Output:


true

func (*LocalEnv) Clearenv

func (e *LocalEnv) Clearenv()

Clearenv deletes all entries from the given LocalEnv. The program's environment remains unchanged.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create a environment store
	localEnv := envish.NewLocalEnv(envish.CopyProgramEnv)

	// empty the environment store completely
	localEnv.Clearenv()
}
Output:

func (*LocalEnv) Environ

func (e *LocalEnv) Environ() []string

Environ returns a copy of all entries in the form "key=value". This is compatible with any Golang standard library, such as `os/exec`.

Example
package main

import (
	"os/exec"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// get a copy to pass into `os/exec`
	cmd := exec.Command("go", "doc")
	cmd.Env = localEnv.Environ()
}
Output:

func (*LocalEnv) Expand

func (e *LocalEnv) Expand(fmt string) string

Expand replaces ${var} or $var in the input string.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the expansion.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// show what we have
	fmt.Print(localEnv.Expand("USER is ${USER}\n"))
}
Output:

func (*LocalEnv) Getenv

func (e *LocalEnv) Getenv(key string) string

Getenv returns the value of the variable named by the key.

If the key is not found, an empty string is returned.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// get a variable from the environment store
	user := localEnv.Getenv("USER")
	fmt.Print(user)
}
Output:

func (*LocalEnv) IsExporter

func (e *LocalEnv) IsExporter() bool

IsExporter returns true if this backing store holds variables that should be exported to external programs.

It is used by OverlayEnv.Environ to work out which keys and values the OverlayEnv should include in its output.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// by default, the environment store is NOT an exporter
	//
	// if you want to change this hint, use envish.SetAsExporter
	exporting := localEnv.IsExporter()
	fmt.Print(exporting)
}
Output:


false

func (*LocalEnv) Length

func (e *LocalEnv) Length() int

Length returns the number of key/value pairs stored in the LocalEnv.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find out how many variables it contains
	//
	// a new LocalEnv starts with no entries
	fmt.Printf("environment has %d entries\n", localEnv.Length())
}
Output:

environment has 0 entries

func (*LocalEnv) LookupEnv

func (e *LocalEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the variable named by the key.

If the key is not found, an empty string is returned, and the returned boolean is false.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find out if a key exists
	value, ok := localEnv.LookupEnv("USER")
	fmt.Printf("key exists: %v", ok)
	fmt.Printf("value of key: %s", value)
}
Output:

func (*LocalEnv) MatchVarNames

func (e *LocalEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for `${!prefix*}` string expansion syntax.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// find all variables that begin with 'ANSIBLE_'
	for _, key := range localEnv.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, localEnv.Getenv(key))
	}
}
Output:

func (*LocalEnv) Setenv

func (e *LocalEnv) Setenv(key, value string) error

Setenv sets the value of the variable named by the key. The program's environment remains unchanged.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// set a value in the environment store
	localEnv.Setenv("DEBIAN_FRONTEND", "noninteractive")
}
Output:

Example (ErrEmptyKey)

Setenv will return a `envish.ErrEmptyKey` error if you pass in a key that is either empty, or contains only whitespace.

package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// try to create an invalid environment variable
	err := localEnv.Setenv("", "key-is-invalid")
	fmt.Print(err)
}
Output:

zero-length key, or key only contains whitespace
Example (ErrNilPointer)
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	var localEnv *envish.LocalEnv = nil
	err := localEnv.Setenv("valid-key", "valid-value")
	fmt.Print(err)
}
Output:

nil pointer to environment store passed to LocalEnv.Setenv

func (*LocalEnv) Unsetenv

func (e *LocalEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create an environment store
	localEnv := envish.NewLocalEnv()

	// delete an entry from the environment store
	localEnv.Unsetenv("$#")
}
Output:

type OverlayEnv

type OverlayEnv struct {
	// contains filtered or unexported fields
}

OverlayEnv works on a collection of variable backing stores.

Use an OverlayEnv to combine one (or more) LocalEnv and a single ProgramEnv into a single logical environment.

We do this in https://github.com/ganbarodigital/go_scriptish to emulate local variable support.

func NewOverlayEnv

func NewOverlayEnv(envs []Expander) *OverlayEnv

NewOverlayEnv builds a single logical environments from the given set of underlying environments.

NewOverlayEnv returns a pointer to an OverlayEnv struct. You can use the methods of the OverlayStruct to read from and write to each of these environments.

The order of the arguments to NewOverlayEnv matters. The returned OverlayEnv's methods will read from / write to the underlying environments in the order you've given.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// you can now treat them as a single environment
	env.Setenv("$1", "go")
}
Output:

func (*OverlayEnv) Clearenv

func (e *OverlayEnv) Clearenv()

Clearenv deletes all variables in every environment in the OverlayEnv. If your overlay env includes a ProgramEnv, this *WILL* delete all of your program's environment variables.

Use with extreme caution!

func (*OverlayEnv) Environ

func (e *OverlayEnv) Environ() []string

Environ() returns a copy of all of the variables in your `OverlayEnv` in the form `key=value`. This format is compatible with Golang's built-in packages.

When it builds the list, it follows these rules:

* it searches the environments in the order you provided them to NewOverlayEnv

* it only includes variables from environments where the IsExporter method returns `true`

* if the same variable is set in multiple environments, it uses the first value it finds

Example
package main

import (
	"os/exec"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create our independent environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// export their variables
	//
	// it will pick up variables from:
	//
	// - progVars (because it was called with the SetAsExporter functional option)
	// - progEnv (because ProgramEnv.IsExporter() ALWAYS returns `true`)
	//
	// if a variable is set in both `progVars` and `progEnv`, it will use the
	// value from `progVars`
	environ := env.Environ()

	// pass it into run a child process
	cmd := exec.Command("godoc")
	cmd.Env = environ

	// you can now call cmd.Start()
}
Output:

func (*OverlayEnv) Expand

func (e *OverlayEnv) Expand(fmt string) string

Expand will replace `${key}` and `$key` entries in a format string, by looking up values from the environments contained within the OverlayEnv.

* it uses the given OverlayEnv's LookupEnv to find the values of variables

* it uses the given OverlayEnv's Setenv to set the values of variables

* it uses the given OverlayEnv's MatchVarNames to expand variable name prefixes

* it uses the given OverlayEnv's LookupHomeDir to expand `~` (tilde)

Hopefully, we've got the logic right, and you'll find that your expansions just work the way you'd naturally expect.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the shell expansion. It supports the vast majority of UNIX shell string expansion operations.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// use UNIX shell expansion to see what we have
	fmt.Print(env.Expand("USER is ${USER}\n"))
}
Output:

func (*OverlayEnv) Export

func (e *OverlayEnv) Export(key, value string) error

Export emulates UNIX shell `export XXX=YYY` behaviour. It returns an error if the environment variable cannot be set.

* It makes no changes at all if the given OverlayEnv does not have any environments that are exporters.

* It searches each environment inside the given OverlayEnv in the order that you passed them into NewOverlayEnv.

* It overwrites any existing value for the given key, in every environment that it searches (including environments that are not exporters). This should ensure consistent results whenever you call Getenv on the given OverlayEnv.

* It stops once it has set the environment variable inside an environment that is an exporter.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// set a variable that we want to see appear in the Environ
	// results
	//
	// this will be set in the second environment that we passed
	// into NewOverlayEnv above
	env.Export("DEBIAN_FRONTEND", "noninteractive")
}
Output:

func (*OverlayEnv) GetEnvByID

func (e *OverlayEnv) GetEnvByID(id int) (Expander, bool)

GetEnvByID returns the requested environment from the given OverlayEnv. ID `0` is the first environment you passed into NewOverlayEnv, ID `1` is the second environment, and so on.

If you request an ID that the given OverlayEnv does not have, it returns `nil, false`.

GetEnvByID is handy if you don't want to keep separate references to the environments after you've combined them into the OverlayEnv.

Example
// Envish is a library to help you emulate UNIX-like program environments
// in Golang packages
//
// Copyright 2019-present Ganbaro Digital Ltd
// 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 names of the copyright holders nor the names of his
//     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.

package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

const (
	LocalVars = iota
	ProgramVars
	ProgramEnv
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// NOTE how we use the constant defined earlier to find the right
	// environment to set this variable in
	localVars, _ := env.GetEnvByID(LocalVars)

	// this will now be available to read and update in the `env` elsewhere
	localVars.Setenv("$#", "2")
}
Output:

func (*OverlayEnv) GetTopMostEnv

func (e *OverlayEnv) GetTopMostEnv() (Expander, error)

GetTopMostEnv returns the envish environment that's at the top of the overlay stack.

If we don't have that environment, we return a suitable error.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// now, imagine we want to set some local variables
	localVars, _ := env.GetTopMostEnv()
	localVars.Setenv("$#", "2")
}
Output:

func (*OverlayEnv) Getenv

func (e *OverlayEnv) Getenv(key string) string

Getenv returns the value of the given variable. If the variable does not exist, it returns `""` (empty string).

* it searches the environments in the order you provided them to NewOverlayEnv

* if the same variable is set in multiple environments, it uses the first value it finds

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// show what we have
	fmt.Printf("USER is %s`n", env.Getenv("USER"))
}
Output:

func (*OverlayEnv) IsExporter

func (e *OverlayEnv) IsExporter() bool

IsExporter returns `true` if (and only if) any of the environments in the overlay env hold variables that should be exported to external programs.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}
Output:

true
Example (NoExporters)

If none of the environments passed to NewOverlayEnv are exporters, IsExporter will return `false`.

package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}
Output:

false
Example (ProgramEnvIsAlwaysAnExporter)

If you have a ProgramEnv anywhere in your OverlayEnv, IsExporter will always return `true`.

package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewProgramEnv(),
		},
	)

	fmt.Print(env.IsExporter())
}
Output:

true

func (*OverlayEnv) LookupEnv

func (e *OverlayEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the given variable. If the variable does not exist, it returns `"", false`.

* it searches the environments in the order you provided them to NewOverlayEnv

* if the same variable is set in multiple environments, it uses the first value it finds

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	home, ok := env.LookupEnv("HOME")
	fmt.Printf("did we find $HOME?: %v", ok)
	fmt.Printf("what value did we find?: %v", home)
}
Output:

func (*OverlayEnv) MatchVarNames

func (e *OverlayEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for `${!prefix*}` string expansion syntax.

* it searches the environments in the order you provided them to NewOverlayEnv

* if the same key is found in multiple environments, it only returns the key once (ie, results are deduped before they are returned)

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// build an environment stack without keeping a reference
	// to any of the individual environments
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			envish.NewLocalEnv(),
			envish.NewLocalEnv(envish.SetAsExporter),
			envish.NewProgramEnv(),
		},
	)

	// print out all the variables with the prefix ANSIBLE_
	for _, key := range env.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, env.Getenv(key))
	}
}
Output:

func (*OverlayEnv) Setenv

func (e *OverlayEnv) Setenv(key, value string) error

Setenv creates a variable (if it doesn't already exist) or updates its value (if it does exist).

* it searches the environments in the order you provided to NewOverlayEnv

* if the same variable is set in multiple environments, it updates the first variable it finds

* if the variable does not exist, it is always created in the first environment you provided to NewOverlayEnv

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// some example data to show how Setenv() works
	localVars.Setenv("LOCAL", "100")
	progVars.Setenv("PROG", "200")
	progEnv.Setenv("ENV", "300")

	// this will update the variable "PROG" in the `progVars` environment,
	// because it already exists
	env.Setenv("PROG", "250")

	// this will create the variable "NEWVAR" in the `localVars` environment,
	// because:
	//
	// a) NEWVAR does not yet exist, and
	// b) `localVars` was the first environment passed into `NewOverlayEnv()`
	env.Setenv("NEWVAR", "101")
}
Output:

func (*OverlayEnv) Unsetenv

func (e *OverlayEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

It will be deleted from all the environments in the stack.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// create individual environments
	localVars := envish.NewLocalEnv()
	progVars := envish.NewLocalEnv(envish.SetAsExporter)
	progEnv := envish.NewProgramEnv()

	// combine them
	env := envish.NewOverlayEnv(
		[]envish.Expander{
			localVars,
			progVars,
			progEnv,
		},
	)

	// some example data to show how Unsetenv() works
	localVars.Setenv("VAR", "100")
	progVars.Setenv("VAR", "200")
	progEnv.Setenv("VAR", "300")

	// `value` is "100"
	value := env.Getenv("VAR")
	fmt.Printf("value is: %s\n", value)

	// delete it
	env.Unsetenv("VAR")

	// `ok1` is false; "VAR" has been deleted from here
	_, ok1 := localVars.LookupEnv("VAR")
	fmt.Printf("ok1 is: %v\n", ok1)

	// `ok2` is also false; "VAR" has been deleted from here as well
	_, ok2 := progVars.LookupEnv("VAR")
	fmt.Printf("ok2 is: %v\n", ok2)

	// `ok3` is also false; "VAR" has been deleted from here as well
	_, ok3 := progEnv.LookupEnv("VAR")
	fmt.Printf("ok3 is: %v\n", ok3)

}
Output:

value is: 100
ok1 is: false
ok2 is: false
ok3 is: false

type ProgramEnv

type ProgramEnv struct {
}

ProgramEnv puts helper wrapper functions around your program's environment.

func NewProgramEnv

func NewProgramEnv() *ProgramEnv

NewProgramEnv returns an envish environment that works directly with your program's environment.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	env := envish.NewProgramEnv()

	fmt.Printf("USER is %s", env.Getenv("USER"))
}
Output:

func (*ProgramEnv) Clearenv

func (e *ProgramEnv) Clearenv()

Clearenv deletes all entries from your program's environment. Use with extreme caution!

func (*ProgramEnv) Environ

func (e *ProgramEnv) Environ() []string

Environ returns a copy of all entries in the form "key=value". This format is compatible with Golang's built-in packages.

Example
package main

import (
	"os/exec"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// get a list of all entries in the environment
	environ := env.Environ()

	// pass it into run a child process
	cmd := exec.Command("godoc")
	cmd.Env = environ

	// you can now call cmd.Start()
}
Output:

func (*ProgramEnv) Expand

func (e *ProgramEnv) Expand(fmt string) string

Expand replaces ${var} or $var in the input string, by looking up values from your program's environment.

Internally, it uses https://github.com/ganbarodigital/go_shellexpand to do the shell expansion. It supports the vast majority of UNIX shell string expansion operations.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// show what we have
	fmt.Print(env.Expand("USER is ${USER}"))
}
Output:

func (*ProgramEnv) Getenv

func (e *ProgramEnv) Getenv(key string) string

Getenv returns the value of the variable named by the key.

If the key is not found, an empty string is returned.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// show what we have
	fmt.Printf("USER is %s", env.Getenv("USER"))
}
Output:

func (*ProgramEnv) IsExporter

func (e *ProgramEnv) IsExporter() bool

IsExporter always returns `true`.

It is used by OverlayEnv.Environ to work out which keys and values the OverlayEnv should include in its output.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	// a ProgramEnv is always an environment exporter
	fmt.Printf("env.IsExporter() is %v", env.IsExporter())

}
Output:

env.IsExporter() is true

func (*ProgramEnv) LookupEnv

func (e *ProgramEnv) LookupEnv(key string) (string, bool)

LookupEnv returns the value of the variable named by the key.

If the key is not found, an empty string is returned, and the returned boolean is false.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to our program environment
	env := envish.NewProgramEnv()

	value, ok := env.LookupEnv("USER")

	fmt.Printf("ok is: %v\n", ok)
	fmt.Printf("value is: %v\n", value)
}
Output:

func (*ProgramEnv) MatchVarNames

func (e *ProgramEnv) MatchVarNames(prefix string) []string

MatchVarNames returns a list of variable names that start with the given prefix.

It's a feature needed for `${!prefix*}` string expansion syntax.

Example
package main

import (
	"fmt"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// print out all the variables with the prefix ANSIBLE_
	for _, key := range env.MatchVarNames("ANSIBLE_") {
		fmt.Printf("%s = %s", key, env.Getenv(key))
	}
}
Output:

func (*ProgramEnv) RestoreEnvironment

func (e *ProgramEnv) RestoreEnvironment(pairs []string)

RestoreEnvironment writes the given "key=value" pairs into your program's environment.

It does *not* empty your program's environment first!

It was originally added so that our unit tests could put the 'go test' program environment back in place after each test had run.

Example
package main

import (
	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// take a backup of the whole environment
	backup := env.Environ()

	// nuke the environment
	env.Clearenv()

	// restore from backup
	env.RestoreEnvironment(backup)
}
Output:

func (*ProgramEnv) Setenv

func (e *ProgramEnv) Setenv(key, value string) error

Setenv sets the value of the variable named by the key.

Example
package main

import (
	"os/exec"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// set a new environment variable
	//
	// this will be picked up the next time you call a child process
	env.Setenv("DEBIAN_FRONTEND", "noninteractive")

	// pass it into run a child process
	cmd := exec.Command("apt-get", "install", "mysql-server")
	cmd.Env = env.Environ()

	// you can now call cmd.Start()
}
Output:

func (*ProgramEnv) Unsetenv

func (e *ProgramEnv) Unsetenv(key string)

Unsetenv deletes the variable named by the key.

This will remove the given variable from your program's environment. Use with caution!

Example
package main

import (
	"os/exec"

	envish "github.com/ganbarodigital/go_envish/v4"
)

func main() {
	// get access to your program environment
	env := envish.NewProgramEnv()

	// set a new environment variable
	//
	// this will be picked up the next time you call a child process
	env.Setenv("DEBIAN_FRONTEND", "noninteractive")

	// pass it into run a child process
	cmd := exec.Command("apt-get", "install", "mysql-server")
	cmd.Env = env.Environ()

	// you can now call cmd.Start()

	// afterwards, undo what you originally set
	env.Unsetenv("DEBIAN_FRONTEND")
}
Output:

type Reader

type Reader interface {
	// Environ returns a copy of all entries in the form "key=value".
	Environ() []string

	// Getenv returns the value of the variable named by the key.
	//
	// If the key is not found, an empty string is returned.
	Getenv(key string) string

	// IsExporter returns true if this backing store holds variables that
	// should be exported to external programs
	IsExporter() bool

	// LookupEnv returns the value of the variable named by the key.
	//
	// If the key is not found, an empty string is returned, and the returned
	// boolean is false.
	LookupEnv(key string) (string, bool)

	// MatchVarNames returns a list of keys that start with the given prefix.
	//
	// It's a feature needed for `${!prefix*}` string expansion syntax.
	MatchVarNames(prefix string) []string
}

Reader is the interface that wraps a basic, read-only key/value store.

type ReaderWriter

type ReaderWriter interface {
	Reader
	Writer
}

ReaderWriter is the interface that groups the basic Read and Write methods for a key/value backing store.

type Writer

type Writer interface {
	// Clearenv deletes all entries
	Clearenv()

	// Setenv sets the value of the variable named by the key.
	Setenv(key, value string) error

	// Unsetenv deletes the variable named by the key.
	Unsetenv(key string)
}

Writer is the interface that wraps a basic, write-only key/value store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL