Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@kch
Created August 4, 2010 09:54
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kch/507914 to your computer and use it in GitHub Desktop.
Save kch/507914 to your computer and use it in GitHub Desktop.
converts plist files to really pretty, aligned yaml files with mostly literal utf8 strings
#!/usr/local/bin/ruby
# encoding: UTF-8
require 'yaml'
require "base64"
require 'rexml/document'
class PList < BasicObject
ELEMENT_PROCESSORS = {}
def self.process_element_named(name, &block); ELEMENT_PROCESSORS[name.to_s] = block; end
def self.process_element(elt) ; ELEMENT_PROCESSORS[elt.name][elt]; end
process_element_named(:plist ) { |elt| process_element(elt.elements.first) }
process_element_named(:true ) { |elt| true }
process_element_named(:false ) { |elt| false }
process_element_named(:string ) { |elt| elt.text }
process_element_named(:integer) { |elt| elt.text.to_i }
process_element_named(:real ) { |elt| elt.text.to_f }
process_element_named(:date ) { |elt| ::Date.parse(elt.text) }
process_element_named(:data ) { |elt| ::Base64.decode64(elt.text) }
process_element_named(:array ) { |elt| elt.elements.to_a.inject([]) { |l, e| l << process_element(e) } }
process_element_named(:dict ) { |elt| elt.elements.partition { |e| e.name == 'key' }
.inject { |keys, values| keys.zip(values) }
.inject({}) { |h, (k, v)| h[k.text] = process_element(v); h } }
def self.new_from_path(file_path, sanitize = false)
new(open(file_path), sanitize)
end
def self.pipe(cmd, input, allow_broken_pipe = false)
::Open3.popen3(cmd) { |i, o, *| i.write(input) rescue ::Errno::EPIPE raise unless allow_broken_pipe; i.close; o.read }
end
def initialize(io_or_string, sanitize = false)
s = io_or_string.is_a?(::IO) ? io_or_string.read : io_or_string.to_s
sanitize ||= ::PList.pipe('file -', s, :allow_broken_pipe) !~ /\b\XMLb/
s = ::PList.pipe('plutil -convert xml1 -o - -', s) if sanitize
@root_object = ::PList.process_element(::REXML::Document.new(s).root)
end
def method_missing(*args, &block)
@root_object.send(*args, &block)
end
end
class PrettyYAML
RX_QUOTED_STRING = /(?<!\\)".*?(?<!\\)"/
RX_YAML_HASH_BLOCK = /^( *)\S.*?:.*\n(\1\S.*?:.*\n)+/
RX_KV_PAIR_LINE = /^(.*?):(.*\n)/
def self.[](yaml_string)
new(yaml_string).to_s
end
def initialize(yaml_string)
@yaml_string = yaml_string
unescape_utf8_strings!
align_colons!
end
def unescape_utf8_strings!
@yaml_string.gsub!(RX_QUOTED_STRING) { |m| (s = eval(m).force_encoding(__ENCODING__)).valid_encoding? ? s.inspect : m }
end
def align_colons!
@yaml_string.gsub!(RX_YAML_HASH_BLOCK) do |m|
max = m.lines.map { |line| line.index(":") }.max
m.lines.map { |line| line.gsub(RX_KV_PAIR_LINE) { "%-#{max}s :%s" % [$1, $2] } }.join
end
end
def to_s
@yaml_string
end
end
ARGV.each { |path| puts PrettyYAML[PList.new_from_path(path).to_yaml] } if $0 == __FILE__
@lukelbd
Copy link

lukelbd commented Nov 20, 2018

Thanks! To run this I also needed require 'date' (installed with gem install date).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment