Skip to content

Commit

Permalink
refmt: add options and transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
rjeczalik committed Apr 8, 2022
1 parent 2bd25f6 commit 9671524
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 17 deletions.
127 changes: 110 additions & 17 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
Expand Down Expand Up @@ -38,26 +39,85 @@ func (c *envCodec) codec() codec {
}
}

type Options map[string]string

func parseOptions(key string) (string, Options) {
opts := make(Options)

for i, j := 0, 0; ; {
if i = strings.IndexByte(key, '['); j == -1 {
break
}

if j = strings.IndexByte(key, ']'); i == -1 {
break
}

k, v := key[i+1:j], ""

if l := strings.IndexByte(k, '='); l != -1 {
k, v = k[:l], v[l+1:]
}

opts[k] = v

key = key[:i] + key[j+1:]
}

return key, opts
}

func executeOptions(v interface{}, opts Options, marshal bool) interface{} {
if _, ok := opts["b64"]; ok {
if s, ok := v.(string); ok {
if marshal {
if _, err := base64.StdEncoding.DecodeString(s); err == nil {
return s
}

return base64.StdEncoding.EncodeToString([]byte(s))
}

if p, err := base64.StdEncoding.DecodeString(s); err == nil {
return string(p)
}

return s
}
}

return v
}

func transform(marshal bool) func(map[string]interface{}, string) {
return func(m map[string]interface{}, key string) {
v := m[key]

k, opts := parseOptions(key)
v = executeOptions(v, opts, marshal)

delete(m, key)
m[k] = v
}
}

func (c *envCodec) marshal(v interface{}) ([]byte, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, errors.New("envCoded: cannot marshal non-object value")
}

envs := object.Flatten(m, "_")

var (
p = *c.prefix
envs = object.Flatten(m, "_")
keys = object.Keys(envs)
buf bytes.Buffer
)

for _, k := range keys {
v := fmt.Sprintf("%q", envs[k])

if _, ok := envs[k].(string); ok {
v = strings.Trim(v, `"`)
}
for _, key := range keys {
v := envs[key]
k, opts := parseOptions(key)
v = executeOptions(v, opts, true)

fmt.Fprintf(&buf, "%s%s=%s\n", p, strings.ToUpper(k), v)
}
Expand All @@ -76,6 +136,7 @@ type codec struct {

type jsonCodec struct {
compact *bool
b64 *bool
}

func (c *jsonCodec) codec() codec {
Expand All @@ -86,6 +147,10 @@ func (c *jsonCodec) codec() codec {
}

func (c *jsonCodec) marshal(v interface{}) ([]byte, error) {
if m, ok := v.(map[string]interface{}); ok {
object.Walk(m, transform(true))
}

if *c.compact {
return json.Marshal(v)
}
Expand All @@ -97,24 +162,46 @@ func (c *jsonCodec) unmarshal(p []byte) (v interface{}, _ error) {
if err := json.Unmarshal(p, &v); err != nil {
return nil, err
}
if m, ok := v.(map[string]interface{}); ok {
object.Walk(m, transform(false))
}
return v, nil
}

type yamlCodec struct{}

func (c *yamlCodec) codec() codec {
return codec{
marshal: c.marshal,
unmarshal: c.unmarshal,
}
}

func (c *yamlCodec) marshal(v interface{}) ([]byte, error) {
if m, ok := v.(map[string]interface{}); ok {
object.Walk(m, transform(true))
}
return yaml.Marshal(v)
}

func (c *yamlCodec) unmarshal(p []byte) (v interface{}, _ error) {
if err := yaml.Unmarshal(p, &v); err != nil {
return nil, err
}

return object.FixYAML(v), nil
}

var m = map[string]codec{
"json": (&jsonCodec{
compact: flag.Bool("c", false, "One-line output for JSON format."),
}).codec(),
"yaml": {
marshal: yaml.Marshal,
unmarshal: func(p []byte) (v interface{}, _ error) {
if err := yaml.Unmarshal(p, &v); err != nil {
return nil, err
}
return object.FixYAML(v), nil
},
},
"yaml": new(yamlCodec).codec(),
"hcl": {
marshal: func(v interface{}) ([]byte, error) {
if m, ok := v.(map[string]interface{}); ok {
object.Walk(m, transform(true))
}
p, err := jsonMarshal(v)
if err != nil {
return nil, err
Expand All @@ -134,6 +221,9 @@ var m = map[string]codec{
return nil, err
}
object.FixHCL(v)
if m, ok := v.(map[string]interface{}); ok {
object.Walk(m, transform(false))
}
return v, nil
},
},
Expand Down Expand Up @@ -333,6 +423,9 @@ func (f *Format) unmarshal(file string) (v interface{}, err error) {
if err != nil {
return nil, err
}
if q, err := base64.StdEncoding.DecodeString(string(p)); err == nil {
p = q
}
if t := typ(file); t != "" {
return m[t].unmarshal(p)
}
Expand Down
53 changes: 53 additions & 0 deletions object/walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package object

import "fmt"

func Walk(m map[string]interface{}, fn func(map[string]interface{}, string)) {
type elm struct {
parent map[string]interface{}
key []string
left []string
}

var (
it elm
k string
queue = []elm{{parent: m, left: Keys(m)}}
)

for len(queue) != 0 {
it, queue = queue[len(queue)-1], queue[:len(queue)-1]
k, it.left = it.left[0], it.left[1:]

key := clone(it.key, k)

if len(it.left) != 0 {
queue = append(queue, it)
}

switch v := it.parent[k].(type) {
case []interface{}:
m := make(map[string]interface{}, len(v))

for i, v := range v {
m[fmt.Sprint(i)] = v
}

queue = append(queue, elm{
parent: m,
key: key,
left: Keys(m),
})
case map[string]interface{}:
queue = append(queue, elm{
parent: v,
key: key,
left: Keys(v),
})
default:
if len(key) != 0 {
fn(it.parent, k)
}
}
}
}

0 comments on commit 9671524

Please sign in to comment.