Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/output/invalid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package output

import (
"fmt"
)

type InvalidOutput struct {
Config Config
}

func (o *InvalidOutput) GenerateOutput(elements interface{}) (string, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, here you're 'struct typing', rather than explicitly declaring an type Outputer interface, right?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

return "", fmt.Errorf("Unrecognized output format: %s", o.Config.Format)
}
30 changes: 30 additions & 0 deletions lib/output/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package output

import (
"encoding/json"
"fmt"
)

type JSONOutput struct {
Config Config
}

func (o *JSONOutput) GenerateOutput(elements interface{}) (string, error) {
if _, ok := elements.([]interface{}); ok {
if j, err := json.MarshalIndent(elements, " ", " "); err != nil {
return "", err
} else {
return string(j), nil
}
} else {
if _, ok := elements.(map[string]interface{}); ok {
if j, err := json.MarshalIndent(elements, " ", " "); err != nil {
return "", err
} else {
return string(j), nil
}
} else {
return fmt.Sprintf("%s", elements), nil
}
}
}
119 changes: 13 additions & 106 deletions lib/output/output.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package output

import (
"encoding/json"
"fmt"
"sort"
"strings"
)
type OutputBuilder interface {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, so we are declaring a public interface here... This is intended to communicate to implementors of new output types that their output struct, say xmlOutput, should minimally receive a GenerateOutput method, right?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe implementations have to match exactly the same number of methods as well as exact input and output types.

In this case, if a struct would implement anything other than GenerateOutput, it would no longer conform to the interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 'struct typing' allows any struct that minimally implements the interface, but may implement methods outside the interface, to be used as the interface. For example: https://play.golang.org/p/2JS1qC9kca

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - I did not know that :)

GenerateOutput(interface{}) (string, error)
}

type Config struct {
Format string
Expand All @@ -20,111 +17,21 @@ func (o *Output) Generate(elements interface{}) (string, error) {
return "", nil
}

var v OutputBuilder
switch o.Config.Format {
case "json":
return o.JSONOutput(elements)
case "shell":
return o.ShellOutput(elements)
}

return "", fmt.Errorf("Unrecognized output format: %s", o.Config.Format)
}

func (o *Output) JSONOutput(elements interface{}) (string, error) {
if _, ok := elements.([]interface{}); ok {
if j, err := json.MarshalIndent(elements, " ", " "); err != nil {
return "", err
} else {
return string(j), nil
}
} else {
if _, ok := elements.(map[string]interface{}); ok {
if j, err := json.MarshalIndent(elements, " ", " "); err != nil {
return "", err
} else {
return string(j), nil
}
} else {
return fmt.Sprintf("%s", elements), nil
}
}
}

func (o *Output) ShellOutput(elements interface{}) (string, error) {
var keys []string
var output string

parsed := make(map[string]string)
_, _, parsed = parseElementsForShell("elements", elements, parsed)

for k := range parsed {
keys = append(keys, k)
}

sort.Strings(keys)

for _, k := range keys {
output = fmt.Sprintf("%s%s=\"%s\"\n", output, k, parsed[k])
}

return output, nil
}

func parseElementsForShell(label string, elements interface{}, parsed map[string]string) (string, interface{}, map[string]string) {
var e interface{}
switch elements.(type) {
case map[string]interface{}:
for key, subElements := range elements.(map[string]interface{}) {
l := fmt.Sprintf("%s_%s", label, sanitizeKeyForShell(key))
_, e, parsed = parseElementsForShell(l, subElements, parsed)
v = &JSONOutput{
Config: o.Config,
}
case []interface{}:
for i, subElements := range elements.([]interface{}) {
l := fmt.Sprintf("%s_%d", label, i)
_, e, parsed = parseElementsForShell(l, subElements, parsed)
case "shell":
v = &ShellOutput{
Config: o.Config,
}
default:
parsed[label] = fmt.Sprintf("%v", elements)
return label, nil, parsed
v = &InvalidOutput{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safe zero value/Null Object pattern. Makes sense.

Config: o.Config,
}
}

return label, e, parsed
}

func sanitizeKeyForShell(key string) string {
// replace non-compliant shell characters with underscores,
// per IEEE Std 1003.1-2001
r := strings.NewReplacer(
"`", "_",
"~", "_",
"!", "_",
"@", "_",
"#", "_",
"$", "_",
"%", "_",
"^", "_",
"&", "_",
"*", "_",
"(", "_",
")", "_",
"-", "_",
"+", "_",
"=", "_",
"{", "_",
"}", "_",
"[", "_",
"]", "_",
"|", "_",
":", "_",
";", "_",
"'", "_",
"\"", "_",
"/", "_",
"?", "_",
",", "_",
".", "_",
"<", "_",
">", "_",
)
return r.Replace(key)
return v.GenerateOutput(elements)
}
90 changes: 90 additions & 0 deletions lib/output/shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package output

import (
"fmt"
"sort"
"strings"
)

type ShellOutput struct {
Config Config
}

func (o *ShellOutput) GenerateOutput(elements interface{}) (string, error) {
var keys []string
var output string

parsed := make(map[string]string)
_, _, parsed = parseElementsForShell("elements", elements, parsed)

for k := range parsed {
keys = append(keys, k)
}

sort.Strings(keys)

for _, k := range keys {
output = fmt.Sprintf("%s%s=\"%s\"\n", output, k, parsed[k])
}

return output, nil
}

func parseElementsForShell(label string, elements interface{}, parsed map[string]string) (string, interface{}, map[string]string) {
var e interface{}
switch elements.(type) {
case map[string]interface{}:
for key, subElements := range elements.(map[string]interface{}) {
l := fmt.Sprintf("%s_%s", label, sanitizeKeyForShell(key))
_, e, parsed = parseElementsForShell(l, subElements, parsed)
}
case []interface{}:
for i, subElements := range elements.([]interface{}) {
l := fmt.Sprintf("%s_%d", label, i)
_, e, parsed = parseElementsForShell(l, subElements, parsed)
}
default:
parsed[label] = fmt.Sprintf("%v", elements)
return label, nil, parsed
}

return label, e, parsed
}

func sanitizeKeyForShell(key string) string {
// replace non-compliant shell characters with underscores,
// per IEEE Std 1003.1-2001
r := strings.NewReplacer(
"`", "_",
"~", "_",
"!", "_",
"@", "_",
"#", "_",
"$", "_",
"%", "_",
"^", "_",
"&", "_",
"*", "_",
"(", "_",
")", "_",
"-", "_",
"+", "_",
"=", "_",
"{", "_",
"}", "_",
"[", "_",
"]", "_",
"|", "_",
":", "_",
";", "_",
"'", "_",
"\"", "_",
"/", "_",
"?", "_",
",", "_",
".", "_",
"<", "_",
">", "_",
)
return r.Replace(key)
}