Skip to content

Commit

Permalink
New flags: '-wrap' and '-unwrap'.
Browse files Browse the repository at this point in the history
Refactored the func 'remashal' to implement the new flags.
  • Loading branch information
dbohdan committed Oct 6, 2014
1 parent b15717b commit bb9ac46
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 36 deletions.
71 changes: 57 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,76 @@ commands `toml2yaml`, `toml2json`, `yaml2toml`, `yaml2json`. `json2toml` and

```
remarshal -if inputformat -of outputformat [-indent-json=(true|false)]
[-i inputfile] [-o outputfile]
[-i inputfile] [-o outputfile] [-wrap wrapper] [-unwrap wrapper]
```

where `inputformat` and `outputformat` can each be `toml`, `yaml` or
`json`.

```
toml2toml [-o outputfile] [[-i] inputfile]
yaml2toml [-o outputfile] [[-i] inputfile]
json2toml [-o outputfile] [[-i] inputfile]
toml2yaml [-o outputfile] [[-i] inputfile]
yaml2yaml [-o outputfile] [[-i] inputfile]
json2yaml [-o outputfile] [[-i] inputfile]
toml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
yaml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
json2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
toml2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
yaml2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
json2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
toml2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
yaml2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
json2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
toml2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
[-o outputfile] [[-i] inputfile]
yaml2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
[-o outputfile] [[-i] inputfile]
json2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
[-o outputfile] [[-i] inputfile]
```

The all of the commands above exit with status 0 on success and 1 on failure.
All of the commands above exit with status 0 on success and 1 on failure.

If no `inputfile` is given or it is `-` or a blank string the data to convert is
read from standard input. If no `outputfile` is given or it is `-` or a blank
string the result of the conversion is written to standard output.

For short commands (`x2y`) the flag `-i` before `inputfile` can be omitted if
`inputfile` is the last argument.
For the short commands (`x2y`) the flag `-i` before `inputfile` can be omitted
if `inputfile` is the last argument.

## Wrappers

The flags `-wrap` and `-unwrap` are there to solve the problem of converting
JSON and YAML data to TOML if the topmost element of that data is not of a map
type (i.e., not an object in JSON or an associative array in YAML) but a list, a
string or a number. Such data can not be represented as TOML directly; it needs
to wrapped in a map type first. Passing the flag `-wrap someKey` to `remarshal`
or one of its short commands wraps the input data in a "wrapper" map with one
key, "someKey", with the input data as its value. The flag `-unwrap someKey`
does the opposite: if it is specified only the value stored under the key
"someKey" in the top-level map element of the input data is converted to the
target format and output; all other data is skipped. If the top-level element is
not a map or does not have the key `someKey` then `-unwrap someKey` returns an
error.

The following shell transcript demonstrates the problem and how `-wrap` and
`-unwrap` solve it:

```
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | ./remarshal -if json -of toml
cannot convert data: top-level values must be a Go map or struct
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | \
./remarshal -if json -of toml -wrap main
[[main]]
a = "b"
[[main]]
c = [1, 2, 3]
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | \
./remarshal -if json -of toml -wrap main > test.toml
$ ./remarshal -if toml -of json -indent-json=0 < test.toml
{"main":[{"a":"b"},{"c":[1,2,3]}]}
$ ./remarshal -if toml -of json -indent-json=0 -unwrap main < test.toml
[{"a":"b"},{"c":[1,2,3]}]
```

# Building and installation

Expand Down Expand Up @@ -139,7 +182,7 @@ name = "Kiev"

# Known bugs

* Converting data with floating point values to YAML may cause the loss of
* Converting data with floating point values to YAML may cause a loss of
precision.

# License
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.3
0.3.0
83 changes: 62 additions & 21 deletions remarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ const (
fUnknown
)

const (
defaultFormatFlagValue = "unspecified"
defaultWrapFlagValue = "key"
)

// convertMapsToStringMaps recursively converts values of type
// map[interface{}]interface{} contained in item to map[string]interface{}. This
// is needed before the encoders for TOML and JSON can accept data returned by
Expand Down Expand Up @@ -58,7 +63,7 @@ func convertMapsToStringMaps(item interface{}) (res interface{}, err error) {

// convertNumberToInt64 recursively walks the structures contained in item
// converting values of the type json.Number to int64 or, failing that, float64.
// This approach is meant to prevent encoders putting numbers stored as
// This approach is meant to prevent encoders from putting numbers stored as
// json.Number in quotes or encoding large intergers in scientific notation.
func convertNumberToInt64(item interface{}) (res interface{}, err error) {
switch item.(type) {
Expand Down Expand Up @@ -104,7 +109,7 @@ func stringToFormat(s string) (f format, err error) {
return fYAML, nil
case "json":
return fJSON, nil
case "unknown":
case defaultFormatFlagValue:
return fPlaceholder, errors.New("placeholder format")
default:
return fUnknown, errors.New("cannot convert string to format: '" +
Expand All @@ -131,13 +136,10 @@ func filenameToFormat(s string) (inputf format, outputf format, err error) {
return prefix, suffix, nil
}

// remarshal converts input data of format inputFormat to outputFormat and
// returns the result.
func remarshal(input []byte, inputFormat format, outputFormat format,
indentJSON bool) (result []byte, err error) {
var data interface{}

// Decode the serialized data.
// unmarshal decodes serialized data in the format inputFormat into a structure
// of nested maps and slices.
func unmarshal(input []byte, inputFormat format) (data interface{},
err error) {
switch inputFormat {
case fTOML:
_, err = toml.Decode(string(input), &data)
Expand All @@ -157,8 +159,13 @@ func remarshal(input []byte, inputFormat format, outputFormat format,
if err != nil {
return nil, err
}
return
}

// Reencode the data in the output format.
// marshal encodes data stored in nested maps and slices in the format
// outputFormat.
func marshal(data interface{}, outputFormat format,
indentJSON bool) (result []byte, err error) {
switch outputFormat {
case fTOML:
buf := new(bytes.Buffer)
Expand All @@ -177,31 +184,36 @@ func remarshal(input []byte, inputFormat format, outputFormat format,
if err != nil {
return nil, err
}

return
}

func main() {
var inputFile, outputFile, inputFormatStr, outputFormatStr string
var inputFormat, outputFormat format
indentJSON := true
// processCommandLine parses the command line arguments (including os.Args[0],
// the program name) and sets the input and the output file names as well as
// other conversion options based on them.
func processCommandLine() (inputFile string, outputFile string,
inputFormat format, outputFormat format,
indentJSON bool, wrap string, unwrap string) {
var inputFormatStr, outputFormatStr string

// Parse the command line arguments and choose the input and the output
// format.
flag.StringVar(&inputFile, "i", "-", "input file")
flag.StringVar(&outputFile, "o", "-", "output file")
flag.StringVar(&wrap, "wrap", defaultWrapFlagValue,
"wrap the data in a map type with the given key")
flag.StringVar(&unwrap, "unwrap", defaultWrapFlagValue,
"only output the data stored under the given key")

// See if our executable is named, e.g., "json2yaml".
// See if our program is named, e.g., "json2yaml" (normally due to having
// been started through a symlink).
inputFormat, outputFormat, err := filenameToFormat(os.Args[0])
formatFromProgramName := err == nil
if !formatFromProgramName {
// Only give the user an option to specify the input and the output
// format with flags when it is mandatory, i.e., when we are *not* being
// run as "json2yaml" or similar. This makes the usage messages for the
// "x2y" commands more accurate as well.
flag.StringVar(&inputFormatStr, "if", "unknown",
flag.StringVar(&inputFormatStr, "if", defaultFormatFlagValue,
"input format ('toml', 'yaml' or 'json')")
flag.StringVar(&outputFormatStr, "of", "unknown",
flag.StringVar(&outputFormatStr, "of", defaultFormatFlagValue,
"input format ('toml', 'yaml' or 'json')")
}
if !formatFromProgramName || outputFormat == fJSON {
Expand Down Expand Up @@ -252,9 +264,16 @@ func main() {
os.Exit(1)
}
}
return
}

func main() {
inputFile, outputFile, inputFormat, outputFormat,
indentJSON, wrap, unwrap := processCommandLine()

// Read the input data from either standard input or a file.
var input []byte
var err error
if inputFile == "" || inputFile == "-" {
input, err = ioutil.ReadAll(os.Stdin)
} else {
Expand All @@ -270,11 +289,33 @@ func main() {

}

output, err := remarshal(input, inputFormat, outputFormat, indentJSON)
// Convert the input data from inputFormat to outputFormat.
data, err := unmarshal(input, inputFormat)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Unwrap and/or wrap the data in a map if we were told to.
if unwrap != defaultWrapFlagValue {
temp, ok := data.(map[string]interface{})
if !ok {
fmt.Printf("cannot unwrap data: top-level value not a map\n")
os.Exit(1)
}
data, ok = temp[unwrap]
if !ok {
fmt.Printf("cannot unwrap data: no key '%s'\n", unwrap)
os.Exit(1)
}
}
if wrap != defaultWrapFlagValue {
data = map[string]interface{}{wrap: data}
}
output, err := marshal(data, outputFormat, indentJSON)
if err != nil {
fmt.Printf("cannot convert data: %v\n", err)
os.Exit(1)
}

// Print the result to either standard output or a file.
if outputFile == "" || outputFile == "-" {
Expand Down

0 comments on commit bb9ac46

Please sign in to comment.