Module: StateMate::Adapters::Defaults
- Includes:
- StateMate::Adapters
- 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'
Constants included from StateMate::Adapters
API_METHOD_NAMES, DEFAULT_KEY_SEP
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.
Methods included from StateMate::Adapters
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.
504 505 506 507 508 509 510 511 512 513 514 515 |
# File 'lib/state_mate/adapters/defaults.rb', line 504 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.
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/state_mate/adapters/defaults.rb', line 537 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).
576 577 578 579 580 581 582 583 |
# File 'lib/state_mate/adapters/defaults.rb', line 576 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.
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/state_mate/adapters/defaults.rb', line 235 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
205 206 207 208 209 210 |
# File 'lib/state_mate/adapters/defaults.rb', line 205 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.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/state_mate/adapters/defaults.rb', line 373 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.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/state_mate/adapters/defaults.rb', line 330 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.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/state_mate/adapters/defaults.rb', line 284 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 " key parsed into empty list: \#{ key.inspect }.\n END\n end\n \n # check for non-strings, empty domain or key segments\n strings.each do |string|\n if !string.is_a?(String) || string.empty?\n raise ArgumentError.new NRSER.squish <<-END\n domain and all key segments must be non-empty Strings,\n found \#{ string.inspect } in key \#{ key.inspect }.\n END\n end\n end\n\n [strings[0], strings[1..-1]]\nend\n" |
.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"
186 187 188 189 190 191 192 |
# File 'lib/state_mate/adapters/defaults.rb', line 186 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 |
# File 'lib/state_mate/adapters/defaults.rb', line 54 def self.read key, = {} = { 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 ....
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'lib/state_mate/adapters/defaults.rb', line 413 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).
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'lib/state_mate/adapters/defaults.rb', line 455 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.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/state_mate/adapters/defaults.rb', line 139 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.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/state_mate/adapters/defaults.rb', line 96 def self.write key, value, = {} = { 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 |