Plists in Ruby

I’m currently working on tweaking Apple’s lazy GUI design by alphabetising VPN connections in both System Preferences and the VPN Connection menu. This is definitely only intended for Leopard and I wont be testing it in Tiger or supporting it. It’s just for my own personal use as I have 40 odd VPN connections. The initial proof of concept code is below. I still need to work on getting things to work reliably and smoothly.

I found the plist ruby gem to be somewhat unreliable but think I might have found a way to temporarily provide all the functionality required by piggy backing on plutil. The email I sent to Patrick is below as I don’t want to have to type up the same waffle twice.

Hi Patrick,

I’m working on sorting the network connect plist so that System Preferences and the VPN menu will display connections sorted alphabetically.

Initially parsing…

File.expand_path("~/Library/Preferences/ByHost/com.apple.networkConnect.#{mac_addr}.plist")

…returned the old unknown elements runtime error. Regrettably I can’t send the entire contents of my preference file as it contains some sensitive information even without passwords.

It did make me think about an alternative approach however, which allowed me to parse the plist successfully. I used plutil to convert the plist file to an xml1 format…

plutil -convert xml1 -o /tmp/plist.xml com.apple.network…

Then by parsing the xml I had full access to the file. Of course, one could use Ruby’s built-in temporary file functionality rather than writing to the /tmp directory as I did.

This could theoretically be included as a fall back should an unknown element be encountered.

Thanks for the effort you’ve put in to developing this gem. It’s a handy piece of kit!

Kind regards,
James

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env ruby
 
begin
  require "rubygems"
  require "plist"
rescue LoadError
end
 
# def print_usage
#   puts "Usage: #{File.basename(__FILE__)} [file_path]"
#   exit
# end
 
if ARGV.length == 1
  file_path = File.expand_path(ARGV[0])
else
  mac_addr = `ifconfig en0 ether`.split("n")[1].gsub(':', '').match(/[da-f]{12}/)
  puts mac_addr if $VERBOSE == 1
 
  file_path = File.expand_path("~/Library/Preferences/ByHost/com.apple.networkConnect.#{mac_addr}.plist")
end
 
if File.exists?(file_path)
  # We don't want to worry about overwriting files
  uniq_file = "/tmp/plist.#{(rand * 10000).floor}.xml"
 
  # Thank you Apple, I can never stay mad at you!
  %x(plutil -convert xml1 -o '#{uniq_file}' '#{file_path}')
 
  # Parse our valid xml file
  plist = Plist.parse_xml(File.expand_path(uniq_file))
 
  # This is everything that comes out
  puts plist.inspect
 
  # Clean up, just for fun!
  FileUtils.rm(uniq_file)
else
  raise ArgumentError, "Invalid path specified "#{file_path}""
end

Don’t use this code AS IS! It’s a proof of concept. It doesn’t work and the really hard graft is yet to come. This just shows it can be done!

Leave a Reply