From a80c059baeff0b7d563499e963059a183c7e1bd9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 8 Dec 2013 06:06:05 +0000 Subject: [PATCH] Engine: break out Env utilities into their own type - Env --- engine/engine.go | 1 + engine/env.go | 221 +++++++++++++++++++++++++++++++++++++++++++++++ engine/job.go | 140 ++++-------------------------- 3 files changed, 237 insertions(+), 125 deletions(-) create mode 100644 engine/env.go diff --git a/engine/engine.go b/engine/engine.go index 34b28f64db..44facf5277 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -111,6 +111,7 @@ func (eng *Engine) Job(name string, args ...string) *Job { Stdin: NewInput(), Stdout: NewOutput(), Stderr: NewOutput(), + env: &Env{}, } job.Stdout.Add(utils.NopWriteCloser(eng.Stdout)) job.Stderr.Add(utils.NopWriteCloser(eng.Stderr)) diff --git a/engine/env.go b/engine/env.go new file mode 100644 index 0000000000..5e4f59395a --- /dev/null +++ b/engine/env.go @@ -0,0 +1,221 @@ +package engine + +import ( + "strings" + "io" + "encoding/json" + "strconv" + "bytes" + "fmt" +) + +type Env []string + + +func (env *Env) Get(key string) (value string) { + // FIXME: use Map() + for _, kv := range *env { + if strings.Index(kv, "=") == -1 { + continue + } + parts := strings.SplitN(kv, "=", 2) + if parts[0] != key { + continue + } + if len(parts) < 2 { + value = "" + } else { + value = parts[1] + } + } + return +} + +func (env *Env) Exists(key string) bool { + _, exists := env.Map()[key] + return exists +} + +func (env *Env) GetBool(key string) (value bool) { + s := strings.ToLower(strings.Trim(env.Get(key), " \t")) + if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { + return false + } + return true +} + + +func (env *Env) SetBool(key string, value bool) { + if value { + env.Set(key, "1") + } else { + env.Set(key, "0") + } +} + +func (env *Env) GetInt(key string) int64 { + s := strings.Trim(env.Get(key), " \t") + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return -1 + } + return val +} + +func (env *Env) SetInt(key string, value int64) { + env.Set(key, fmt.Sprintf("%d", value)) +} + +// Returns nil if key not found +func (env *Env) GetList(key string) []string { + sval := env.Get(key) + if sval == "" { + return nil + } + l := make([]string, 0, 1) + if err := json.Unmarshal([]byte(sval), &l); err != nil { + l = append(l, sval) + } + return l +} + +func (env *Env) SetJson(key string, value interface{}) error { + sval, err := json.Marshal(value) + if err != nil { + return err + } + env.Set(key, string(sval)) + return nil +} + +func (env *Env) SetList(key string, value []string) error { + return env.SetJson(key, value) +} + +func (env *Env) Set(key, value string) { + *env = append(*env, key+"="+value) +} + +func NewDecoder(src io.Reader) *Decoder { + return &Decoder{ + json.NewDecoder(src), + } +} + +type Decoder struct { + *json.Decoder +} + +func (decoder *Decoder) Decode() (*Env, error) { + m := make(map[string]interface{}) + if err := decoder.Decoder.Decode(&m); err != nil { + return nil, err + } + env := &Env{} + for key, value := range m { + env.SetAuto(key, value) + } + return env, nil +} + +// DecodeEnv decodes `src` as a json dictionary, and adds +// each decoded key-value pair to the environment. +// +// If `src` cannot be decoded as a json dictionary, an error +// is returned. +func (env *Env) Decode(src io.Reader) error { + m := make(map[string]interface{}) + if err := json.NewDecoder(src).Decode(&m); err != nil { + return err + } + for k, v := range m { + env.SetAuto(k, v) + } + return nil +} + +func (env *Env) SetAuto(k string, v interface{}) { + // FIXME: we fix-convert float values to int, because + // encoding/json decodes integers to float64, but cannot encode them back. + // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + if fval, ok := v.(float64); ok { + env.SetInt(k, int64(fval)) + } else if sval, ok := v.(string); ok { + env.Set(k, sval) + } else if val, err := json.Marshal(v); err == nil { + env.Set(k, string(val)) + } else { + env.Set(k, fmt.Sprintf("%v", v)) + } +} + +func (env *Env) Encode(dst io.Writer) error { + m := make(map[string]interface{}) + for k, v := range env.Map() { + var val interface{} + if err := json.Unmarshal([]byte(v), &val); err == nil { + // FIXME: we fix-convert float values to int, because + // encoding/json decodes integers to float64, but cannot encode them back. + // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) + if fval, isFloat := val.(float64); isFloat { + val = int(fval) + } + m[k] = val + } else { + m[k] = v + } + } + if err := json.NewEncoder(dst).Encode(&m); err != nil { + return err + } + return nil +} + +func (env *Env) WriteTo(dst io.Writer) (n int64, err error) { + // FIXME: return the number of bytes written to respect io.WriterTo + return 0, env.Encode(dst) +} + +func (env *Env) Export(dst interface{}) (err error) { + defer func() { + if err != nil { + err = fmt.Errorf("ExportEnv %s", err) + } + }() + var buf bytes.Buffer + // step 1: encode/marshal the env to an intermediary json representation + if err := env.Encode(&buf); err != nil { + return err + } + // step 2: decode/unmarshal the intermediary json into the destination object + if err := json.NewDecoder(&buf).Decode(dst); err != nil { + return err + } + return nil +} + +func (env *Env) Import(src interface{}) (err error) { + defer func() { + if err != nil { + err = fmt.Errorf("ImportEnv: %s", err) + } + }() + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(src); err != nil { + return err + } + if err := env.Decode(&buf); err != nil { + return err + } + return nil +} + +func (env *Env) Map() map[string]string { + m := make(map[string]string) + for _, kv := range *env { + parts := strings.SplitN(kv, "=", 2) + m[parts[0]] = parts[1] + } + return m +} + diff --git a/engine/job.go b/engine/job.go index 066a144664..478f06cd60 100644 --- a/engine/job.go +++ b/engine/job.go @@ -1,11 +1,8 @@ package engine import ( - "bytes" - "encoding/json" "fmt" "io" - "strconv" "strings" "time" ) @@ -27,7 +24,7 @@ type Job struct { Eng *Engine Name string Args []string - env []string + env *Env Stdout *Output Stderr *Output Stdin *Input @@ -105,80 +102,40 @@ func (job *Job) String() string { } func (job *Job) Getenv(key string) (value string) { - for _, kv := range job.env { - if strings.Index(kv, "=") == -1 { - continue - } - parts := strings.SplitN(kv, "=", 2) - if parts[0] != key { - continue - } - if len(parts) < 2 { - value = "" - } else { - value = parts[1] - } - } - return + return job.env.Get(key) } func (job *Job) GetenvBool(key string) (value bool) { - s := strings.ToLower(strings.Trim(job.Getenv(key), " \t")) - if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { - return false - } - return true + return job.env.GetBool(key) } func (job *Job) SetenvBool(key string, value bool) { - if value { - job.Setenv(key, "1") - } else { - job.Setenv(key, "0") - } + job.env.SetBool(key, value) } func (job *Job) GetenvInt(key string) int64 { - s := strings.Trim(job.Getenv(key), " \t") - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return -1 - } - return val + return job.env.GetInt(key) } func (job *Job) SetenvInt(key string, value int64) { - job.Setenv(key, fmt.Sprintf("%d", value)) + job.env.SetInt(key, value) } // Returns nil if key not found func (job *Job) GetenvList(key string) []string { - sval := job.Getenv(key) - if sval == "" { - return nil - } - l := make([]string, 0, 1) - if err := json.Unmarshal([]byte(sval), &l); err != nil { - l = append(l, sval) - } - return l + return job.env.GetList(key) } func (job *Job) SetenvJson(key string, value interface{}) error { - sval, err := json.Marshal(value) - if err != nil { - return err - } - job.Setenv(key, string(sval)) - return nil + return job.env.SetJson(key, value) } func (job *Job) SetenvList(key string, value []string) error { - return job.SetenvJson(key, value) + return job.env.SetJson(key, value) } func (job *Job) Setenv(key, value string) { - job.env = append(job.env, key+"="+value) + job.env.Set(key, value) } // DecodeEnv decodes `src` as a json dictionary, and adds @@ -187,90 +144,23 @@ func (job *Job) Setenv(key, value string) { // If `src` cannot be decoded as a json dictionary, an error // is returned. func (job *Job) DecodeEnv(src io.Reader) error { - m := make(map[string]interface{}) - if err := json.NewDecoder(src).Decode(&m); err != nil { - return err - } - for k, v := range m { - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) - if fval, ok := v.(float64); ok { - job.SetenvInt(k, int64(fval)) - } else if sval, ok := v.(string); ok { - job.Setenv(k, sval) - } else if val, err := json.Marshal(v); err == nil { - job.Setenv(k, string(val)) - } else { - job.Setenv(k, fmt.Sprintf("%v", v)) - } - } - return nil + return job.env.Decode(src) } func (job *Job) EncodeEnv(dst io.Writer) error { - m := make(map[string]interface{}) - for k, v := range job.Environ() { - var val interface{} - if err := json.Unmarshal([]byte(v), &val); err == nil { - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See http://golang.org/src/pkg/encoding/json/decode.go#L46) - if fval, isFloat := val.(float64); isFloat { - val = int(fval) - } - m[k] = val - } else { - m[k] = v - } - } - if err := json.NewEncoder(dst).Encode(&m); err != nil { - return err - } - return nil + return job.env.Encode(dst) } func (job *Job) ExportEnv(dst interface{}) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("ExportEnv %s", err) - } - }() - var buf bytes.Buffer - // step 1: encode/marshal the env to an intermediary json representation - if err := job.EncodeEnv(&buf); err != nil { - return err - } - // step 2: decode/unmarshal the intermediary json into the destination object - if err := json.NewDecoder(&buf).Decode(dst); err != nil { - return err - } - return nil + return job.env.Export(dst) } func (job *Job) ImportEnv(src interface{}) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("ImportEnv: %s", err) - } - }() - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(src); err != nil { - return err - } - if err := job.DecodeEnv(&buf); err != nil { - return err - } - return nil + return job.env.Import(src) } func (job *Job) Environ() map[string]string { - m := make(map[string]string) - for _, kv := range job.env { - parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = parts[1] - } - return m + return job.env.Map() } func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {