Module: StateMate::Adapters::Defaults
- Defined in:
- lib/state_mate/adapters/defaults.rb
Constant Summary collapse
- KEY_SEP =
string seperator used to split keys
':'- DEFAULTS_CMD =
path to the
defaultssystem command '/usr/bin/defaults'
Class Method Summary collapse
-
.basic_delete(domain, key, current_host) ⇒ Object
private
does a delete of either a entire domain's properties or a single top level key directly using
defaults delete .... -
.basic_write(domain, key, value, current_host) ⇒ Object
private
does a write of either a entire domain's properties or a single top level key directly using
defaults write .... -
.deep_write(domain, key, deep_segs, value, current_host) ⇒ Object
private
internal compliment to Defaults.basic_write that writes "deep" keys (keys with additional segments beyond domain and top-level).
-
.domain_to_filepath(domain, user = ENV['USER'], current_host = false) ⇒ String
get the filepath to the
.plistfor a domain string. -
.hardware_uuid ⇒ String
get the "by host" / "current host" id, also called the "hardware uuid".
-
.hash_deep_write!(hash, key, value) ⇒ Object
does a "deep" mutating write in a Hash given a series of keys and a value.
-
.native_types(object, keys_as_symbols = false) ⇒ Object
pure.
-
.parse_key(key) ⇒ Array<String, Array<String>>
pure.
-
.prefs_path(user) ⇒ String
pure.
-
.read(key, options = {}) ⇒ Object
the API method that StateMate.execute calls (through StateSet#execute) to read the value of a (possibly deep) key.
-
.read_defaults(domain, current_host = false) ⇒ Hash
private
does an system call to read and parse an domain's entire plist file using
defaults export .... -
.read_type(domain, key, current_host) ⇒ Symbol
private
reads the type of key using
defauls read-type ...(hence it only reads top-level keys). -
.to_xml_element(obj) ⇒ REXML::Element
pure.
-
.write(key, value, options = {}) ⇒ Object
the API method that StateMate.execute calls (through StateSet#execute) to write the value of a (possibly deep) key.
Class Method Details
.basic_delete(domain, key, current_host) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
does a delete of either a entire domain's properties or a single
top level key directly using defaults delete ....
called by basic_write when it's provided nil for a value.
518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/state_mate/adapters/defaults.rb', line 518 def self.basic_delete domain, key, current_host sudo = domain.start_with?('/Library') ? "sudo" : nil Cmds! '%{sudo?} %{cmd} %{current_host?} delete %{domain} %{key?}', cmd: DEFAULTS_CMD, current_host: (current_host ? '-currentHost' : nil), domain: domain, key: (key ? key : nil), sudo: sudo nil end |
.basic_write(domain, key, value, current_host) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
does a write of either a entire domain's properties or a single
top level key directly using defaults write ....
called by write when there are zero or one key segments.
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/state_mate/adapters/defaults.rb', line 551 def self.basic_write domain, key, value, current_host if value.nil? basic_delete(domain, key, current_host) else sudo = domain.start_with?('/Library') ? "sudo" : nil Cmds! '%{sudo?} %{cmd} %{current_host?} write %{domain} %{key?} %{xml}', cmd: DEFAULTS_CMD, current_host: (current_host ? '-currentHost' : nil), domain: domain, key: (key ? key : nil), xml: to_xml_element(value).to_s, sudo: sudo end nil end |
.deep_write(domain, key, deep_segs, value, current_host) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
internal compliment to basic_write that writes "deep" keys (keys with additional segments beyond domain and top-level).
590 591 592 593 594 595 596 597 |
# File 'lib/state_mate/adapters/defaults.rb', line 590 def self.deep_write domain, key, deep_segs, value, current_host root = read [domain, key], 'current_host' => current_host # handle the root not being there root = {} unless root.is_a? Hash hash_deep_write! root, deep_segs, value basic_write domain, key, root, current_host nil end |
.domain_to_filepath(domain, user = ENV['USER'], current_host = false) ⇒ String
get the filepath to the .plist for a domain string.
not currently called by any StateMate stuff but seemed nice to keep around for scripts and the like.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/state_mate/adapters/defaults.rb', line 249 def self.domain_to_filepath domain, user = ENV['USER'], current_host = false # there are a few cases: # # 1.) absolute file path if domain.start_with? '/' domain # # 2.) home-based path elsif domain.start_with? '~/' if user == 'root' "/var/root/#{ domain[2..-1] }" else "/Users/#{ user }/#{ domain[2..-1] }" end # # global domain elsif domain == "NSGlobalDomain" if current_host "#{ prefs_path user }/.GlobalPreferences.#{ hardware_uuid }.plist" else "#{ prefs_path user }/.GlobalPreferences.plist" end # # 3.) domain with corresponding plist else if current_host "#{ prefs_path user }/ByHost/#{ domain }.#{ hardware_uuid }.plist" else "#{ prefs_path user }/#{ domain }.plist" end end end |
.hardware_uuid ⇒ String
get the "by host" / "current host" id, also called the "hardware uuid".
adapted from
http://stackoverflow.com/questions/933460/unique-hardware-id-in-mac-os-x
219 220 221 222 223 224 |
# File 'lib/state_mate/adapters/defaults.rb', line 219 def self.hardware_uuid plist_xml_str = Cmds!("ioreg -r -d 1 -c IOPlatformExpertDevice -a").out plist = CFPropertyList::List.new data: plist_xml_str dict = CFPropertyList.native_types(plist.value).first dict['IOPlatformUUID'] end |
.hash_deep_write!(hash, key, value) ⇒ Object
does a "deep" mutating write in a Hash given a series of keys and a value.
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/state_mate/adapters/defaults.rb', line 387 def self.hash_deep_write! hash, key, value segment = key.first rest = key[1..-1] # terminating case: we are at the last segment if rest.empty? hash[segment] = value else case hash[segment] when Hash # go deeper hash_deep_write! hash[segment], rest, value else hash[segment] = {} hash_deep_write! hash[segment], rest, value end end value end |
.native_types(object, keys_as_symbols = false) ⇒ Object
pure
creates a native Ruby type represnetation of a CFType hiercharchy.
customized from CFPropertyList to use the Base64 encoding of binary blobs since JSON pukes on the raw ones.
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/state_mate/adapters/defaults.rb', line 344 def self.native_types(object,keys_as_symbols=false) return if object.nil? if (object.is_a?(CFPropertyList::CFDate) || object.is_a?(CFPropertyList::CFString) || object.is_a?(CFPropertyList::CFInteger) || object.is_a?(CFPropertyList::CFReal) || object.is_a?(CFPropertyList::CFBoolean)) || object.is_a?(CFPropertyList::CFUid) then return object.value elsif(object.is_a?(CFPropertyList::CFData)) then return CFPropertyList::Blob.new(object.encoded_value) elsif(object.is_a?(CFPropertyList::CFArray)) then ary = [] object.value.each do |v| ary.push native_types(v) end return ary elsif(object.is_a?(CFPropertyList::CFDictionary)) then hsh = {} object.value.each_pair do |k,v| k = k.to_sym if keys_as_symbols hsh[k] = native_types(v) end return hsh end end |
.parse_key(key) ⇒ Array<String, Array<String>>
pure
parses the key into domain and key segments.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/state_mate/adapters/defaults.rb', line 298 def self.parse_key key strings = case key when Array key when String key.split KEY_SEP else raise "must be string or array, not #{ key.inspect }" end # case # make sure there is at least one element if strings.empty? raise ArgumentError.new NRSER.squish <<-END key parsed into empty list: #{ key.inspect }. END end # check for non-strings, empty domain or key segments strings.each do |string| if !string.is_a?(String) || string.empty? raise ArgumentError.new NRSER.squish <<-END domain and all key segments must be non-empty Strings, found #{ string.inspect } in key #{ key.inspect }. END end end [strings[0], strings[1..-1]] end |
.prefs_path(user) ⇒ String
pure
builds the Preferences folder path depending on the user given,
which will be either
"/Library/Preferences"
if user is "root", otherwise
"/Users/#{ user }/Library/Preferences"
200 201 202 203 204 205 206 |
# File 'lib/state_mate/adapters/defaults.rb', line 200 def self.prefs_path user if user == 'root' '/Library/Preferences' else "/Users/#{ user }/Library/Preferences" end end |
.read(key, options = {}) ⇒ Object
the API method that StateMate.execute calls (through StateSet#execute) to read the value of a (possibly deep) key.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/state_mate/adapters/defaults.rb', line 54 def self.read key, = {} if .key? :current_host raise ArgumentError.new NRSER.squish <<-END current_host option key must be a string, not a symbol. END end = { 'current_host' => false, }.merge domain, key_segs = parse_key key value = read_defaults domain, ['current_host'] key_segs.each do |seg| value = if (value.is_a?(Hash) && value.key?(seg)) value[seg] else nil end end value end |
.read_defaults(domain, current_host = false) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
does an system call to read and parse an domain's entire plist file using
defaults export ....
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/state_mate/adapters/defaults.rb', line 427 def self.read_defaults domain, current_host = false file = Tempfile.new('read_defaults') begin Cmds! '%{cmd} %{current_host?} export %{domain} %{filepath}', cmd: DEFAULTS_CMD, current_host: (current_host ? '-currentHost' : nil), domain: domain, filepath: file.path plist = CFPropertyList::List.new file: file.path data = native_types plist.value ensure file.close file.unlink # deletes the temp file end end |
.read_type(domain, key, current_host) ⇒ Symbol
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
reads the type of key using defauls read-type ... (hence it only
reads top-level keys).
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'lib/state_mate/adapters/defaults.rb', line 469 def self.read_type domain, key, current_host result = Cmds! '%{cmd} %{current_host?} read-type %{domain} %{key}', cmd: DEFAULTS_CMD, current_host: (current_host ? '-currentHost' : nil), domain: domain, key: key out = result.out.chomp case out when "Type is string" :string when "Type is data" :data when "Type is integer" :int when "Type is float" :float when "Type is boolean" :bool when "Type is date" :date when "Type is array" :array when "Type is dictionary" :dict else raise "unknown output: #{ out.inspect }" end end |
.to_xml_element(obj) ⇒ REXML::Element
pure
convert a ruby object to a REXML::Element for a plist.
not sure why i'm using this instead of something from CFPropertyList... maybe it's a left-over from before CFPropertyList was included, maybe there was some issue with CFPropertyList... not sure.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/state_mate/adapters/defaults.rb', line 153 def self.to_xml_element obj case obj when String REXML::Element.new("string").add_text obj when Fixnum REXML::Element.new('integer').add_text obj.to_s when Float REXML::Element.new('real').add_text obj.to_s when Hash dict = REXML::Element.new('dict') obj.each {|dict_key, dict_obj| dict.add_element REXML::Element.new('key').add_text(dict_key) dict.add_element to_xml_element(dict_obj) } dict when Array array = REXML::Element.new('array') obj.each {|array_entry| array.add_element to_xml_element(array_entry) } array when TrueClass, FalseClass REXML::Element.new obj.to_s when Time REXML::Element.new('date').add_text obj.utc.iso8601 else raise TypeError, "can't handle type: #{ obj.inspect }" end end |
.write(key, value, options = {}) ⇒ Object
the API method that StateMate.execute calls (through StateSet#execute) to write the value of a (possibly deep) key.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/state_mate/adapters/defaults.rb', line 104 def self.write key, value, = {} if .key? :current_host raise ArgumentError.new NRSER.squish <<-END current_host option key must be a string, not a symbol. END end = { 'current_host' => false, }.merge domain, key_segs = parse_key key if key_segs.length > 1 deep_write domain, key_segs[0], key_segs.drop(1), value, ['current_host'] else basic_write domain, key_segs[0], value, ['current_host'] end nil end |