Last active
October 21, 2021 00:23
-
-
Save swdunlop/8872387 to your computer and use it in GitHub Desktop.
PLYML is a simple utility to convert OSX plists to/from YAML to make them easier to hack on
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* plyml converts Apple plists into YAML, and vice-versa. This is used to make it easier to hack on TextMate and Sublime Text themes. It depends on Apple's "plutil" utility, meaning it is only useful on OSX. */ | |
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"launchpad.net/goyaml" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"strings" | |
) | |
func main() { | |
if len(os.Args) != 2 { | |
exitWithUsage() | |
} | |
src := os.Args[1] | |
if !fileExists(src) { | |
exitWithUsage() | |
} | |
data, err := readData(src) | |
exitIfError(err) | |
switch { | |
case hasExt(src, ".yml", ".yaml"): | |
exitIfError(compileYaml(data)) | |
default: | |
exitIfError(produceYaml(data)) | |
} | |
} | |
// readData reads all of stdin to produce a string | |
func readData(src string) (string, error) { | |
p, err := ioutil.ReadFile(src) | |
return string(p), err | |
} | |
func fileExists(src string) bool { | |
_, err := os.Stat(src) | |
if err != nil { | |
return false | |
} | |
return true | |
} | |
// hasExt returns true if src ends in any of exts | |
func hasExt(src string, exts ...string) bool { | |
src = filepath.Ext(src) | |
for _, ext := range exts { | |
if ext == src { | |
return true | |
} | |
} | |
return false | |
} | |
// exitIfError invokes exitWithError if err is not nil | |
func exitIfError(err error) { | |
if err != nil { | |
exitWithError(err) | |
} | |
} | |
// exitWithError exits with status 3 after reporting the error to stderr | |
func exitWithError(err error) { | |
println("!!", err.Error()) | |
os.Exit(3) | |
} | |
// exitWithUsage exits with status 2 after suggesting how to use plyml | |
func exitWithUsage() { | |
println("usage: plyml source.* >output.yml ## produces yaml from plist") | |
println(" or: plyml source.yml >source.plist ## compiles yaml to xml1 plist") | |
os.Exit(2) | |
} | |
// compileYaml takes a plist document and prints a YAML representation | |
func compileYaml(data string) error { | |
plist := make(map[string]interface{}) | |
err := goyaml.Unmarshal([]byte(data), &plist) | |
if err != nil { | |
return err | |
} | |
normalizeDict(plist) | |
js, err := json.Marshal(plist) | |
if err != nil { | |
return err | |
} | |
xml, err := plutilConvert("xml1", string(js)) | |
if err != nil { | |
return err | |
} | |
return printString(xml) | |
} | |
// produceYaml takes a YAML document and prints an XML1 plist | |
func produceYaml(data string) error { | |
js, err := plutilConvert("json", data) | |
if err != nil { | |
return err | |
} | |
plist := make(map[string]interface{}) | |
err = json.Unmarshal([]byte(js), &plist) | |
if err != nil { | |
return err | |
} | |
yml, err := goyaml.Marshal(plist) | |
if err != nil { | |
return err | |
} | |
return printString(string(yml)) | |
} | |
// normalizeDict goes through goyaml's output to replace map[interface{}]... with map[string]... by normalizing keys using fmt.Sprint | |
func normalizeDict(dict map[string]interface{}) { | |
for k, v := range dict { | |
// fmt.Fprintf(os.Stderr, "k: %#v t: %T\n", k, v) | |
switch old := v.(type) { | |
case map[interface{}]interface{}: | |
fix := make(map[string]interface{}) | |
for k, v := range old { | |
fix[fmt.Sprint(k)] = v | |
} | |
normalizeDict(fix) | |
dict[k] = fix | |
case []interface{}: | |
normalizeArray(old) | |
} | |
} | |
} | |
// normalizeArray goes through goyaml's output to replace map[interface{}]... with map[string]... by normalizing keys using fmt.Sprint | |
func normalizeArray(array []interface{}) { | |
for i, v := range array { | |
// fmt.Fprintf(os.Stderr, "i: %#v t: %T\n", i, v) | |
switch old := v.(type) { | |
case map[interface{}]interface{}: | |
fix := make(map[string]interface{}) | |
for k, v := range old { | |
fix[fmt.Sprint(k)] = v | |
} | |
normalizeDict(fix) | |
array[i] = fix | |
case []interface{}: | |
normalizeArray(old) | |
} | |
} | |
} | |
// printString just writes a string to Stdout | |
func printString(str string) error { | |
_, err := os.Stdout.WriteString(str) | |
return err | |
} | |
// plutilConvert wraps Apple's plutil conversion utility, distributed with XCode for OSX | |
func plutilConvert(into, input string) (string, error) { | |
cmd := exec.Command("plutil", "-convert", into, "-", "-o", "-") | |
cmd.Stderr = os.Stderr | |
cmd.Stdin = strings.NewReader(input) | |
out, err := cmd.StdoutPipe() | |
if err != nil { | |
return "", err | |
} | |
defer out.Close() | |
err = cmd.Start() | |
if err != nil { | |
return "", err | |
} | |
p, err := ioutil.ReadAll(out) | |
if err != nil { | |
go cmd.Wait() | |
return "", err | |
} | |
return string(p), cmd.Wait() | |
} | |
// panicIfError is only used by the examples, and panics if err is non-nil | |
func panicIfError(err error) { | |
if err != nil { | |
panic(err) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
func ExampleShakespeareYaml() { | |
panicIfError(compileYaml(`Author: William Shakespeare`)) | |
// Output: <?xml version="1.0" encoding="UTF-8"?> | |
// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
// <plist version="1.0"> | |
// <dict> | |
// <key>Author</key> | |
// <string>William Shakespeare</string> | |
// </dict> | |
// </plist> | |
} | |
func ExampleShakespeareXml() { | |
panicIfError(produceYaml( | |
`<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>Author</key> | |
<string>William Shakespeare</string> | |
</dict> | |
</plist>`)) | |
//Output: Author: William Shakespeare | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment