Skip to content

Commit bb9ac46

Browse files
committed
New flags: '-wrap' and '-unwrap'.
Refactored the func 'remashal' to implement the new flags.
1 parent b15717b commit bb9ac46

File tree

3 files changed

+120
-36
lines changed

3 files changed

+120
-36
lines changed

README.md

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,76 @@ commands `toml2yaml`, `toml2json`, `yaml2toml`, `yaml2json`. `json2toml` and
99

1010
```
1111
remarshal -if inputformat -of outputformat [-indent-json=(true|false)]
12-
[-i inputfile] [-o outputfile]
12+
[-i inputfile] [-o outputfile] [-wrap wrapper] [-unwrap wrapper]
1313
1414
```
1515

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

1919
```
20-
toml2toml [-o outputfile] [[-i] inputfile]
21-
yaml2toml [-o outputfile] [[-i] inputfile]
22-
json2toml [-o outputfile] [[-i] inputfile]
23-
toml2yaml [-o outputfile] [[-i] inputfile]
24-
yaml2yaml [-o outputfile] [[-i] inputfile]
25-
json2yaml [-o outputfile] [[-i] inputfile]
26-
toml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
27-
yaml2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
28-
json2json [-indent-json=(true|false)] [-o outputfile] [[-i] inputfile]
20+
toml2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
21+
yaml2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
22+
json2toml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
23+
toml2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
24+
yaml2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
25+
json2yaml [-wrap wrapper] [-unwrap wrapper] [-o outputfile] [[-i] inputfile]
26+
toml2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
27+
[-o outputfile] [[-i] inputfile]
28+
yaml2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
29+
[-o outputfile] [[-i] inputfile]
30+
json2json [-indent-json=(true|false)] [-wrap wrapper] [-unwrap wrapper]
31+
[-o outputfile] [[-i] inputfile]
2932
```
3033

31-
The all of the commands above exit with status 0 on success and 1 on failure.
34+
All of the commands above exit with status 0 on success and 1 on failure.
3235

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

37-
For short commands (`x2y`) the flag `-i` before `inputfile` can be omitted if
38-
`inputfile` is the last argument.
40+
For the short commands (`x2y`) the flag `-i` before `inputfile` can be omitted
41+
if `inputfile` is the last argument.
42+
43+
## Wrappers
44+
45+
The flags `-wrap` and `-unwrap` are there to solve the problem of converting
46+
JSON and YAML data to TOML if the topmost element of that data is not of a map
47+
type (i.e., not an object in JSON or an associative array in YAML) but a list, a
48+
string or a number. Such data can not be represented as TOML directly; it needs
49+
to wrapped in a map type first. Passing the flag `-wrap someKey` to `remarshal`
50+
or one of its short commands wraps the input data in a "wrapper" map with one
51+
key, "someKey", with the input data as its value. The flag `-unwrap someKey`
52+
does the opposite: if it is specified only the value stored under the key
53+
"someKey" in the top-level map element of the input data is converted to the
54+
target format and output; all other data is skipped. If the top-level element is
55+
not a map or does not have the key `someKey` then `-unwrap someKey` returns an
56+
error.
57+
58+
The following shell transcript demonstrates the problem and how `-wrap` and
59+
`-unwrap` solve it:
60+
61+
```
62+
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | ./remarshal -if json -of toml
63+
cannot convert data: top-level values must be a Go map or struct
64+
65+
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | \
66+
./remarshal -if json -of toml -wrap main
67+
[[main]]
68+
a = "b"
69+
70+
[[main]]
71+
c = [1, 2, 3]
72+
73+
$ echo '[{"a":"b"},{"c":[1,2,3]}]' | \
74+
./remarshal -if json -of toml -wrap main > test.toml
75+
76+
$ ./remarshal -if toml -of json -indent-json=0 < test.toml
77+
{"main":[{"a":"b"},{"c":[1,2,3]}]}
78+
79+
$ ./remarshal -if toml -of json -indent-json=0 -unwrap main < test.toml
80+
[{"a":"b"},{"c":[1,2,3]}]
81+
```
3982

4083
# Building and installation
4184

@@ -139,7 +182,7 @@ name = "Kiev"
139182

140183
# Known bugs
141184

142-
* Converting data with floating point values to YAML may cause the loss of
185+
* Converting data with floating point values to YAML may cause a loss of
143186
precision.
144187

145188
# License

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.3
1+
0.3.0

remarshal.go

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const (
2727
fUnknown
2828
)
2929

30+
const (
31+
defaultFormatFlagValue = "unspecified"
32+
defaultWrapFlagValue = "key"
33+
)
34+
3035
// convertMapsToStringMaps recursively converts values of type
3136
// map[interface{}]interface{} contained in item to map[string]interface{}. This
3237
// is needed before the encoders for TOML and JSON can accept data returned by
@@ -58,7 +63,7 @@ func convertMapsToStringMaps(item interface{}) (res interface{}, err error) {
5863

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

134-
// remarshal converts input data of format inputFormat to outputFormat and
135-
// returns the result.
136-
func remarshal(input []byte, inputFormat format, outputFormat format,
137-
indentJSON bool) (result []byte, err error) {
138-
var data interface{}
139-
140-
// Decode the serialized data.
139+
// unmarshal decodes serialized data in the format inputFormat into a structure
140+
// of nested maps and slices.
141+
func unmarshal(input []byte, inputFormat format) (data interface{},
142+
err error) {
141143
switch inputFormat {
142144
case fTOML:
143145
_, err = toml.Decode(string(input), &data)
@@ -157,8 +159,13 @@ func remarshal(input []byte, inputFormat format, outputFormat format,
157159
if err != nil {
158160
return nil, err
159161
}
162+
return
163+
}
160164

161-
// Reencode the data in the output format.
165+
// marshal encodes data stored in nested maps and slices in the format
166+
// outputFormat.
167+
func marshal(data interface{}, outputFormat format,
168+
indentJSON bool) (result []byte, err error) {
162169
switch outputFormat {
163170
case fTOML:
164171
buf := new(bytes.Buffer)
@@ -177,31 +184,36 @@ func remarshal(input []byte, inputFormat format, outputFormat format,
177184
if err != nil {
178185
return nil, err
179186
}
180-
181187
return
182188
}
183189

184-
func main() {
185-
var inputFile, outputFile, inputFormatStr, outputFormatStr string
186-
var inputFormat, outputFormat format
187-
indentJSON := true
190+
// processCommandLine parses the command line arguments (including os.Args[0],
191+
// the program name) and sets the input and the output file names as well as
192+
// other conversion options based on them.
193+
func processCommandLine() (inputFile string, outputFile string,
194+
inputFormat format, outputFormat format,
195+
indentJSON bool, wrap string, unwrap string) {
196+
var inputFormatStr, outputFormatStr string
188197

189-
// Parse the command line arguments and choose the input and the output
190-
// format.
191198
flag.StringVar(&inputFile, "i", "-", "input file")
192199
flag.StringVar(&outputFile, "o", "-", "output file")
200+
flag.StringVar(&wrap, "wrap", defaultWrapFlagValue,
201+
"wrap the data in a map type with the given key")
202+
flag.StringVar(&unwrap, "unwrap", defaultWrapFlagValue,
203+
"only output the data stored under the given key")
193204

194-
// See if our executable is named, e.g., "json2yaml".
205+
// See if our program is named, e.g., "json2yaml" (normally due to having
206+
// been started through a symlink).
195207
inputFormat, outputFormat, err := filenameToFormat(os.Args[0])
196208
formatFromProgramName := err == nil
197209
if !formatFromProgramName {
198210
// Only give the user an option to specify the input and the output
199211
// format with flags when it is mandatory, i.e., when we are *not* being
200212
// run as "json2yaml" or similar. This makes the usage messages for the
201213
// "x2y" commands more accurate as well.
202-
flag.StringVar(&inputFormatStr, "if", "unknown",
214+
flag.StringVar(&inputFormatStr, "if", defaultFormatFlagValue,
203215
"input format ('toml', 'yaml' or 'json')")
204-
flag.StringVar(&outputFormatStr, "of", "unknown",
216+
flag.StringVar(&outputFormatStr, "of", defaultFormatFlagValue,
205217
"input format ('toml', 'yaml' or 'json')")
206218
}
207219
if !formatFromProgramName || outputFormat == fJSON {
@@ -252,9 +264,16 @@ func main() {
252264
os.Exit(1)
253265
}
254266
}
267+
return
268+
}
269+
270+
func main() {
271+
inputFile, outputFile, inputFormat, outputFormat,
272+
indentJSON, wrap, unwrap := processCommandLine()
255273

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

271290
}
272291

273-
output, err := remarshal(input, inputFormat, outputFormat, indentJSON)
292+
// Convert the input data from inputFormat to outputFormat.
293+
data, err := unmarshal(input, inputFormat)
274294
if err != nil {
275295
fmt.Println(err)
276296
os.Exit(1)
277297
}
298+
// Unwrap and/or wrap the data in a map if we were told to.
299+
if unwrap != defaultWrapFlagValue {
300+
temp, ok := data.(map[string]interface{})
301+
if !ok {
302+
fmt.Printf("cannot unwrap data: top-level value not a map\n")
303+
os.Exit(1)
304+
}
305+
data, ok = temp[unwrap]
306+
if !ok {
307+
fmt.Printf("cannot unwrap data: no key '%s'\n", unwrap)
308+
os.Exit(1)
309+
}
310+
}
311+
if wrap != defaultWrapFlagValue {
312+
data = map[string]interface{}{wrap: data}
313+
}
314+
output, err := marshal(data, outputFormat, indentJSON)
315+
if err != nil {
316+
fmt.Printf("cannot convert data: %v\n", err)
317+
os.Exit(1)
318+
}
278319

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

0 commit comments

Comments
 (0)