Class: Appscript::Reference

Inherits:
AS_SafeObject show all
Defined in:
lib/rb-scpt.rb

Overview

REFERENCE

Direct Known Subclasses

Application, OSAX::ScriptingAddition

Constant Summary collapse

IgnoreEnums =

‘csig’ attribute flags (see ASRegistry.h; note: there’s no option for ‘numeric strings’ in 10.4)

[
[:case, KAE::KAECaseConsiderMask, KAE::KAECaseIgnoreMask],
[:diacriticals, KAE::KAEDiacriticConsiderMask, KAE::KAEDiacriticIgnoreMask],
[:whitespace, KAE::KAEWhiteSpaceConsiderMask, KAE::KAEWhiteSpaceIgnoreMask],
[:hyphens, KAE::KAEHyphensConsiderMask, KAE::KAEHyphensIgnoreMask],
[:expansion, KAE::KAEExpansionConsiderMask, KAE::KAEExpansionIgnoreMask],
[:punctuation, KAE::KAEPunctuationConsiderMask, KAE::KAEPunctuationIgnoreMask],
]
DefaultConsiderations =

default cons, csig attributes

AEM::DefaultCodecs.pack([AEM::AEEnum.new(KAE::KAECase)])
DefaultConsidersAndIgnores =
_pack_uint32(KAE::KAECaseIgnoreMask)

Constants inherited from AS_SafeObject

AS_SafeObject::EXCLUDE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AS_SafeObject

hide

Constructor Details

#initialize(app_data, aem_reference) ⇒ Reference

Returns a new instance of Reference.



367
368
369
370
371
# File 'lib/rb-scpt.rb', line 367

def initialize(app_data, aem_reference)
  super()
  @AS_app_data = app_data
  @AS_aem_reference = aem_reference
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object

Public properties and methods; these are called by end-user and other clients (e.g. generic references)



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/rb-scpt.rb', line 630

def method_missing(name, *args)
  selector_type, code = @AS_app_data.reference_by_name[name]
  case selector_type # check if name is a property/element/command name, and if it is handle accordingly
    when :property
      raise ArgumentError, "wrong number of arguments for '#{name}' property (1 for 0)" if args != []
      return Reference.new(@AS_app_data, @AS_aem_reference.property(code))
    when :element
      raise ArgumentError, "wrong number of arguments for '#{name}' elements (1 for 0)" if args != []
      return Reference.new(@AS_app_data, @AS_aem_reference.elements(code))
    when :command
      return _send_command(args, name, code[0], code[1])
  else
    # see if it's a method that has been added to Object class [presumably] at runtime, but excluded
    # by AS_SafeObject to avoid potential conflicts with property/element/command names
    begin
      # Notes:
      # rb-appscript has to prevent arbitrary methods that are added to Ruby's base Object class
      # by client code from automatically appearing in Appscript::Reference as well, as these new
      # methods may inadvertently mask property/element/command names, causing appscript to
      # behave incorrectly. However, once it is confirmed that a given method will not mask an existing
      # property/element/command name, it can be added retrospectively to the Reference instance
      # upon which it was called, which is what happens here.
      #
      # This means that methods such as #pretty_print and #pretty_inspect, which are
      # injected into Object when the 'pp' module is loaded, will still be available in appscript
      # references, even though they are not on AS_SafeObject's official list of permitted methods,
      # *as long as* properties/elements/commands of the same name do not already exist for that
      # reference.
      #
      # Where properties/elements/commands of the same name do already exist, appscript
      # will still defer to those, of course, and this may cause problems for the caller if
      # they were wanting the other behaviour. (But, that's the risk one runs with any sort
      # of subclassing exercise when the contents of the superclass are not known for certain
      # beforehand.) Clients that require access to these methods will need to add their names
      # to the ReservedKeywords list (see _appscript/reservedkeywords.rb) at runtime, thereby
      # forcing appscript to append underscores to the conflicting property/element/command
      # names in order to disambiguate them, and modifying any code that refers to those
      # properties/elements/commands accordingly.
      meth = Object.instance_method(name)
    rescue NameError # message not handled
      msg = "Unknown property, element or command: '#{name}'"
      if @AS_app_data.reference_by_name.has_key?("#{name}_".intern)
        msg += " (Did you mean '#{name}_'?)"
      end
      raise RuntimeError, msg
    end
    return meth.bind(self).call(*args)
  end
end

Instance Attribute Details

#AS_aem_referenceObject

users may occasionally require access to the following for creating workarounds to problem apps note: calling #AS_app_data on a newly created application object will return an AppData instance that is not yet fully initialised, so remember to call its #connect method before use



364
365
366
# File 'lib/rb-scpt.rb', line 364

def AS_aem_reference
  @AS_aem_reference
end

#AS_app_dataObject

users may occasionally require access to the following for creating workarounds to problem apps note: calling #AS_app_data on a newly created application object will return an AppData instance that is not yet fully initialised, so remember to call its #connect method before use



364
365
366
# File 'lib/rb-scpt.rb', line 364

def AS_app_data
  @AS_app_data
end

Class Method Details

._pack_uint32(n) ⇒ Object

used to pack csig attributes



389
390
391
# File 'lib/rb-scpt.rb', line 389

def Reference._pack_uint32(n) # used to pack csig attributes
  return AE::AEDesc.new(KAE::TypeUInt32, [n].pack('L'))
end

Instance Method Details

#==(val) ⇒ Object Also known as: eql?

standard object methods



585
586
587
588
# File 'lib/rb-scpt.rb', line 585

def ==(val)
  return (self.class == val.class and @AS_app_data.target == val.AS_app_data.target \
      and @AS_aem_reference == val.AS_aem_reference)
end

#[](selector, end_range_selector = nil) ⇒ Object

Raises:

  • (TypeError)


680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# File 'lib/rb-scpt.rb', line 680

def [](selector, end_range_selector=nil)
  raise TypeError, "Bad selector: nil not allowed." if selector == nil
  if end_range_selector != nil
    new_ref = @AS_aem_reference.by_range(
        self._resolve_range_boundary(selector),
        self._resolve_range_boundary(end_range_selector))
  else
    case selector
      when String
         new_ref = @AS_aem_reference.by_name(selector)
      when Appscript::GenericReference, Appscript::Reference, AEMReference::Test
        case selector
          when Appscript::GenericReference
            test_clause = selector.AS_resolve(@AS_app_data)
            begin
              test_clause = test_clause.AS_aem_reference
            rescue NoMethodError
              raise ArgumentError, "Not a valid its-based test: #{selector}"
            end
          when Appscript::Reference
            test_clause = selector.AS_aem_reference
        else
          test_clause = selector
        end
        if not test_clause.is_a?(AEMReference::Test)
          raise TypeError, "Not an its-based test: #{selector}"
        end
        new_ref = @AS_aem_reference.by_filter(test_clause)
    else
      new_ref = @AS_aem_reference.by_index(selector)
    end
  end
  return Reference.new(@AS_app_data, new_ref)
end

#_resolve_range_boundary(selector) ⇒ Object



373
374
375
376
377
378
379
380
381
# File 'lib/rb-scpt.rb', line 373

def _resolve_range_boundary(selector)
  if selector.is_a?(Appscript::GenericReference)
    return selector.AS_resolve(@AS_app_data).AS_aem_reference
  elsif selector.is_a?(Reference)
    return selector.AS_aem_reference
  else
    return selector
  end
end

#_send_command(args, name, code, labelled_arg_terms) ⇒ Object

Raises:



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
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
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'lib/rb-scpt.rb', line 411

def _send_command(args, name, code, labelled_arg_terms)
  atts = {KAE::KeySubjectAttr => nil}
  params = {}
  case args.length
    when 0
      keyword_args = {}
    when 1 # note: if a command takes a hash as its direct parameter, user must pass {} as a second arg otherwise hash will be assumed to be keyword parameters
      if args[0].is_a?(Hash)
        keyword_args = args[0]
      else
        params[KAE::KeyDirectObject] = args[0]
        keyword_args = {}
      end
    when 2
      params[KAE::KeyDirectObject], keyword_args = args
  else
    raise ArgumentError, "Too many direct parameters."
  end
  if not keyword_args.is_a?(Hash)
    raise ArgumentError, "Second argument must be a Hash containing zero or more keyword parameters."
  end
  # get user-specified timeout, if any
  timeout = (keyword_args.delete(:timeout) {60}).to_i
  if timeout <= 0
    timeout = KAE::KNoTimeOut
  else
    timeout *= 60
  end
  # default send flags
  send_flags = KAE::KAECanSwitchLayer
  # ignore application's reply?
  send_flags += keyword_args.delete(:wait_reply) == false ? KAE::KAENoReply : KAE::KAEWaitReply
  # add considering/ignoring attributes
  ignore_options = keyword_args.delete(:ignore)
  if ignore_options == nil
    atts[KAE::EnumConsiderations] = DefaultConsiderations
    atts[KAE::EnumConsidsAndIgnores] = DefaultConsidersAndIgnores
  else
    atts[KAE::EnumConsiderations] = ignore_options
    csig = 0
    IgnoreEnums.each do |option, consider_mask, ignore_mask|
      csig += ignore_options.include?(option) ? ignore_mask : consider_mask
    end
    atts[KAE::EnumConsidsAndIgnores] = Reference._pack_uint32(csig)
  end
  # optionally specify return value type
  if keyword_args.has_key?(:result_type)
    params[KAE::KeyAERequestedType] = keyword_args.delete(:result_type)
  end
  # extract labelled parameters, if any
  keyword_args.each do |param_name, param_value|
    param_code = labelled_arg_terms[param_name]
    if param_code == nil
      raise ArgumentError, "Unknown keyword parameter: #{param_name.inspect}"
    end
    params[param_code] = param_value
  end
  # apply special cases
  # Note: appscript does not replicate every little AppleScript quirk when packing event attributes and parameters (e.g. AS always packs a make command's tell block as the subject attribute, and always includes an each parameter in count commands), but should provide sufficient consistency with AS's habits and give good usability in their own right.
  if @AS_aem_reference != AEM.app # If command is called on a Reference, rather than an Application...
    if code == 'coresetd'
      #  if ref.set(...) contains no 'to' argument, use direct argument for 'to' parameter and target reference for direct parameter
      if params.has_key?(KAE::KeyDirectObject) and not params.has_key?(KAE::KeyAEData)
        params[KAE::KeyAEData] = params[KAE::KeyDirectObject]
        params[KAE::KeyDirectObject] = @AS_aem_reference
      elsif not params.has_key?(KAE::KeyDirectObject)
        params[KAE::KeyDirectObject] = @AS_aem_reference
      else
        atts[KAE::KeySubjectAttr] = @AS_aem_reference
      end
    elsif code == 'corecrel'
      # this next bit is a bit tricky:
      # - While it should be possible to pack the target reference as a subject attribute, when the target is of typeInsertionLoc, CocoaScripting stupidly tries to coerce it to typeObjectSpecifier, which causes a coercion error.
      # - While it should be possible to pack the target reference as the 'at' parameter, some less-well-designed applications won't accept this and require it to be supplied as a subject attribute (i.e. how AppleScript supplies it).
      # One option is to follow the AppleScript approach and force users to always supply subject attributes as target references and 'at' parameters as 'at' parameters, but the syntax for the latter is clumsy and not backwards-compatible with a lot of existing appscript code (since earlier versions allowed the 'at' parameter to be given as the target reference). So for now we split the difference when deciding what to do with a target reference: if it's an insertion location then pack it as the 'at' parameter (where possible), otherwise pack it as the subject attribute (and if the application doesn't like that then it's up to the client to pack it as an 'at' parameter themselves).
      #
      # if ref.make(...) contains no 'at' argument and target is an insertion reference, use target reference for 'at' parameter...
      if @AS_aem_reference.is_a?(AEMReference::InsertionSpecifier) \
          and not params.has_key?(KAE::KeyAEInsertHere)
        params[KAE::KeyAEInsertHere] = @AS_aem_reference
      else # ...otherwise pack the target reference as the subject attribute
        atts[KAE::KeySubjectAttr] = @AS_aem_reference
      end
    elsif params.has_key?(KAE::KeyDirectObject)
      # if user has already supplied a direct parameter, pack that reference as the subject attribute
      atts[KAE::KeySubjectAttr] = @AS_aem_reference
    else
      # pack that reference as the direct parameter
      params[KAE::KeyDirectObject] = @AS_aem_reference
    end
  end
  # build and send the Apple event, returning its result, if any
  begin
    return @AS_app_data.target.event(code, params, atts,
        KAE::KAutoGenerateReturnID, @AS_app_data).send(timeout, send_flags)
  rescue AEM::EventError => e
    if e.number == -1708 and code == 'ascrnoop'
      return # 'launch' events always return 'not handled' errors; just ignore these
    elsif [-600, -609].include?(e.number) and @AS_app_data.constructor == :by_path
      #
      # Event was sent to a local app for which we no longer have a valid address
      # (i.e. the application has quit since this AEM::Application object was made).
      #
      # - If application is running under a new process id, we just update the
      #   AEM::Application object and resend the event.
      #
      # - If application isn't running, then we see if the event being sent is one of
      #   those allowed to relaunch the application (i.e. 'run' or 'launch'). If it is, the
      #   application is relaunched, the process id updated and the event resent;
      #   if not, the error is rethrown.
      #
      if not AEM::Application.process_exists_for_path?(@AS_app_data.identifier)
        if code == 'ascrnoop'
          AEM::Application.launch(@AS_app_data.identifier)
        elsif code != 'aevtoapp'
          raise CommandError.new(self, name, args, e, @AS_app_data)
        end
      end
      # update AEMApplication object's AEAddressDesc
      @AS_app_data.target.reconnect
      # re-send command
      begin
        return @AS_app_data.target.event(code, params, atts,
            KAE::KAutoGenerateReturnID, @AS_app_data).send(timeout, send_flags)
      rescue AEM::EventError => e
        raise CommandError.new(self, name, args, e, @AS_app_data)
      end
    end
  end
  raise CommandError.new(self, name, args, e, @AS_app_data)
end

#afterObject



743
744
745
# File 'lib/rb-scpt.rb', line 743

def after
  return Reference.new(@AS_app_data, @AS_aem_reference.after)
end

#and(*operands) ⇒ Object



820
821
822
# File 'lib/rb-scpt.rb', line 820

def and(*operands)
  return Reference.new(@AS_app_data, @AS_aem_reference.and(*operands))
end

#anyObject



727
728
729
# File 'lib/rb-scpt.rb', line 727

def any
  return Reference.new(@AS_app_data, @AS_aem_reference.any)
end

#beforeObject



739
740
741
# File 'lib/rb-scpt.rb', line 739

def before
  return Reference.new(@AS_app_data, @AS_aem_reference.before)
end

#beginningObject



731
732
733
# File 'lib/rb-scpt.rb', line 731

def beginning
  return Reference.new(@AS_app_data, @AS_aem_reference.beginning)
end

#begins_with(operand) ⇒ Object



788
789
790
# File 'lib/rb-scpt.rb', line 788

def begins_with(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.begins_with(operand))
end

#commandsObject



559
560
561
# File 'lib/rb-scpt.rb', line 559

def commands
  return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :command ? name.to_s : nil }).compact.sort
end

#contains(operand) ⇒ Object



796
797
798
# File 'lib/rb-scpt.rb', line 796

def contains(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.contains(operand))
end

#does_not_begin_with(operand) ⇒ Object



804
805
806
# File 'lib/rb-scpt.rb', line 804

def does_not_begin_with(operand)
  return self.begins_with(operand).not
end

#does_not_contain(operand) ⇒ Object



812
813
814
# File 'lib/rb-scpt.rb', line 812

def does_not_contain(operand)
  return self.contains(operand).not
end

#does_not_end_with(operand) ⇒ Object



808
809
810
# File 'lib/rb-scpt.rb', line 808

def does_not_end_with(operand)
  return self.ends_with(operand).not
end

#elementsObject



574
575
576
# File 'lib/rb-scpt.rb', line 574

def elements
  return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :element ? name.to_s : nil }).compact.sort
end

#endObject



735
736
737
# File 'lib/rb-scpt.rb', line 735

def end
  return Reference.new(@AS_app_data, @AS_aem_reference.end)
end

#ends_with(operand) ⇒ Object



792
793
794
# File 'lib/rb-scpt.rb', line 792

def ends_with(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.ends_with(operand))
end

#eq(operand) ⇒ Object

avoid colliding with comparison operators, which are normally used to compare two references



772
773
774
# File 'lib/rb-scpt.rb', line 772

def eq(operand) # avoid colliding with comparison operators, which are normally used to compare two references
  return Reference.new(@AS_app_data, @AS_aem_reference.eq(operand))
end

#firstObject



715
716
717
# File 'lib/rb-scpt.rb', line 715

def first
  return Reference.new(@AS_app_data, @AS_aem_reference.first)
end

#ge(operand) ⇒ Object



768
769
770
# File 'lib/rb-scpt.rb', line 768

def ge(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.ge(operand))
end

#gt(operand) ⇒ Object

Following methods will be called by its-based generic references Note that rb-appscript’s comparison ‘operator’ names are gt/ge/eq/ne/lt/le, not >/>=/==/!=/</<= as in py-appscript. Unlike Python, Ruby’s != operator isn’t overridable, and a mixture of styles would be confusing to users. On the plus side, it does mean that rb-appscript’s generic refs can be compared for equality.



764
765
766
# File 'lib/rb-scpt.rb', line 764

def gt(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.gt(operand))
end

#hashObject



592
593
594
595
596
597
# File 'lib/rb-scpt.rb', line 592

def hash
  if not defined? @_hash
    @_hash = [@AS_app_data.target, @AS_aem_reference].hash
  end
  return @_hash
end

#help(flags = '-t') ⇒ Object



385
386
387
# File 'lib/rb-scpt.rb', line 385

def help(flags='-t')
  return @AS_app_data.help(flags, self)
end

#ID(id) ⇒ Object



757
758
759
# File 'lib/rb-scpt.rb', line 757

def ID(id)
  return Reference.new(@AS_app_data, @AS_aem_reference.by_id(id))
end

#is_in(operand) ⇒ Object



800
801
802
# File 'lib/rb-scpt.rb', line 800

def is_in(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.is_in(operand))
end

#is_not_in(operand) ⇒ Object



816
817
818
# File 'lib/rb-scpt.rb', line 816

def is_not_in(operand)
  return self.is_in(operand).not
end

#is_running?Boolean

Utility methods

Returns:

  • (Boolean)


611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/rb-scpt.rb', line 611

def is_running?
  identifier = @AS_app_data.identifier
  case @AS_app_data.constructor
    when :by_path
      return AEM::Application.process_exists_for_path?(identifier)
    when :by_pid
      return AEM::Application.process_exists_for_pid?(identifier)
    when :by_url
      return AEM::Application.process_exists_for_url?(identifier)
    when :by_aem_app
      return AEM::Application.process_exists_for_desc?(identifier.address_desc)
  else # when :current
    return true
  end
end

#keywordsObject



578
579
580
# File 'lib/rb-scpt.rb', line 578

def keywords
  return (@AS_app_data.type_by_name.collect { |name, code| name.to_s }).sort
end

#lastObject



723
724
725
# File 'lib/rb-scpt.rb', line 723

def last
  return Reference.new(@AS_app_data, @AS_aem_reference.last)
end

#le(operand) ⇒ Object



784
785
786
# File 'lib/rb-scpt.rb', line 784

def le(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.le(operand))
end

#lt(operand) ⇒ Object



780
781
782
# File 'lib/rb-scpt.rb', line 780

def lt(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.lt(operand))
end

#methodsObject



555
556
557
# File 'lib/rb-scpt.rb', line 555

def methods
  return (Object.instance_methods + @AS_app_data.reference_by_name.keys.collect { |name| name.to_s }).uniq
end

#middleObject



719
720
721
# File 'lib/rb-scpt.rb', line 719

def middle
  return Reference.new(@AS_app_data, @AS_aem_reference.middle)
end

#ne(operand) ⇒ Object



776
777
778
# File 'lib/rb-scpt.rb', line 776

def ne(operand)
  return Reference.new(@AS_app_data, @AS_aem_reference.ne(operand))
end

#next(klass) ⇒ Object



752
753
754
755
# File 'lib/rb-scpt.rb', line 752

def next(klass)
  return Reference.new(@AS_app_data, @AS_aem_reference.next(
      @AS_app_data.type_by_name.fetch(klass).code))
end

#notObject



828
829
830
# File 'lib/rb-scpt.rb', line 828

def not
  return Reference.new(@AS_app_data, @AS_aem_reference.not)
end

#or(*operands) ⇒ Object



824
825
826
# File 'lib/rb-scpt.rb', line 824

def or(*operands)
  return Reference.new(@AS_app_data, @AS_aem_reference.or(*operands))
end

#parameters(command_name) ⇒ Object



563
564
565
566
567
568
# File 'lib/rb-scpt.rb', line 563

def parameters(command_name)
  if not @AS_app_data.reference_by_name.has_key?(command_name.intern)
    raise ArgumentError, "Command not found: #{command_name}"
  end
  return (@AS_app_data.reference_by_name[command_name.intern][1][1].keys.collect { |name| name.to_s }).sort
end

#previous(klass) ⇒ Object



747
748
749
750
# File 'lib/rb-scpt.rb', line 747

def previous(klass)
  return Reference.new(@AS_app_data, @AS_aem_reference.previous(
      @AS_app_data.type_by_name.fetch(klass).code))
end

#propertiesObject



570
571
572
# File 'lib/rb-scpt.rb', line 570

def properties
  return (@AS_app_data.reference_by_name.collect { |name, info| info[0] == :property ? name.to_s : nil }).compact.sort
end

#respond_to?(name, includePriv = false) ⇒ Boolean

introspection

Returns:

  • (Boolean)


547
548
549
550
551
552
553
# File 'lib/rb-scpt.rb', line 547

def respond_to?(name, includePriv=false)
  if Object.respond_to?(name)
    return true
  else
    return @AS_app_data.reference_by_name.has_key?(name.is_a?(String) ? name.intern : name)
  end
end

#to_sObject Also known as: inspect



599
600
601
602
603
604
# File 'lib/rb-scpt.rb', line 599

def to_s
  if not defined? @_to_s
    @_to_s = ReferenceRenderer.render(@AS_app_data, @AS_aem_reference)
  end
  return @_to_s
end