Class: RunLoop::PlistBuddy

Inherits:
Object
  • Object
show all
Includes:
Shell
Defined in:
lib/run_loop/plist_buddy.rb

Overview

A class for reading and writing property list values.

Why not use CFPropertyList? Because it is super wonky. Among its other faults, it matches Boolean to a string type with ‘true/false’ values which is problematic for our purposes.

Constant Summary

Constants included from Shell

Shell::DEFAULT_OPTIONS

Instance Method Summary collapse

Methods included from Shell

run_shell_command, #run_shell_command

Methods included from Encoding

#transliterate

Instance Method Details

#create_plist(path) ⇒ Object

Creates an new empty plist at ‘path`.

Is not responsible for creating directories or ensuring write permissions.

Parameters:

  • path (String)

    Where to create the new plist.



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/run_loop/plist_buddy.rb', line 90

def create_plist(path)
  File.open(path, 'w') do |file|
    file.puts "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    file.puts "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
    file.puts "<plist version=\"1.0\">"
    file.puts '<dict>'
    file.puts '</dict>'
    file.puts '</plist>'
  end
  path
end

#ensure_plist(directory, name) ⇒ Object

Ensures a plist exists at path by creating necessary directories and creating an empty plist if none exists.



104
105
106
107
108
109
110
111
112
# File 'lib/run_loop/plist_buddy.rb', line 104

def ensure_plist(directory, name)
  FileUtils.mkdir_p(directory) if !File.exist?(directory)

  plist = File.join(directory, name)

  create_plist(plist) if !File.exists?(plist)

  plist
end

#plist_key_exists?(key, file, opts = {}) ⇒ Boolean

Checks if the key exists in plist.

Parameters:

  • key (String)

    the key to inspect (may not be nil or empty)

  • file (String)

    the plist to read

  • opts (Hash) (defaults to: {})

    options for controlling execution

Options Hash (opts):

  • :verbose (Boolean) — default: false

    controls log level

Returns:

  • (Boolean)

    true if the key exists in plist file



41
42
43
# File 'lib/run_loop/plist_buddy.rb', line 41

def plist_key_exists?(key, file, opts={})
  plist_read(key, file, opts) != nil
end

#plist_read(key, file, opts = {}) ⇒ String

Reads key from file and returns the result.

Parameters:

  • key (String)

    the key to inspect (may not be nil or empty)

  • file (String)

    the plist to read

  • opts (Hash) (defaults to: {})

    options for controlling execution

Options Hash (opts):

  • :verbose (Boolean) — default: false

    controls log level

Returns:

  • (String)

    the value of the key

Raises:

  • (ArgumentError)

    if nil or empty key



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/run_loop/plist_buddy.rb', line 22

def plist_read(key, file, opts={})
  if key.nil? or key.length == 0
    raise(ArgumentError, "key '#{key}' must not be nil or empty")
  end
  cmd = build_plist_cmd(:print, {:key => key}, file)
  success, output = execute_plist_cmd(cmd, file, opts)
  if !success
    nil
  else
    output
  end
end

#plist_set(key, type, value, file, opts = {}) ⇒ Boolean

Replaces or creates the value of key in the file.

Parameters:

  • key (String)

    the key to set (may not be nil or empty)

  • type (String)

    the plist type (used only when adding a value)

  • value (String)

    the new value

  • file (String)

    the plist to read

  • opts (Hash) (defaults to: {})

    options for controlling execution

Options Hash (opts):

  • :verbose (Boolean) — default: false

    controls log level

Returns:

  • (Boolean)

    true if the operation was successful

Raises:

  • (ArgumentError)

    if nil or empty key



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/run_loop/plist_buddy.rb', line 55

def plist_set(key, type, value, file, opts={})
  default_opts = {:verbose => false}
  merged = default_opts.merge(opts)

  if key.nil? or key.length == 0
    raise(ArgumentError, "key '#{key}' must not be nil or empty")
  end

  cmd_args = {:key => key,
              :type => type,
              :value => value}

  if plist_key_exists?(key, file, merged)
    cmd = build_plist_cmd(:set, cmd_args, file)
  else
    cmd = build_plist_cmd(:add, cmd_args, file)
  end

  success, output = execute_plist_cmd(cmd, file, merged)
  if !success
    raise RuntimeError, %Q[
Encountered an error performing operation on plist:

#{plist_buddy} -c "#{cmd}" #{file}
=> #{output}
]
  end
  success
end

#run_command(cmd, file, opts = {}) ⇒ Object

Sends an arbitrary command (-c) to PlistBuddy.

This class does not handle setting data, date, dictionary, or array or manipulating elements in existing array or dictionary types. This method is an attempt to bridge this gap.

When setting/adding bool, real, integer, string values, use #plist_set.

For reading values, use #plist_read.

Parameters:

  • cmd (String)

    The command passed to PlistBuddy with -c

  • file (String)

    Path the plist file

  • opts (Hash) (defaults to: {})

    options for controlling execution

Options Hash (opts):

  • :verbose (Boolean) — default: false

    controls log level

Returns:

  • Boolean, String Success and the output of running the command.

Raises:

  • RuntimeError when running the command fails.



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/run_loop/plist_buddy.rb', line 130

def run_command(cmd, file, opts={})
  success, output = execute_plist_cmd(cmd, file, opts)
  if !success
    raise RuntimeError, %Q[
Encountered an error performing operation on plist:

#{plist_buddy} -c "#{cmd}" #{file}
=> #{output}
]
  end
  return success, output
end

#unshift_array(key, type, value, path, opts = {}) ⇒ Object

Add value to the head of an array type.

Parameters:

  • key (String)

    The plist key

  • type (String)

    any allowed plist type

  • value (Object)

    the value to add

  • path (String)

    the plist path

  • opts (Hash) (defaults to: {})

    options for controlling execution

Options Hash (opts):

  • :verbose (Boolean) — default: false

    controls log level

Raises:

  • RuntimeError when running the command fails.

  • RuntimeError if attempt to push value onto non-array container.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/run_loop/plist_buddy.rb', line 153

def unshift_array(key, type, value, path, opts={})
  if !plist_key_exists?(key, path)
    run_command("Add :#{key} array", path, opts)
  else
    key_type = plist_read(key, path).split(" ")[0]
    if key_type != "Array"
      raise RuntimeError, %Q[
Could not push #{value} onto array:
  Expected:  key #{key} be of type Array
 Found:  had type #{key_type}

in plist:

  #{path}
]
    end
  end

  run_command("Add :#{key}:0 #{type} #{value}", path, opts)
end