zos

package
v0.0.0-alpha.10 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2025 License: Apache-2.0 Imports: 9 Imported by: 6

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnvSubst

func EnvSubst(b []byte) (subst []byte, err error)

EnvSubst substitute environmental variable in the given bytes. See the ResolveEnv for available variable syntax. EnvSubst does not support nested variables. Use EnvSubst2 to allow 2 levels nested variable. Note that escaping variable like '\${FOO}' is not supported.

Example
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("FOO", "foo")
	os.Setenv("BAR", "FOO")

	b1, _ := zos.EnvSubst([]byte(`{FOO}=${FOO}`))
	fmt.Println(string(b1))

	b2, _ := zos.EnvSubst([]byte(`{{BAR}}=${${BAR}}`))
	fmt.Println(string(b2)) // Nested env is not supported.

}
Output:

{FOO}=foo
{{BAR}}=${FOO}
Example (All)
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("ABC", "abcdefg")
	os.Setenv("FOO", "foo")
	os.Setenv("BAR", "BAR")
	os.Setenv("ARR_X", "xxx")
	os.Setenv("ARR_Y", "yyy")

	txt := []byte(`
01: {parameter}                 => ${FOO}
02: {parameter:-word}           => ${BAZ:-default}
03: {parameter-word}            => ${BAZ-default}
04: {parameter:=word}           => ${BAZ:=default}
05: {parameter=word}            => ${BAZ=default}
06: {parameter:?word}           => ${BAZ:?default}
07: {parameter?word}            => ${BAZ?default}
08: {parameter:+word}           => ${BAZ:+default}
09: {parameter+word}            => ${BAZ+default}
10: {parameter:offset}          => ${ABC:3}
11: {parameter:offset:length}   => ${ABC:3:3}
12: {!prefix*}                  => ${!ARR*}
13: {!prefix@}                  => ${!ARR@}
14: {#parameter}                => ${#FOO}
15: {parameter#word}            => ${FOO#[a-z]}
16: {parameter##word}           => ${FOO##[a-z]}
17: {parameter%word}            => ${FOO%[a-z]}
18: {parameter%%word}           => ${FOO%%[a-z]}
19: {parameter/pattern/string}  => ${FOO/[a-z]/x}
20: {parameter//pattern/string} => ${FOO//[a-z]/x}
21: {parameter/#pattern/string} => ${FOO/#[a-z]/x}
22: {parameter/%pattern/string} => ${FOO/%[a-z]/x}
23: {parameter^pattern}         => ${FOO^[f]}
24: {parameter^^pattern}        => ${FOO^^[o]}
25: {parameter,pattern}         => ${BAR,[B]}
26: {parameter,,pattern}        => ${BAR,,[A]}
27: {parameter@U}               => ${FOO@U}
27: {parameter@u}               => ${FOO@u}
27: {parameter@L}               => ${BAR@L}
27: {parameter@l}               => ${BAR@l}
`)

	b, _ := zos.EnvSubst(txt)
	fmt.Println(string(b))
}
Output:

01: {parameter}                 => foo
02: {parameter:-word}           => default
03: {parameter-word}            => default
04: {parameter:=word}           => default
05: {parameter=word}            => default
06: {parameter:?word}           => default
07: {parameter?word}            => default
08: {parameter:+word}           => default
09: {parameter+word}            => default
10: {parameter:offset}          => defg
11: {parameter:offset:length}   => def
12: {!prefix*}                  => ARR_X ARR_Y
13: {!prefix@}                  => ARR_X ARR_Y
14: {#parameter}                => 3
15: {parameter#word}            => oo
16: {parameter##word}           => oo
17: {parameter%word}            => fo
18: {parameter%%word}           => fo
19: {parameter/pattern/string}  => xoo
20: {parameter//pattern/string} => xxx
21: {parameter/#pattern/string} => xoo
22: {parameter/%pattern/string} => fox
23: {parameter^pattern}         => Foo
24: {parameter^^pattern}        => fOO
25: {parameter,pattern}         => bAR
26: {parameter,,pattern}        => BaR
27: {parameter@U}               => FOO
27: {parameter@u}               => Foo
27: {parameter@L}               => bar
27: {parameter@l}               => bAR

func EnvSubst2

func EnvSubst2(b []byte) (subst []byte, err error)

EnvSubst2 substitute environmental variable in the given bytes. See the ResolveEnv for available variable syntax. EnvSubst does support nested variables up to 2 levels. ${FOO_${BAR}} is allowed but ${FOO_${BAR_${BAZ}}}} is not allowed. Note that escaping variable like '\${FOO}' is not supported.

Example
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("FOO", "foo")
	os.Setenv("BAR", "FOO")

	b1, _ := zos.EnvSubst2([]byte(`{FOO}=${FOO}`))
	fmt.Println(string(b1))

	b2, _ := zos.EnvSubst2([]byte(`{{BAR}}={FOO}=${${BAR}}`))
	fmt.Println(string(b2)) // Nested env is not supported.

}
Output:

{FOO}=foo
{{BAR}}={FOO}=foo

func GetenvMap

func GetenvMap(name, delim, sep string) map[string][]string

GetenvMap get map data from environmental variable. delim is the delimiter that separates key value pairs. sep is the separator string that separates key and value. For example ENV="foo=f1,foo=f2,bar=b1,baz" has ',' as delimiter and '=' as separator. It results in {"foo":["f1","f2"], "bar":["b1"], "baz":[""]} Key-value pairs without separator are considered as key only. In that cases, key is saved in the returned map with empty string value.

Example
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("FOO", "key1:val1,key2:val2")
	os.Setenv("BAR", "key1-alice|key1-bob|key2")

	fmt.Println(zos.GetenvMap("FOO", ",", ":"))
	fmt.Println(zos.GetenvMap("BAR", "|", "-"))
}
Output:

map[key1:[val1] key2:[val2]]
map[key1:[alice bob] key2:[]]

func GetenvSlice

func GetenvSlice(name, delim string) []string

GetenvSlice get slice data from environmental variable. delim is the delimiter that separates each values.

Example
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("FOO", "foo1,foo2,foo3")
	os.Setenv("BAR", "bar1|bar2|bar3")

	fmt.Println(zos.GetenvSlice("FOO", ","))
	fmt.Println(zos.GetenvSlice("BAR", "|"))
}
Output:

[foo1 foo2 foo3]
[bar1 bar2 bar3]

func IsDir

func IsDir(path string) (bool, error)

IsDir returns if the given path is directory or not. It returns true even if the directory is symbolic link.

func IsFile

func IsFile(path string) (bool, error)

IsFile returns if the given path is file or not. It returns false for non regular files such as

  • Directory
  • Symbolic link
  • Device file
  • Unix domain socket file

See fs.ModeType.

func ListFiles

func ListFiles(recursive bool, paths ...string) ([]string, error)

ListFiles returns file paths of the given paths. Paths can be directories or file and can be relative path or absolute path. If recursive is true, it looks for all sub directories recursively. Note that returned paths are cleaned by filepath.Clean.

func LoadEnv

func LoadEnv(b []byte) (map[string]string, error)

LoadEnv loads environmental variable from the given bytes. Typically LoadEnv is used for loading .env file. LoadEnv resolves embedded environmental variables in the b. THe syntax for substituting environmental variable follows the specification of ResolveEnv.

References:

Input specifications:

Single line:
	# Following declaration results in BAR.
	# Single quotes and double quotes are removed if entire value is enclosed.
	# "export" can be placed before env name.
	FOO=BAR          >> BAR
	FOO="BAR"        >> BAR
	FOO='BAR'        >> BAR
	FOO='B"R'        >> B"R
	FOO="B'R"        >> B'R
	export FOO=BAR   >> BAR

Multiple line:
	# The following definition of FOO results in "BARBAZ".
	# Line breaks of LF or CRLF are removed.
	# BOTH single quotes and double quotes can be used to enclose multiple lines.
	FOO="
	BAR
	BAZ
	"

Comments:
	# Sharp '#' can be used for commenting.
	# It must not be in the scope of single or double quotes.
	# It must have at least 1 white space before '#' if the comment is after value.
	# comment            >> Comment is appropriately parsed.
	FOO=BAR # comment    >> Comment is appropriately parsed.
	FOO=BAR# comment     >> '#' is not parsed as comment. It considered as a part of value.

Escapes:
	# '\\' can be used for escaping character following the 3 rules.
	# 1. '\\' always escapes special character of ', ", \\, #
	# 2. '\\' is ignored when it is not in the scope of single or double quotes.
	# 3. '\\'n or "\n" in the scope of single or doubles quotes results in line breaks of LF.
	FOO=B\"R      >> B"R
	FOO=B\'R      >> B'A
	FOO="B\"R"    >> B"R
	FOO=B\R       >> BR (Its not in a scope of single or double quotes.)
	FOO="B\nR"    >> B<LF>R (\n is, if in a scope of quotes, converted into a line break.)

Environmental variables:
	# LoadEnv resolves environmental variables.
	FOO=BAR${BAZ}

func OpenFileReadOnly

func OpenFileReadOnly(path string) (*os.File, error)

OpenFileReadOnly returns the file with read-only mode. Unlike os.Open, it use os.ModePerm for permission. See also io.OpenFile, os.Open.

func OpenFileReadWrite

func OpenFileReadWrite(path string) (*os.File, error)

OpenFileReadWrite returns the file with read write mode. See also os.MkdirAll and io.OpenFile. It

  • creates directory if not exists.
  • creates file if not exists.
  • opens file with append mode.

func OpenFileWriteOnly

func OpenFileWriteOnly(path string) (*os.File, error)

OpenFileWriteOnly returns the file with write-only mode. Unlike os.Create, it use os.ModePerm for permission. See also os.MkdirAll, io.OpenFile and os.Create. It

  • creates directory if not exists.
  • creates file if not exists.
  • opens file with append mode.

func ReadFiles

func ReadFiles(recursive bool, paths ...string) (map[string][]byte, error)

ReadFiles reads files of the given paths. Paths can be absolute or relative, and file or directory. It check all sub directories and read files if the first argument recursive is true. It returns an empty map and nil error if no files found. note that paths in the map key are cleaned by filepath.Clean.

func ResolveEnv

func ResolveEnv(in []byte) ([]byte, error)

ResolveEnv substitutes a single environmental variable expression. Supported expressions are listed below. Expressions are basically derived from shell parameter substitution. Note that the substitution behavior is NOT exactly the same as bash.

Rules:

Expressions:
  01: ${parameter}                  --- See the substitution rule table below.
  02: ${parameter:-word}            --- See the substitution rule table below.
  03: ${parameter-word}             --- See the substitution rule table below.
  04: ${parameter:=word}            --- See the substitution rule table below.
  05: ${parameter=word}             --- See the substitution rule table below.
  06: ${parameter:?word}            --- See the substitution rule table below.
  07: ${parameter?word}             --- See the substitution rule table below.
  08: ${parameter:+word}            --- See the substitution rule table below.
  09: ${parameter+word}             --- See the substitution rule table below.
  10: ${parameter:offset}           --- Trim characters before offset.
  11: ${parameter:offset:length}    --- Trim characters before offset and after offset+length.
  12: ${!prefix*}                   --- Join the parameter name which has the prefix with a white space (Same with ${!prefix*}).
  13: ${!prefix@}                   --- Currently fallback to #12.
  14: ${#parameter}                 --- Length of value.
  15: ${parameter#word}             --- Currently fallback to #16.
  16: ${parameter##word}            --- Remove prefix of the value which matched to the word. Longest match if pattern specified.
  17: ${parameter%word}             --- Currently fallback to #18.
  18: ${parameter%%word}            --- Remove suffix of the value which matched to the word. Longest match if pattern specified.
  19: ${parameter/pattern/string}   --- Replace the first value which matched to the pattern to string.
  20: ${parameter//pattern/string}  --- Replace all values which matched to the pattern to string.
  21: ${parameter/#pattern/string}  --- Replace the prefix to string if matched to the pattern.
  22: ${parameter/%pattern/string}  --- Replace the suffix to string if matched to the pattern.
  23: ${parameter^pattern}          --- Convert initial character to upper case if matched to the pattern.
  24: ${parameter^^pattern}         --- Convert all characters which matched to the pattern to upper case.
  25: ${parameter,pattern}          --- Convert initial character to lower case if matched to the pattern.
  26: ${parameter,,pattern}         --- Convert all characters which matched to the pattern to lower case.
  27: ${parameter@operator}         --- Process value with the operator.

Substitution rules:
  |  #  |     expression     |    parameter Set     |  parameter Set  | parameter Unset |
  |     |                    |    and Not Null      |    But Null     |                 |
  | --- | ------------------ | -------------------- | --------------- | --------------- |
  | 01  | ${parameter}       | substitute parameter | substitute null | substitute null |
  | 02  | ${parameter:-word} | substitute parameter | substitute word | substitute word |
  | 03  | ${parameter-word}  | substitute parameter | substitute null | substitute word |
  | 04  | ${parameter:=word} | substitute parameter | substitute word | assign word     |
  | 05  | ${parameter=word}  | substitute parameter | substitute null | assign word     |
  | 06  | ${parameter:?word} | substitute parameter | error           | error           |
  | 07  | ${parameter?word}  | substitute parameter | substitute null | error           |
  | 08  | ${parameter:+word} | substitute word      | substitute null | substitute null |
  | 09  | ${parameter+word}  | substitute word      | substitute word | substitute null |

parameter:
  [0-9a-zA-Z_]+

word:
  [^\$]*

pattern:
  c       : matches to the character ('$' is not allowed).
  [a-z]   : matches specified character range.
  .*      : matches any length of characters.
  .?      : matches zero or single characters.

operator:
  U       : convert all characters to upper case using [strings.ToUpper]
  u       : convert the first character to upper case using [strings.ToUpper]
  L       : convert all characters to lower case using [strings.ToLower]
  l       : convert the first character to lower case using [strings.ToLower]
Example
package main

import (
	"fmt"
	"os"

	"github.com/aileron-projects/go/zos"
)

func main() {
	os.Setenv("ABC", "abcdefg")
	os.Setenv("FOO", "foo")
	os.Setenv("BAR", "BAR")
	os.Setenv("ARR_X", "xxx")
	os.Setenv("ARR_Y", "yyy")

	must := func(b []byte, err error) string {
		if err != nil {
			panic(err)
		}
		return string(b)
	}
	fmt.Println("${FOO} ------------", must(zos.ResolveEnv([]byte("${FOO}"))))
	fmt.Println("${BAZ:-default} ---", must(zos.ResolveEnv([]byte("${BAZ:-default}"))))
	fmt.Println("${BAZ-default}  ---", must(zos.ResolveEnv([]byte("${BAZ-default}"))))
	fmt.Println("${BAZ:=default} ---", must(zos.ResolveEnv([]byte("${BAZ:=default}"))))
	fmt.Println("${BAZ=default}  ---", must(zos.ResolveEnv([]byte("${BAZ=default}"))))
	fmt.Println("${BAZ:?default} ---", must(zos.ResolveEnv([]byte("${BAZ:?default}"))))
	fmt.Println("${BAZ?default}  ---", must(zos.ResolveEnv([]byte("${BAZ?default}"))))
	fmt.Println("${BAZ:+default} ---", must(zos.ResolveEnv([]byte("${BAZ:+default}"))))
	fmt.Println("${BAZ+default}  ---", must(zos.ResolveEnv([]byte("${BAZ+default}"))))
	fmt.Println("${ABC:3} ----------", must(zos.ResolveEnv([]byte("${ABC:3}"))))
	fmt.Println("${ABC:3:3} --------", must(zos.ResolveEnv([]byte("${ABC:3:3}"))))
	fmt.Println("${!ARR*} ----------", must(zos.ResolveEnv([]byte("${!ARR*}"))))
	fmt.Println("${!ARR@} ----------", must(zos.ResolveEnv([]byte("${!ARR@}"))))
	fmt.Println("${#FOO} ----------", must(zos.ResolveEnv([]byte("${#FOO}"))))
	fmt.Println("${FOO#[a-z]} -----", must(zos.ResolveEnv([]byte("${FOO#[a-z]}"))))
	fmt.Println("${FOO##[a-z]} ----", must(zos.ResolveEnv([]byte("${FOO##[a-z]}"))))
	fmt.Println("${FOO%[a-z]} -----", must(zos.ResolveEnv([]byte("${FOO%[a-z]}"))))
	fmt.Println("${FOO%%[a-z]} ----", must(zos.ResolveEnv([]byte("${FOO%%[a-z]}"))))
	fmt.Println("${FOO/[a-z]/x} ---", must(zos.ResolveEnv([]byte("${FOO/[a-z]/x}"))))
	fmt.Println("${FOO//[a-z]/x} --", must(zos.ResolveEnv([]byte("${FOO//[a-z]/x}"))))
	fmt.Println("${FOO/#[a-z]/x} --", must(zos.ResolveEnv([]byte("${FOO/#[a-z]/x}"))))
	fmt.Println("${FOO/%[a-z]/x} --", must(zos.ResolveEnv([]byte("${FOO/%[a-z]/x}"))))
	fmt.Println("${FOO^[f]} -------", must(zos.ResolveEnv([]byte("${FOO^[f]}"))))
	fmt.Println("${FOO^^[o]} ------", must(zos.ResolveEnv([]byte("${FOO^^[o]}"))))
	fmt.Println("${BAR,[B]} -------", must(zos.ResolveEnv([]byte("${BAR,[B]}"))))
	fmt.Println("${BAR,,[A]} ------", must(zos.ResolveEnv([]byte("${BAR,,[A]}"))))
	fmt.Println("${FOO@U} ---------", must(zos.ResolveEnv([]byte("${FOO@U}"))))
}
Output:

${FOO} ------------ foo
${BAZ:-default} --- default
${BAZ-default}  --- default
${BAZ:=default} --- default
${BAZ=default}  --- default
${BAZ:?default} --- default
${BAZ?default}  --- default
${BAZ:+default} --- default
${BAZ+default}  --- default
${ABC:3} ---------- defg
${ABC:3:3} -------- def
${!ARR*} ---------- ARR_X ARR_Y
${!ARR@} ---------- ARR_X ARR_Y
${#FOO} ---------- 3
${FOO#[a-z]} ----- oo
${FOO##[a-z]} ---- oo
${FOO%[a-z]} ----- fo
${FOO%%[a-z]} ---- fo
${FOO/[a-z]/x} --- xoo
${FOO//[a-z]/x} -- xxx
${FOO/#[a-z]/x} -- xoo
${FOO/%[a-z]/x} -- fox
${FOO^[f]} ------- Foo
${FOO^^[o]} ------ fOO
${BAR,[B]} ------- bAR
${BAR,,[A]} ------ BaR
${FOO@U} --------- FOO

Types

type EnvError

type EnvError struct {
	Err     error
	Type    string
	Pattern string
	Info    string
}

EnvError is the environmental substitution error.

func (*EnvError) Error

func (e *EnvError) Error() string

func (*EnvError) Is

func (e *EnvError) Is(err error) bool

func (*EnvError) Unwrap

func (e *EnvError) Unwrap() error

Jump to

Keyboard shortcuts

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