Skip to content

Instantly share code, notes, and snippets.

@swdunlop
Last active October 21, 2021 00:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swdunlop/8872387 to your computer and use it in GitHub Desktop.
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
/* 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)
}
}
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