Skip to content

Commit

Permalink
Added the ability to convert some customized slices as maps (#10)
Browse files Browse the repository at this point in the history
* Ability for convert some configured slices as maps is added

* Some comments

* Some fixes for generate slices

* Tests are fixed

* comment is added

Co-authored-by: Konstantin Zhernosenko <konstantin.zhernosenko@humans.net>
  • Loading branch information
kainobor and Konstantin Zhernosenko authored Aug 13, 2021
1 parent d0330ab commit 05c061f
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 50 deletions.
161 changes: 139 additions & 22 deletions lib/cimp/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ type KV struct {

type index map[string]tree.Path

type treeConverter struct {
Format FileFormat
Indent int
type TreeConverter interface {
Convert(tree.Tree) (*tree.Tree, error)
}

type branchesToStringConverter struct {
Format FileFormat
Indent int
Exceptions map[string]string
}

type branchesToTreeConverter struct {
branchPathToBranchElementFieldName map[string]string
onlyKeys bool // it'll convert only keys as for tree. Don't use converted tree after that!!! It'll be broken and can be only marshalled.
}

const consulSep = "/"
Expand Down Expand Up @@ -159,13 +169,14 @@ func (kv *KV) DeepClone() *KV {
return newKV
}

func (kv *KV) ConvertBranchesToString(format FileFormat, indent int) error {
tc := treeConverter{
Format: format,
Indent: indent,
func (kv *KV) ConvertBranchesToString(format FileFormat, indent int, exceptions map[string]string) error {
tc := branchesToStringConverter{
Format: format,
Indent: indent,
Exceptions: exceptions,
}

convertedTree, err := tc.convertBranchesToString(kv.tree)
convertedTree, err := tc.Convert(kv.tree)
if err != nil {
return fmt.Errorf("convert branches to string: %w", err)
}
Expand All @@ -174,6 +185,38 @@ func (kv *KV) ConvertBranchesToString(format FileFormat, indent int) error {
return nil
}

func (kv *KV) ConvertBranchesToTree(branchPathToBranchElementFieldName map[string]string) error {
tc := branchesToTreeConverter{
branchPathToBranchElementFieldName: branchPathToBranchElementFieldName,
onlyKeys: false,
}

convertedTree, err := tc.Convert(kv.tree)
if err != nil {
return fmt.Errorf("convert branches to trees: %w", err)
}
kv.SetTree(convertedTree)

return nil
}

// ConvertBranchesKeysAsForTree converts only keys.
// It can be useful before marshaling, but if you want to change values or something after that - keys may to be returned to default values.
func (kv *KV) ConvertBranchesKeysAsForTree(branchPathToBranchElementFieldName map[string]string) error {
tc := branchesToTreeConverter{
branchPathToBranchElementFieldName: branchPathToBranchElementFieldName,
onlyKeys: true,
}

convertedTree, err := tc.Convert(kv.tree)
if err != nil {
return fmt.Errorf("convert branches' keys as for tree: %w", err)
}
kv.SetTree(convertedTree)

return nil
}

func (kv *KV) ConvertTreeNamesToCamelCase() {
kv.setNamesToSnakeCase(kv.tree)
kv.idx.clear()
Expand Down Expand Up @@ -203,15 +246,18 @@ func (kv *KV) setNamesToSnakeCase(m tree.Marshalable) {
}
}

// ConvertBranchesToString walks recursively through the map and marshals all slices to strings
func (c treeConverter) convertBranchesToString(mt *tree.Tree) (*tree.Tree, error) {
// Convert walks recursively through the map and marshals all slices to strings
func (c branchesToStringConverter) Convert(mt *tree.Tree) (*tree.Tree, error) {
newTree := mt.ShallowClone()

for k, v := range mt.Content {
switch item := v.(type) {
case *tree.Leaf:
continue
continue // already added by ShallowClone
case *tree.Branch:
if _, ok := c.Exceptions[v.GetFullKey()]; ok {
continue
}
buf := bytes.Buffer{}
switch c.Format {
case JSONFormat:
Expand All @@ -229,20 +275,91 @@ func (c treeConverter) convertBranchesToString(mt *tree.Tree) (*tree.Tree, error
}

leaf := tree.NewLeaf(k, mt.FullKey)
leafValueBuf := bytes.NewBufferString("\n")
leafValueBuf.Write(buf.Bytes())
endLineAndIndent := []byte("\n" + strings.Repeat(" ", int(leaf.GetNestingLevel())*c.Indent))
leafValue := bytes.ReplaceAll(
leafValueBuf.Bytes(),
[]byte("\n"),
endLineAndIndent,
)
leafValue = bytes.TrimSuffix(leafValue, endLineAndIndent)
leaf.Value = string(leafValue)
if len(buf.Bytes()) > 0 && buf.Bytes()[0] == byte('[') {
leaf.Value = string(bytes.TrimSuffix(buf.Bytes(), []byte("\n")))
} else {
leafValueBuf := bytes.NewBufferString("\n")
leafValueBuf.Write(buf.Bytes())
endLineAndIndent := []byte("\n" + strings.Repeat(" ", int(leaf.GetNestingLevel())*c.Indent))
leafValue := bytes.ReplaceAll(
leafValueBuf.Bytes(),
[]byte("\n"),
endLineAndIndent,
)
leafValue = bytes.TrimSuffix(leafValue, endLineAndIndent)
leaf.Value = string(leafValue)
}

newTree.AddOrReplaceDirectly(k, leaf)
case *tree.Tree:
newItem, err := c.convertBranchesToString(item)
newItem, err := c.Convert(item)
if err != nil {
return nil, fmt.Errorf("convert branches of tree %q: %w", k, err)
}
newTree.AddOrReplaceDirectly(k, newItem)
}
}

return newTree, nil
}

// Convert walks recursively through the map and marshals needed slices to trees
func (c branchesToTreeConverter) Convert(mt *tree.Tree) (*tree.Tree, error) {
newTree := mt.ShallowClone()

for k, v := range mt.Content {
switch item := v.(type) {
case *tree.Leaf:
continue // already added by ShallowClone
case *tree.Branch:
fieldName, isBranchShouldBeConverted := c.branchPathToBranchElementFieldName[v.GetFullKey()]
if !isBranchShouldBeConverted {
continue
}

convertedTree := tree.NewSubTree(v.GetName(), mt.GetFullKey())
branchWithConvertedKeys := v.(*tree.Branch).DeepClone()
for elementIdx, branchElement := range v.(*tree.Branch).Content {
branchElementAsTree, isTree := branchElement.(*tree.Tree)
branchElementName := ""
if !isTree {
return nil, fmt.Errorf("branch %q should be converted to tree, but it's not branch of trees", v.GetFullKey())
}

isBranchElementHaveField := false
for _, treeElement := range branchElementAsTree.Content {
if treeElement.GetName() != fieldName {
continue
}
isBranchElementHaveField = true
treeElementAsLeaf, isTreeElementLeaf := treeElement.(*tree.Leaf)
if !isTreeElementLeaf {
return nil, fmt.Errorf("field %q of element #%d of branch %q is not a leaf", fieldName, elementIdx+1, v.GetFullKey())
}
treeElementValueAsString, isTreeElementValueString := treeElementAsLeaf.Value.(string)
if !isTreeElementValueString {
return nil, fmt.Errorf("field %q of element #%d of branch %q is not a string", fieldName, elementIdx+1, v.GetFullKey())
}
branchElementName = treeElementValueAsString
break
}
if !isBranchElementHaveField {
return nil, fmt.Errorf("branch element #%d of branch %q doesn't have field %q", elementIdx+1, v.GetFullKey(), fieldName)
}

if !c.onlyKeys {
convertedTree.AddOrReplaceDirectly(branchElementName, branchElementAsTree)
} else {
branchWithConvertedKeys.Content[elementIdx].ChangeName(branchElementName, v.GetFullKey())
}
}
if !c.onlyKeys {
newTree.AddOrReplaceDirectly(k, convertedTree)
} else {
newTree.AddOrReplaceDirectly(k, branchWithConvertedKeys)
}
case *tree.Tree:
newItem, err := c.Convert(item)
if err != nil {
return nil, fmt.Errorf("convert branches of tree %q: %w", k, err)
}
Expand Down
10 changes: 2 additions & 8 deletions lib/tree/fixtures/branch_hard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@
- true
- 16.466
- Level1: First
Level2:
- Two
- 2
Level2: [Two, 2]
"false":
"3": 18.7
"":
- Some
- other
- false
- things
"": [Some, other, false, things]
6 changes: 1 addition & 5 deletions lib/tree/fixtures/branch_simple.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
- one
- two
- three
- 4
- false
[one, two, three, 4, false]
10 changes: 2 additions & 8 deletions lib/tree/fixtures/tree_hard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ HardBranch:
- true
- 16.466
- Level1: First
Level2:
- Two
- 2
Level2: [Two, 2]
"false":
"3": 18.7
"":
- Some
- other
- false
- things
"": [Some, other, false, things]
8 changes: 1 addition & 7 deletions lib/tree/fixtures/tree_simple.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
LevelLast:
- 4
- 8
- 15
- 16
- 23
- 42
LevelLast: [4, 8, 15, 16, 23, 42]
90 changes: 90 additions & 0 deletions lib/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type Marshalable interface {
GetNestingLevel() int
Delete(string) error
IsEmpty() bool
ChangeName(name, parentFullKey string)
// changeFullKey will change the key recursively, but after that many things can be crashed.
// Don't use if you want to continue to work with tree.
changeFullKey(string)
}

type Tree struct {
Expand Down Expand Up @@ -307,13 +311,26 @@ func (mt *Tree) AddOrReplaceDirectly(name string, value Marshalable) {
if _, ok := mt.Content[name]; !ok {
mt.Order = append(mt.Order, name)
}

switch item := value.(type) {
case *Tree:
item.nestingLevel = mt.nestingLevel + 1
item.FullKey = MakeFullKey(mt.FullKey, name)
item.Name = name
for _, element := range item.Content {
item.AddOrReplaceDirectly(element.GetName(), element)
}
case *Branch:
item.nestingLevel = mt.nestingLevel + 1
item.FullKey = MakeFullKey(mt.FullKey, name)
item.Name = name
for idx, element := range item.Content {
item.AddOrReplaceDirectly(idx, element)
}
case *Leaf:
item.nestingLevel = mt.nestingLevel + 1
item.FullKey = MakeFullKey(mt.FullKey, name)
item.Name = name
}

mt.Content[name] = value
Expand All @@ -323,6 +340,46 @@ func (mb *Branch) Add(value Marshalable) {
mb.Content = append(mb.Content, value)
}

func (mb *Branch) AddOrReplaceDirectly(idx int, value Marshalable) {
if idx > len(mb.Content) {
idx = len(mb.Content)
}

newName := value.GetName()
// if current name is a number
if _, err := strconv.Atoi(newName); err == nil {
newName = strconv.Itoa(idx)
}

itemNewFullKey := MakeFullKey(mb.FullKey, newName)
switch item := value.(type) {
case *Tree:
item.nestingLevel = mb.nestingLevel + 1
item.FullKey = itemNewFullKey
item.Name = newName
for _, element := range item.Content {
item.AddOrReplaceDirectly(element.GetName(), element)
}
case *Branch:
item.nestingLevel = mb.nestingLevel + 1
item.FullKey = itemNewFullKey
item.Name = newName
for subIdx, element := range item.Content {
item.AddOrReplaceDirectly(subIdx, element)
}
case *Leaf:
item.nestingLevel = mb.nestingLevel + 1
item.FullKey = itemNewFullKey
item.Name = newName
}

if idx < len(mb.Content) {
mb.Content[idx] = value
} else {
mb.Content = append(mb.Content, value)
}
}

func (mt *Tree) ShallowClone() *Tree {
newOrder := make([]string, len(mt.Order))
copy(newOrder, mt.Order)
Expand Down Expand Up @@ -501,6 +558,21 @@ func (ml *Leaf) IsEmpty() bool {
return ml.Value == nil
}

func (mt *Tree) ChangeName(name string, parentFullKey string) {
mt.Name = name
mt.changeFullKey(MakeFullKey(parentFullKey, name))
}

func (mb *Branch) ChangeName(name string, parentFullKey string) {
mb.Name = name
mb.changeFullKey(MakeFullKey(parentFullKey, name))
}

func (ml *Leaf) ChangeName(name string, parentFullKey string) {
ml.Name = name
ml.changeFullKey(MakeFullKey(parentFullKey, name))
}

func initNestingLevel(parentFullKey string) int {
if len(parentFullKey) == 0 {
return 1
Expand Down Expand Up @@ -529,3 +601,21 @@ func (ml *Leaf) clearValues() {
ml.Value = nil
}
}

func (mt *Tree) changeFullKey(fullKey string) {
mt.FullKey = fullKey
for _, v := range mt.Content {
v.changeFullKey(MakeFullKey(fullKey, v.GetName()))
}
}

func (mb *Branch) changeFullKey(fullKey string) {
mb.FullKey = fullKey
for _, v := range mb.Content {
v.changeFullKey(MakeFullKey(fullKey, v.GetName()))
}
}

func (ml *Leaf) changeFullKey(fullKey string) {
ml.FullKey = fullKey
}
Loading

0 comments on commit 05c061f

Please sign in to comment.