├── py-appscript ├── lib │ ├── mactypes.py │ ├── appscript │ │ ├── __init__.py │ │ ├── reservedkeywords.py │ │ ├── keywordwrapper.py │ │ └── genericreference.py │ ├── aem │ │ ├── typewrappers.py │ │ └── findapp.py │ └── osax.py ├── doc │ ├── py-appscript-logo.png │ ├── aem-manual │ │ ├── aemreferenceinheritance.gif │ │ ├── index.html │ │ ├── 07_findapp.html │ │ ├── 01_introduction.html │ │ ├── 08_examples.html │ │ ├── 03_packingandunpackingdata.html │ │ └── 02_apioverview.html │ ├── appscript-manual │ │ ├── relationships_example.gif │ │ ├── automation_permissions.gif │ │ ├── python_to_itunes_event.gif │ │ ├── accessibility_permissions.gif │ │ ├── application_architecture.gif │ │ ├── application_architecture2.gif │ │ ├── finder_to_textedit_event.gif │ │ ├── permission_to_automate_dialog.gif │ │ ├── index.html │ │ ├── 05_keywordconversion.html │ │ ├── 08_realvsgenericreferences.html │ │ ├── 01_introduction.html │ │ ├── 12_commandexamples.html │ │ └── 14_notes.html │ ├── index.html │ ├── mactypes-manual │ │ ├── index.html │ │ ├── 01_introduction.html │ │ ├── 04_unitsclass.html │ │ ├── 02_aliasclass.html │ │ └── 03_fileclass.html │ ├── osax-manual │ │ ├── index.html │ │ ├── 02_interface.html │ │ ├── 03_examples.html │ │ ├── 04_notes.html │ │ └── 01_introduction.html │ └── full.css ├── test │ ├── testall.sh │ ├── README │ ├── test_findapp.py │ ├── test_sdef.py │ ├── test_osax.py │ ├── test_mactypes.py │ ├── test_appscriptreference.py │ ├── test_aemreference.py │ ├── test_codecs.py │ └── test_appscriptcommands.py ├── sample │ ├── appscript │ │ ├── Open_file_in_TextEdit.py │ │ ├── List_Music_playlist_names.py │ │ ├── Select_all_HTML_files.py │ │ ├── List_application_processes.py │ │ ├── Contacts_list_people_with_emails.py │ │ ├── Music_remove_duplicates.py │ │ ├── Add_line_numbers_to_TextEdit_doc.py │ │ ├── Print_folder_tree.py │ │ ├── Stagger_Finder_windows.py │ │ ├── Calendar_todos_to_OmniOutliner.py │ │ ├── Add_Calendar_event.py │ │ ├── Info_for_selected_photos.py │ │ ├── Calendar_to_tab_delimited_table.py │ │ ├── HTMLize_TextEdit_doc.py │ │ ├── Path_to_front_Finder_window.py │ │ ├── GUI_scripting.py │ │ ├── Get_info_for_Safari_window.py │ │ ├── Make_Mail_message.py │ │ ├── Music_missing_track_finder.py │ │ └── TextEdit_demo.py │ ├── aem │ │ ├── remote_scripting.py │ │ ├── simple_get.py │ │ ├── filter_reference.py │ │ └── make_and_set.py │ └── osax │ │ └── standard_additions.py ├── MANIFEST.in ├── appscript TODO.txt ├── setup.py ├── README.rst └── ext │ └── ae.h ├── py-osaterminology ├── lib │ └── osaterminology │ │ ├── tables │ │ ├── __init__.py │ │ └── tableparser.py │ │ ├── renderers │ │ ├── __init__.py │ │ ├── relationships.py │ │ ├── typerenderers.py │ │ └── inheritance.py │ │ ├── sax │ │ └── __init__.py │ │ ├── __init__.py │ │ ├── dom │ │ ├── __init__.py │ │ ├── appscripttypes.py │ │ └── applescripttypes.py │ │ ├── defaultterminology │ │ ├── __init__.py │ │ └── nodeautomation.py │ │ └── makeidentifier │ │ ├── nodeautomation.py │ │ ├── pyappscript.py │ │ ├── rbappscript.py │ │ └── __init__.py ├── MANIFEST.in ├── test │ └── test parse.py ├── setup.py └── README.rst ├── ASDictionary ├── src │ ├── icon.ai │ ├── ASDictionary.icns │ ├── python-entitlements.plist │ ├── build.sh │ ├── setup.py │ └── dictionaryexporter.py ├── test │ ├── test_2.rb │ ├── test_3.rb │ └── test_1.rb └── README ├── ASTranslate ├── src │ ├── icon.ai │ ├── ASTranslate.icns │ ├── python-entitlements.plist │ ├── constants.py │ ├── build.sh │ ├── setup.py │ ├── ae.h │ ├── pythonrenderer.py │ ├── ASTranslate.py │ └── eventformatter.py └── README ├── .gitignore ├── README.md ├── py-aemreceive ├── setup.py ├── lib │ └── aemreceive │ │ ├── __init__.py │ │ └── handlererror.py └── README.rst └── .github └── workflows └── main.yml /py-appscript/lib/mactypes.py: -------------------------------------------------------------------------------- 1 | from aem.mactypes import * -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/tables/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /py-osaterminology/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | recursive-include lib *.py 3 | 4 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/renderers/__init__.py: -------------------------------------------------------------------------------- 1 | """osaterminology.renderers""" 2 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/sax/__init__.py: -------------------------------------------------------------------------------- 1 | """osaterminology.sax -- parse aetes""" -------------------------------------------------------------------------------- /ASDictionary/src/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/ASDictionary/src/icon.ai -------------------------------------------------------------------------------- /ASTranslate/src/icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/ASTranslate/src/icon.ai -------------------------------------------------------------------------------- /ASDictionary/src/ASDictionary.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/ASDictionary/src/ASDictionary.icns -------------------------------------------------------------------------------- /ASTranslate/src/ASTranslate.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/ASTranslate/src/ASTranslate.icns -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/__init__.py: -------------------------------------------------------------------------------- 1 | """osaterminology -- Application terminology parser and tools. """ -------------------------------------------------------------------------------- /py-appscript/doc/py-appscript-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/py-appscript-logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | 4 | .pyc$ 5 | .pyo$ 6 | 7 | **/build 8 | **/dist 9 | **/*.egg-info 10 | */MANIFEST 11 | 12 | -------------------------------------------------------------------------------- /ASDictionary/test/test_2.rb: -------------------------------------------------------------------------------- 1 | #!/usr//bin/ruby 2 | 3 | require 'appscript' 4 | include Appscript 5 | 6 | app('Finder').help('-t -i') 7 | 8 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/aemreferenceinheritance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/aem-manual/aemreferenceinheritance.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/relationships_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/relationships_example.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/automation_permissions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/automation_permissions.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/python_to_itunes_event.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/python_to_itunes_event.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/accessibility_permissions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/accessibility_permissions.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/application_architecture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/application_architecture.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/application_architecture2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/application_architecture2.gif -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/finder_to_textedit_event.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/finder_to_textedit_event.gif -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/dom/__init__.py: -------------------------------------------------------------------------------- 1 | """osaterminology.dom -- parse aetes and sdefs into Dictionary instances """ 2 | 3 | from . import aeteparser, sdefparser -------------------------------------------------------------------------------- /py-appscript/test/testall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for f in `ls | grep '^test_'`; 4 | do 5 | echo $f 6 | /usr/bin/env python3.2 $f 7 | echo 8 | echo 9 | done 10 | 11 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/permission_to_automate_dialog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhas/appscript/HEAD/py-appscript/doc/appscript-manual/permission_to_automate_dialog.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # appscript 2 | 3 | Please see the individual READMEs: 4 | 5 | * [aemreceive](py-aemreceive/README.rst) 6 | * [appscript](py-appscript/README.rst) 7 | * [osaterminology](py-osaterminology/README.rst) 8 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Open_file_in_TextEdit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Opens a file in TextEdit. 4 | 5 | from appscript import * 6 | 7 | app('TextEdit').open(mactypes.Alias('Open_file_in_TextEdit.py')) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/List_Music_playlist_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # List names of playlists in Music. 4 | 5 | from appscript import * 6 | 7 | print(app('Music').sources[1].user_playlists.name.get()) -------------------------------------------------------------------------------- /py-appscript/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | recursive-include doc *.css *.html *.png *.gif 3 | recursive-include ext *.h *.c 4 | recursive-include lib *.py 5 | recursive-include sample *.py 6 | recursive-include test *.py README 7 | 8 | -------------------------------------------------------------------------------- /py-osaterminology/test/test parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from osaterminology.dom.sdefparser import * 4 | 5 | res = parseapp('/System/Applications/Reminders.app') 6 | 7 | print(res) 8 | print(res.classes()) 9 | print(res.commands()) -------------------------------------------------------------------------------- /ASDictionary/test/test_3.rb: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/ruby 2 | 3 | require 'appscript'; include Appscript 4 | require 'pp' 5 | 6 | app('Finder').home.folders[con.folders[1], con.folders['Movies']].get 7 | 8 | 9 | pp app('Finder').desktop.items.name.help('-s').get 10 | 11 | -------------------------------------------------------------------------------- /ASDictionary/src/python-entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ASTranslate/src/python-entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /py-appscript/appscript TODO.txt: -------------------------------------------------------------------------------- 1 | replace LSOpenApplication() with LSOpenFromURLSpec() 2 | 3 | use LSCopyApplicationURLsForBundleIdentifier() to find all installed apps with given bundle ID 4 | 5 | problem: LSFindApplicationForInfo() has no LS replacement for looking up by app name; looks like -[NSWorkspace fullPathForApplication:] is only alternative 6 | -------------------------------------------------------------------------------- /py-appscript/sample/aem/remote_scripting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aem import * 4 | 5 | # get text of document 1 of application "TextEdit" of machine "eppc://192.168.2.5" 6 | textedit = Application(url='eppc://192.168.2.5/TextEdit') 7 | print textedit.event(b'coregetd', {b'----':app.elements(b'docu').byindex(1).property(b'ctxt')}).send() 8 | 9 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Select_all_HTML_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Selects all .htm/.html files in the top Finder window. 4 | 5 | from appscript import * 6 | 7 | finder = app('Finder') 8 | 9 | finder.activate() 10 | 11 | w = finder.Finder_windows[1].target.get() 12 | w.files[its.name_extension.isin(['htm', 'html'])].select() -------------------------------------------------------------------------------- /py-appscript/test/README: -------------------------------------------------------------------------------- 1 | Some of the code in these tests may not be 100% portable (hardcoded paths, etc) so may fail for some users, although they have been passed on G4 and i386 hardware running OS X 10.4.x + Python 2.5. 2 | 3 | Note that some mactypes unit tests may fail as Alias and File comparisons are rather dumb and don't normalise path strings before comparing/hashing. -------------------------------------------------------------------------------- /py-appscript/sample/appscript/List_application_processes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Uses System Events to list all running applications. 4 | 5 | from appscript import app 6 | 7 | sysevents = app('System Events') 8 | 9 | processnames = sysevents.application_processes.name.get() 10 | processnames.sort(key=(lambda x: x.lower())) 11 | print('\n'.join(processnames)) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Contacts_list_people_with_emails.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Lists the name and email(s) of every person in Contacts 4 | # with one or more email addresses. 5 | 6 | from appscript import * 7 | 8 | peopleref = app('Contacts').people[its.emails != []] 9 | for name, emails in zip(peopleref.name.get(), peopleref.emails.value.get()): 10 | print(name, emails) -------------------------------------------------------------------------------- /py-appscript/sample/osax/standard_additions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from osax import * 4 | 5 | # create an OSAX instance for StandardAdditions 6 | sa = OSAX() 7 | 8 | sa.beep(2) 9 | 10 | sa.activate() # bring current process to front so dialog box will be visible 11 | print(sa.display_dialog('Hello World!')) 12 | # {k.button_returned: 'OK'} 13 | 14 | print(sa.path_to(k.scripts_folder, from_=k.local_domain)) 15 | # mactypes.Alias('/Library/Scripts') -------------------------------------------------------------------------------- /py-aemreceive/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup 3 | 4 | 5 | setup( 6 | name = "aemreceive", 7 | version = "0.5.0", 8 | description = "Basic Apple event handling support for Python-based Mac OS X applications.", 9 | url='http://appscript.sourceforge.net', 10 | license='Public Domain', 11 | platforms=['Mac OS X'], 12 | packages = ['aemreceive'], 13 | extra_path = "aeosa", 14 | package_dir = { '': 'lib' }, 15 | install_requires = ['appscript >= 1.2.0'], 16 | ) 17 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Music_remove_duplicates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Removes duplicate tracks from the selected playlist. 4 | 5 | from appscript import * 6 | 7 | count = 0 8 | foundids = [] 9 | tracks = app('Music').browser_windows[1].view.tracks 10 | for id, track in zip(tracks.database_ID.get(), tracks.get()): 11 | if id in foundids: 12 | track.delete() 13 | count += 1 14 | else: 15 | foundids.append(id) 16 | print('%i tracks removed.' % count) -------------------------------------------------------------------------------- /py-aemreceive/lib/aemreceive/__init__.py: -------------------------------------------------------------------------------- 1 | """aemreceive -- Install Python functions as Apple event handlers.""" 2 | 3 | from aem import AEType, AEEnum # re-exported for convenience 4 | from aem import kae # re-export for convenience 5 | 6 | from .main import kMissingValue, Codecs, installeventhandler, removeeventhandler, installcoercionhandler, removecoercionhandler 7 | from .handlererror import EventHandlerError 8 | from .typedefs import kArgDesc, kArgMissingValue, ArgType, ArgListOf, ArgEnum, ArgMultiChoice 9 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Add_line_numbers_to_TextEdit_doc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Adds a zero-padded line number to every line in the front TextEdit document. 4 | 5 | from appscript import * 6 | from math import log10 7 | 8 | textref = app('TextEdit').documents[1].text 9 | 10 | lc = textref.paragraphs.count() 11 | fstr = '%%.%ii ' % (int(log10(lc)) + 1) 12 | for i in range(lc): 13 | textref.paragraphs[i+1].characters.beginning.make( 14 | new=k.character, with_data=fstr % (i + 1)) -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/defaultterminology/__init__.py: -------------------------------------------------------------------------------- 1 | """defaultterminology -- translation tables between appscript-style typenames and corresponding AE codes """ 2 | 3 | def getterms(style='py-appscript'): 4 | if style == 'py-appscript': 5 | from . import pyappscript as terms 6 | elif style == 'rb-scpt': 7 | from . import rbappscript as terms 8 | elif style == 'nodeautomation': 9 | from . import nodeautomation as terms 10 | else: 11 | raise KeyError('Unknown style %r' % style) 12 | return terms -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Print_folder_tree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Prints the sub-folder hierarchy of a given folder as a list of folder names 4 | # indented according to depth. 5 | 6 | from appscript import * 7 | 8 | 9 | def printfoldertree(folder, indent=''): 10 | """Print a tab-indented list of a folder tree.""" 11 | print(indent + folder.name.get()) 12 | for folder in folder.folders.get(): 13 | printfoldertree(folder, indent + '\t') 14 | 15 | 16 | printfoldertree(app('Finder').home.folders['Documents']) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Stagger_Finder_windows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Rearranges Finder windows diagonally across screen with title bars one above 4 | # another. 5 | # 6 | # (Could be adapted to work with any scriptable application that defines a 7 | #standard window class.) 8 | 9 | from appscript import * 10 | 11 | x, y = 0, 44 12 | offset = 22 13 | 14 | for window in app('Finder').windows.get()[::-1]: 15 | x1, y1, x2, y2 = window.bounds.get() 16 | window.bounds.set((x, y, x2 - x1 + x, y2 - y1 + y)) 17 | x += offset 18 | y += offset -------------------------------------------------------------------------------- /py-appscript/sample/aem/simple_get.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import aem 4 | 5 | 6 | # Get name of application "TextEdit" 7 | textedit = aem.Application('/System/Applications/TextEdit.app') 8 | print(textedit.event(b'coregetd', {b'----': aem.app.property(b'pnam')}).send()) 9 | 10 | 11 | # Get list of items in home folder as mactype.Alias objects: 12 | finder = aem.Application(aem.findapp.byname('Finder')) 13 | print(finder.event(b'coregetd', { 14 | b'----': aem.app.property(b'home').elements(b'cobj'), 15 | aem.kae.keyAERequestedType: aem.AEType(aem.kae.typeAlias), 16 | }).send()) 17 | 18 | 19 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Calendar_todos_to_OmniOutliner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Creates an OmniOutliner document listing all the 'To Do' items from Calendar. 4 | 5 | from appscript import * 6 | 7 | toprow = app('OmniOutliner').documents.end.make(new=k.document).rows[1] 8 | toprow.properties.set({k.topic: 'Master todo list', k.expanded: True}) 9 | 10 | for cal in app('Calendar').calendars.get(): 11 | subrow = toprow.rows.end.make(new=k.row, 12 | with_properties={k.topic: cal.name.get(), k.expanded: True}) 13 | for summary in cal.todos.summary.get(): 14 | subrow.rows.end.make(new=k.row, 15 | with_properties={k.topic: summary}) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Add_Calendar_event.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Shows how to add an event to Calendar 4 | 5 | import datetime 6 | from appscript import * 7 | 8 | # Add event to Home calendar to run from 7pm to 9 pm today 9 | # (note: when only time is given, appscript uses current date) 10 | 11 | calendarname = 'Home' 12 | start = datetime.time(19, 0, 0) # 7 pm 13 | end = datetime.time(21, 0, 0) # 9 pm 14 | summary = 'Watch dinner & eat teevee' 15 | 16 | app('Calendar').calendars[calendarname].events.end.make( 17 | new=k.event, with_properties={ 18 | k.start_date: start, 19 | k.end_date: end, 20 | k.summary: summary}) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Info_for_selected_photos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # A simple function that gets information on every photo currently selected 4 | # in Photos. 5 | 6 | from appscript import * 7 | 8 | def infoforselectedphotos(): 9 | """Get properties of currently selected photo(s) in Photos.""" 10 | selection = app('Photos').selection.get() 11 | photos = [] 12 | if selection[0].class_.get() == k.photo: 13 | for photo in selection: 14 | photos.append(photo.properties.get()) 15 | else: 16 | raise RuntimeError('No photos selected.') 17 | return photos 18 | 19 | # Test 20 | from pprint import pprint 21 | pprint(infoforselectedphotos()) -------------------------------------------------------------------------------- /py-osaterminology/setup.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name = "osaterminology", 7 | version = "0.16.0", 8 | description = "Parse and render aete/sdef resources.", 9 | url='http://appscript.sourceforge.net', 10 | license='Public Domain', 11 | platforms=['Mac OS X'], 12 | packages = [ 13 | 'osaterminology', 14 | 'osaterminology/defaultterminology', 15 | 'osaterminology/dom', 16 | 'osaterminology/makeidentifier', 17 | 'osaterminology/renderers', 18 | 'osaterminology/sax', 19 | 'osaterminology/tables', 20 | ], 21 | extra_path = "aeosa", 22 | package_dir = { '': 'lib' }, 23 | install_requires = ['appscript >= 1.2.0', 'htmltemplate >= 2.2.1', 'lxml >= 4.7.1'], 24 | ) 25 | -------------------------------------------------------------------------------- /py-aemreceive/README.rst: -------------------------------------------------------------------------------- 1 | About aemreceive 2 | ================ 3 | 4 | Provides basic support for handling Apple events. 5 | 6 | Used by ASDictionary. 7 | 8 | 9 | Requirements 10 | ------------ 11 | 12 | - Python 2.3+ 13 | - Mac OS X 10.4+ 14 | - appscript 0.22.0+ 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | aemreceive is packaged using the standard Python Distribution Utilities 21 | (a.k.a. Distutils). To install appscript, cd to the aemreceive-0.4.0 22 | directory and run: 23 | 24 | python setup.py install 25 | 26 | Setuptools will be used if available, otherwise the setup.py script will 27 | revert to distutils. 28 | 29 | 30 | Copyright 31 | --------- 32 | 33 | aemreceive is released into the public domain. 34 | -------------------------------------------------------------------------------- /ASDictionary/test/test_1.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'appscript' 4 | include Appscript 5 | 6 | Constructors = { 7 | :by_path => 'path', 8 | :by_pid => 'pid', 9 | :by_url => 'url', 10 | :by_aem_app => 'aemapp', 11 | :current => 'current', 12 | } 13 | 14 | te = app('TextEdit') 15 | 16 | help_agent = AEM::Application.by_path(FindApp.by_id('net.sourceforge.appscript.asdictionary')) 17 | 18 | ref = te.documents 19 | 20 | app_data = te.AS_app_data 21 | 22 | puts help_agent.event('AppSHelp', { 23 | 'Cons' => Constructors[app_data.constructor], 24 | 'Iden' => app_data.constructor == :by_aem_app ? app_data.identifier.address_desc : app_data.identifier, 25 | 'Styl' => 'rb-scpt', 26 | 'Flag' => '-t', 27 | 'aRef' => app_data.pack(ref), 28 | }).send 29 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Calendar_to_tab_delimited_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Outputs an Calendar calendar to tab-delimited table listing start and end times, 4 | # summary and description for each event. 5 | 6 | from appscript import * 7 | 8 | calendarname = 'US Holidays' 9 | 10 | ev = app('Calendar').calendars[calendarname].events 11 | events = zip( 12 | ev.start_date.get(), 13 | ev.end_date.get(), 14 | ev.summary.get(), 15 | ev.description.get()) 16 | 17 | # Print tab-delimited table: 18 | print('STARTS\tENDS\tSUMMARY\tDESCRIPTION') 19 | for event in events: 20 | print('\t'.join(['--' if item == k.missing_value else 21 | str(item).replace('\t', ' ').replace('\n', ' ') 22 | for item in event])) -------------------------------------------------------------------------------- /py-appscript/sample/appscript/HTMLize_TextEdit_doc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Convert the front TextEdit document to HTML. 4 | 5 | from appscript import * 6 | 7 | te = app('TextEdit') 8 | 9 | s = te.documents[1].text.get() 10 | for c, r in [('&', '&'), ('<', '<'), ('>', '>'), ('\t', ' ')]: 11 | s = s.replace(c, r) 12 | te.documents[1].text.set(s) 13 | 14 | # Note: in theory, it should be possible to use TextEdit's 'set' command to 15 | # perform the substitutions directly: 16 | # 17 | # for c, r in [('&', '&'), ('<', '<'), ('>', '>')]: 18 | # te.documents[1].characters[its == c].set(r) 19 | # 20 | # Unfortunately, the Text Suite is buggy and doesn't handle this correctly, 21 | # so it's necessary to retrieve the document text and process it in Python. -------------------------------------------------------------------------------- /py-appscript/test/test_findapp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | from aem import findapp 5 | 6 | 7 | class TC_FindApp(unittest.TestCase): 8 | 9 | def test_find(self): 10 | for val, res in [ 11 | ['/System/Applications/Calendar.app', '/System/Applications/Calendar.app'], 12 | ['calendar.app', '/System/Applications/Calendar.app'], 13 | ['CALENDAR.APP', '/System/Applications/Calendar.app'], 14 | ['CALENDAR', '/System/Applications/Calendar.app'], 15 | ]: 16 | self.assertEqual(res, findapp.byname(val)) 17 | self.assertEqual('/System/Library/CoreServices/Finder.app', findapp.byid('com.apple.finder')) 18 | self.assertRaises(findapp.ApplicationNotFoundError, findapp.byname, 'NON-EXISTENT-APP') 19 | 20 | if __name__ == '__main__': 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /py-osaterminology/README.rst: -------------------------------------------------------------------------------- 1 | About osaterminology 2 | ==================== 3 | 4 | osaterminology implements sdef terminology parsers and renderers. 5 | 6 | Used by ASDictionary and ASTranslate. 7 | 8 | 9 | Requirements 10 | ------------ 11 | 12 | - Python 3.7+ 13 | - macOS 10.12+ 14 | - appscript 1.2.0+ 15 | 16 | 17 | Installation 18 | ------------ 19 | 20 | osaterminology is packaged using the standard Python Distribution Utilities 21 | (a.k.a. Distutils). To install osaterminology, cd to the osaterminology-0.15.0 22 | directory and run: 23 | 24 | python setup.py install 25 | 26 | Setuptools will be used if available, otherwise the setup.py script will revert 27 | to distutils. 28 | 29 | 30 | Copyright 31 | --------- 32 | 33 | osaterminology is released into the public domain. 34 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Path_to_front_Finder_window.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Gets the path to the front Finder window, or to the Desktop folder if 4 | # no Finder windows are open. 5 | 6 | from appscript import * 7 | 8 | finder = app('Finder') 9 | 10 | w = finder.Finder_windows[1] 11 | 12 | if w.exists(): # is there a Finder window open? 13 | if w.target.class_.get() == k.computer_object: 14 | # 'Computer' windows don't have a target folder, for obvious reasons. 15 | raise RuntimeError("Can't get path to 'Computer' window.") 16 | folder = w.target # get a reference to its target folder 17 | else: 18 | folder = finder.desktop # get a reference to the desktop folder 19 | 20 | path = folder.get(resulttype=k.alias).path # get folder's path 21 | 22 | print(path) -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/makeidentifier/nodeautomation.py: -------------------------------------------------------------------------------- 1 | """makeidentifier.nodeautomation -- Reserved keywords for nodeautomation""" 2 | 3 | 4 | kReservedWords = [ 5 | 'at', 'named', 'ID', 'previous', 'next', 'slice', 'thru', 'where', 6 | 'first', 'middle', 'last', 'any', 'every', 'beginning', 'end', 'before', 'after', 7 | 'lt', 'le', 'eq', 'ne', 'gt', 'ge', 'beginsWith', 'endsWith', 'contains', 'isIn', 'and', 'or', 'not', 8 | 'customRoot', 'launch', 'isRunning', 'property', 'elements', 'sendAppleEvent', 'help', 9 | 'asType', 'sendOptions', 'withTimeout', 'ignoring', # command attributes 10 | 'toString', 'valueOf', 'constructor', 'isSpecifier', 'isKeyword', 11 | 'fromTypeCode', 'fromEnumCode', # raw Keyword constructors 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /py-appscript/sample/aem/filter_reference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aem import * 4 | 5 | # tell app "Finder" to get every item of home whose name begins with "d" and name is not "Documents" 6 | 7 | print (Application(findapp.byname('Finder')).event(b'coregetd', {b'----': 8 | 9 | app.property(b'home').elements(b'cobj').byfilter( 10 | 11 | its.property(b'pnam').beginswith('d') .AND (its.property(b'pnam').ne('Documents')) 12 | 13 | ) 14 | }).send()) 15 | 16 | # Result should be list of folders of home whose name begins with 'd' except for 'Documents', e.g.: 17 | # 18 | # [ 19 | # app.property(b'sdsk').elements(b'cfol').byname('Users').elements(b'cfol').byname('has').elements(b'cfol').byname('Desktop'), 20 | # app.property(b'sdsk').elements(b'cfol').byname('Users').elements(b'cfol').byname('has').elements(b'cfol').byname('Downloads') 21 | # ] -------------------------------------------------------------------------------- /py-appscript/sample/appscript/GUI_scripting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # A simple demonstration of GUI Scripting 4 | 5 | from appscript import * 6 | 7 | texteditgui = app('System Events').processes['TextEdit'] 8 | 9 | app('TextEdit').activate() 10 | 11 | mref = texteditgui.menu_bars[1].menus 12 | mref['File'].menu_items['New'].click() 13 | mref['Edit'].menu_items['Paste'].click() 14 | mref['Window'].menu_items['Zoom'].click() 15 | 16 | # Note: TextEdit's 'Zoom' menu item has a different name in OS X 10.4: 17 | # mref['Window'].menu_items['Zoom Model'].click() 18 | 19 | # Note: the System Events object model is slightly different in OS X 10.3, 20 | # so 10.3 users should change the last three lines to: 21 | # mref['File'].menu_bar_items['File'].menu_items['New'].click() 22 | # mref['Edit'].menu_bar_items['Edit'].menu_items['Paste'].click() 23 | # mref['Window'].menu_bar_items['Window'].menu_items['Zoom Window'].click() -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Get_info_for_Safari_window.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Get the name and URL of a Safari window. 4 | 5 | from appscript import app 6 | 7 | def infoforbrowserwindow(idx=1): 8 | """Get name and URL of a Safari window. (Raises a RuntimeError 9 | if window doesn't exist or is empty.) 10 | idx : integer -- index of window (1 = front) 11 | Result : tuple -- a tuple of two unicode strings: (name, url) 12 | """ 13 | safari = app('Safari') 14 | if not safari.windows[idx].exists(): 15 | raise RuntimeError("Window %r doesn't exist." % idx) 16 | else: 17 | pagename = safari.windows[idx].name.get() 18 | pageurl = safari.windows[idx].document.URL.get() 19 | if not pageurl: 20 | raise RuntimeError("Window %r is empty." % idx) 21 | return pagename, pageurl 22 | 23 | 24 | # test 25 | print(infoforbrowserwindow()) -------------------------------------------------------------------------------- /py-aemreceive/lib/aemreceive/handlererror.py: -------------------------------------------------------------------------------- 1 | """handlererror -- Used to pass known errors back to client.""" 2 | 3 | from aem import kae 4 | 5 | 6 | class EventHandlerError(Exception): 7 | """Event-handling callbacks should raise an EventHandlerError exception to send an error message back to client.""" 8 | def __init__(self, number, message=None, object=None, coercion=None): 9 | self.number = number 10 | self.message = message 11 | self.object = object 12 | self.coercion = coercion 13 | Exception.__init__(self, number, message, object, coercion) 14 | 15 | def get(self): # used internally by aemreceive 16 | result = {kae.keyErrorNumber: self.number} 17 | for key, val in [ 18 | (kae.keyErrorString, self.message), 19 | (kae.kOSAErrorOffendingObject, self.object), 20 | (kae.kOSAErrorExpectedType, self.coercion)]: 21 | if val is not None: 22 | result[key] = val 23 | return result 24 | 25 | def __str__(self): 26 | return '%s (%i)' % (self.message, self.number) -------------------------------------------------------------------------------- /ASTranslate/src/constants.py: -------------------------------------------------------------------------------- 1 | """ constants """ 2 | 3 | kLanguageCount = 3 4 | 5 | # (note: these are same as indexes of segmented control in window) 6 | kLangPython = 0 7 | kLangRuby = 1 8 | kLangNode = 2 9 | 10 | kLangAll = None 11 | 12 | 13 | class kNoParam: pass 14 | 15 | 16 | class UntranslatedKeywordError(RuntimeError): 17 | 18 | def __init__(self, kind, code, lang): 19 | RuntimeError.__init__(self, kind, code, lang) 20 | self.kind, self.code, self.lang = kind, code, lang 21 | 22 | def __str__(self): 23 | return "Couldn't find keyword definition for %s code %r and %s translator can't display raw AE codes."% (self.kind, self.code, self.lang) 24 | 25 | class UntranslatedUserPropertyError(RuntimeError): 26 | 27 | def __init__(self, name, lang): 28 | RuntimeError.__init__(self, name, lang) 29 | self.name, self.lang = name, lang 30 | 31 | def __str__(self): 32 | return "Found user-defined property %r but %s translator can only display dictionary-defined names."% (self.name, self.lang) 33 | 34 | -------------------------------------------------------------------------------- /py-appscript/lib/appscript/__init__.py: -------------------------------------------------------------------------------- 1 | """py3-appscript -- High-level Mac OS X application scripting support for Python 3. """ 2 | 3 | __version__ = 'dev' 4 | 5 | __all__ = ['ApplicationNotFoundError', 'CommandError', 'CantLaunchApplicationError', 6 | 'app','con', 'its', 'k','mactypes'] 7 | 8 | from aem.findapp import ApplicationNotFoundError 9 | from aem import CantLaunchApplicationError 10 | from .reference import app, CommandError 11 | from .genericreference import con, its 12 | from .keywordwrapper import k 13 | from aem import mactypes 14 | 15 | # The following classes are exposed for occasional typechecking purposes. To avoid excess 16 | # namespace pollution they aren't added to the parent namespace when 'from appscript import *' 17 | # is used, so must be referred to like this [e.g.]: 18 | # 19 | # import appscript 20 | # isinstance(obj, appscript.Reference) 21 | 22 | from .reference import Command, Reference, Application, GenericApp 23 | from .genericreference import GenericReference 24 | from .keywordwrapper import Keyword -------------------------------------------------------------------------------- /py-appscript/lib/appscript/reservedkeywords.py: -------------------------------------------------------------------------------- 1 | """reservedkeywords """ 2 | 3 | import keyword 4 | 5 | # Important: the following must be reserved: 6 | # 7 | # - names of properties and methods used in reference.Application and reference.Reference classes 8 | # - names of built-in keyword arguments in reference.Command.__call__ 9 | 10 | kReservedKeywords = [ 11 | "ID", 12 | "beginning", 13 | "end", 14 | "before", 15 | "after", 16 | "previous", 17 | "next", 18 | "first", 19 | "middle", 20 | "last", 21 | "any", 22 | "beginswith", 23 | "endswith", 24 | "contains", 25 | "isin", 26 | "doesnotbeginwith", 27 | "doesnotendwith", 28 | "doesnotcontain", 29 | "isnotin", 30 | "AND", 31 | "NOT", 32 | "OR", 33 | "begintransaction", 34 | "aborttransaction", 35 | "endtransaction", 36 | "isrunning", 37 | "permissiontoautomate", # 10.14+ 38 | "resulttype", 39 | "canaskforconsent", # 10.14+ 40 | "ignore", 41 | "timeout", 42 | "waitreply", 43 | "help", 44 | "relaunchmode", 45 | "as", 46 | "with", 47 | "True", 48 | "False", 49 | "None", 50 | ] + keyword.kwlist -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/makeidentifier/pyappscript.py: -------------------------------------------------------------------------------- 1 | """makeidentifier.pyappscript -- Reserved keywords for py-appscript""" 2 | 3 | # Important: the following must be reserved: 4 | # 5 | # - names of properties and methods used in reference.Application and reference.Reference classes 6 | # - names of built-in keyword arguments in reference.Command.__call__ 7 | 8 | import keyword 9 | 10 | kReservedWords = [ 11 | "ID", 12 | "beginning", 13 | "end", 14 | "before", 15 | "after", 16 | "previous", 17 | "next", 18 | "first", 19 | "middle", 20 | "last", 21 | "any", 22 | "beginswith", 23 | "endswith", 24 | "contains", 25 | "isin", 26 | "doesnotbeginwith", 27 | "doesnotendwith", 28 | "doesnotcontain", 29 | "isnotin", 30 | "AND", 31 | "NOT", 32 | "OR", 33 | "begintransaction", 34 | "aborttransaction", 35 | "endtransaction", 36 | "isrunning", 37 | "resulttype", 38 | "ignore", 39 | "timeout", 40 | "waitreply", 41 | "help", 42 | "relaunchmode", 43 | "as", 44 | "with", 45 | "True", 46 | "False", 47 | "None", 48 | ] + keyword.kwlist 49 | 50 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Make_Mail_message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Compose an outgoing message in Apple's Mail.app. 4 | 5 | from appscript import * 6 | 7 | def makemessage(addresses, subject, content, showwindow=False): 8 | """Make an outgoing message in Mail. 9 | addresses : list of unicode -- a list of email addresses 10 | subject : unicode -- the message subject 11 | content : unicode -- the message content 12 | showwindow : Boolean -- show message window in Mail 13 | Result : reference -- reference to the new outgoing message 14 | """ 15 | mail = app('Mail') 16 | msg = mail.make( 17 | new=k.outgoing_message, 18 | with_properties={k.visible: showwindow}) 19 | for anaddress in addresses: 20 | msg.to_recipients.end.make( 21 | new=k.recipient, 22 | with_properties={k.address: anaddress}) 23 | msg.subject.set(subject) 24 | msg.content.set(content) 25 | return msg 26 | 27 | 28 | # test 29 | print(makemessage(['joe@example.com', 'jane@example.net'], 30 | 'Hello World', 'Some body text.', True)) -------------------------------------------------------------------------------- /ASDictionary/src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # build, sign, notarize, and zip Python3 app for distribution 4 | # 5 | # CODESIGN_APP_IDENTITY must be Developer ID 6 | 7 | appname='ASDictionary' 8 | 9 | appversion='0.15.1' # update this and setup.py for new release 10 | 11 | 12 | set -e 13 | 14 | python3 setup.py clean 15 | 16 | python3 setup.py py2app 17 | 18 | cd dist 19 | 20 | find *.app -iname '*.so' -or -iname '*.dylib' | while read libfile; do 21 | codesign -f -s "$CODESIGN_APP_IDENTITY" --timestamp -o runtime --entitlements ../python-entitlements.plist "$libfile" 22 | done 23 | 24 | 25 | codesign -f -s "$CODESIGN_APP_IDENTITY" -v --deep --timestamp -o runtime --entitlements ../python-entitlements.plist "${appname}.app" 26 | 27 | ditto -c -k --keepParent "${appname}.app" "${appname}.zip" 28 | 29 | xcrun notarytool submit "${appname}.zip" --keychain-profile notary --wait 30 | 31 | xcrun stapler staple "${appname}.app" 32 | 33 | rm "${appname}.zip" 34 | 35 | cd .. 36 | 37 | cp ../README dist/README 38 | 39 | appdir="${appname}-${appversion}" 40 | 41 | mv dist "${appdir}" 42 | 43 | ditto -c -k --keepParent "${appdir}" "${appdir}.zip" 44 | 45 | rm -rf build 46 | 47 | -------------------------------------------------------------------------------- /ASTranslate/src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # build, sign, notarize, and zip Python3 app for distribution 4 | # 5 | # CODESIGN_APP_IDENTITY must be Developer ID 6 | 7 | appname='ASTranslate' 8 | 9 | appversion='0.7.1' # update this and setup.py for new release 10 | 11 | 12 | set -e 13 | 14 | python3 setup.py clean 15 | 16 | python3 setup.py py2app 17 | 18 | cd dist 19 | 20 | find "${appname}.app" -iname '*.so' -or -iname '*.dylib' | while read libfile; do 21 | codesign -f -s "$CODESIGN_APP_IDENTITY" --timestamp -o runtime --entitlements ../python-entitlements.plist "$libfile" 22 | done 23 | 24 | 25 | codesign -f -s "$CODESIGN_APP_IDENTITY" -v --deep --timestamp -o runtime --entitlements ../python-entitlements.plist "${appname}.app" 26 | 27 | ditto -c -k --keepParent "${appname}.app" "${appname}.zip" 28 | 29 | xcrun notarytool submit "${appname}.zip" --keychain-profile notary --wait 30 | 31 | xcrun stapler staple "${appname}.app" 32 | 33 | rm "${appname}.zip" 34 | 35 | cd .. 36 | 37 | cp ../README dist/README 38 | 39 | appdir="${appname}-${appversion}" 40 | 41 | mv dist "${appdir}" 42 | 43 | ditto -c -k --keepParent "${appdir}" "${appdir}.zip" 44 | 45 | rm -rf build 46 | -------------------------------------------------------------------------------- /py-appscript/test/test_sdef.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from appscript import terminology as t 4 | 5 | from aem import Application 6 | 7 | #app = Application(path='/System/Library/CoreServices/Finder.app') 8 | #app = Application(path='/Applications/Adobe Photoshop 2022/Adobe Photoshop 2022.app') 9 | #app = Application(path='/Applications/Adobe Illustrator 2022/Adobe Illustrator.app') 10 | app = Application(path='/Applications/Microsoft Excel.app') 11 | 12 | keys = ['TYPE', 'TYPE', 'REF', 'REF'] 13 | 14 | from pprint import pprint 15 | sdef = t.sdefforapp(app) 16 | tables = t.tablesforsdef(sdef) 17 | 18 | aete = t.aetesforapp(app) 19 | tables2 = t.tablesforaetes(aete) 20 | 21 | print('\nSDEF only:') 22 | for a, b, c in zip(tables, tables2, keys): 23 | diff = set(a) - (set(b)) 24 | print('\n', c) 25 | for k in diff: 26 | print(k,'\t\t\t\t\t\t', a.get(k) ,'\t\t\t\t\t\t', b.get(k)) 27 | 28 | print('\nAETE only:') 29 | for a, b, c in zip(tables, tables2, keys): 30 | diff = set(b) - (set(a)) 31 | print('\n', c) 32 | for k in diff: 33 | print(k,'\t\t\t\t\t\t', a.get(k) ,'\t\t\t\t\t\t', b.get(k)) 34 | 35 | 36 | 37 | from appscript import app 38 | 39 | ps = app(id='com.adobe.photoshop', terms='sdef') 40 | print(ps.documents.name()) 41 | 42 | -------------------------------------------------------------------------------- /ASDictionary/src/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Requirements (available from PyPI except where noted): 3 | 4 | - htmlemplate 5 | 6 | - py-appscript 7 | 8 | - py-aemreceive (from appscript repository) 9 | 10 | - py-osaterminology (from appscript repository) 11 | 12 | - py2app 13 | 14 | - pyobjc 15 | 16 | -- 17 | 18 | To build, cd to this directory and run: 19 | 20 | python3 setup.py py2app 21 | 22 | """ 23 | 24 | appname='ASDictionary' 25 | 26 | appversion='0.15.1' # update this and build.sh for new release 27 | 28 | 29 | from setuptools import setup 30 | import py2app 31 | import os 32 | 33 | setup( 34 | app=[appname+".py"], 35 | data_files=["MainMenu.xib"], 36 | options=dict( 37 | py2app=dict( 38 | plist=dict( 39 | CFBundleVersion=appversion, 40 | CFBundleShortVersionString=appversion, 41 | NSHumanReadableCopyright="", 42 | CFBundleIdentifier="net.sourceforge.appscript.asdictionary", 43 | CFBundleDocumentTypes = [ 44 | dict( 45 | CFBundleTypeExtensions=["*"], 46 | CFBundleTypeName="public.item", 47 | CFBundleTypeRole="Viewer", 48 | ), 49 | ], 50 | NSAppleEventsUsageDescription="View application terminologies and object models." 51 | ), 52 | resources=[appname+'.icns'], 53 | iconfile=appname+'.icns' 54 | ) 55 | ) 56 | ) 57 | -------------------------------------------------------------------------------- /py-appscript/lib/appscript/keywordwrapper.py: -------------------------------------------------------------------------------- 1 | """keywordwrapper -- generic wrapper for application-specific type and enum names. """ 2 | 3 | # The Keyword class provides a generic wrapper for class, enum, property and type names. 4 | # Users don't instantiate this class directly; instead, the syntactic sugar layer allows keywords 5 | # to be created by referring to the exported 'k' variable; e.g. k.document, k.ask, k.name, k.String. 6 | 7 | class Keyword: 8 | """A class/property/enumerator/type name.""" 9 | 10 | def __init__(self, name): 11 | self.AS_name = name 12 | 13 | def __repr__(self): 14 | return 'k.{}'.format(self.AS_name) 15 | 16 | def __hash__(self): 17 | return hash(self.AS_name) 18 | 19 | def __eq__(self, val): 20 | return val.__class__ == self.__class__ and val.AS_name == self.AS_name 21 | 22 | def __ne__(self, val): 23 | return not self.__eq__(val) 24 | 25 | def __bool__(self): 26 | return self.AS_name != 'missing_value' 27 | 28 | name = property(lambda self:self.AS_name) 29 | 30 | 31 | class _KeywordShim(object): 32 | """ Infinite namespace 'containing' all possible class/property/enumerator/type names. """ 33 | 34 | def __getattr__(self, name): 35 | if name.startswith('__') and name.endswith('__'): 36 | return object.__getattr__(self, name) 37 | else: 38 | return Keyword(name) 39 | 40 | def __repr__(self): 41 | return 'k' 42 | 43 | 44 | k = _KeywordShim() 45 | 46 | -------------------------------------------------------------------------------- /py-appscript/lib/aem/typewrappers.py: -------------------------------------------------------------------------------- 1 | """typewrappers -- wrapper classes for AE type and enumeration codes """ 2 | 3 | ###################################################################### 4 | # PUBLIC 5 | ###################################################################### 6 | 7 | class AETypeBase: # base class; exposed for typechecking purposes 8 | 9 | def __init__(self, code): 10 | # Check arg is a 4-byte code (while ae.newdesc() verifies descriptor type codes okay, it doesn't verify data size so wouldn't catch bad values at packing time): 11 | if not isinstance(code, bytes): 12 | raise TypeError('invalid code (not a bytes object): {!r}'.format(code)) 13 | elif len(code) != 4: 14 | raise ValueError('invalid code (not four bytes long): {!r}'.format(code)) 15 | self._code = code 16 | 17 | code = property(lambda self:self._code) 18 | 19 | def __hash__(self): 20 | return hash(self._code) 21 | 22 | def __eq__(self, val): 23 | return val.__class__ == self.__class__ and val.code == self._code 24 | 25 | def __ne__(self, val): 26 | return not self == val 27 | 28 | def __repr__(self): 29 | return "aem.{}({})".format(self.__class__.__name__, self._code) 30 | 31 | 32 | class AEType(AETypeBase): 33 | """An AE type.""" 34 | 35 | 36 | class AEEnum(AETypeBase): 37 | """An AE enumeration.""" 38 | 39 | 40 | class AEProp(AETypeBase): 41 | """An AE property code.""" 42 | 43 | 44 | class AEKey(AETypeBase): 45 | """An AE keyword.""" 46 | 47 | -------------------------------------------------------------------------------- /ASTranslate/src/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Requirements (available from PyPI except where noted): 3 | 4 | - py-appscript 5 | 6 | - py-osaterminology (from appscript repository) 7 | 8 | - py2app 9 | 10 | - pyobjc 11 | 12 | -- 13 | 14 | To build, cd to this directory and run: 15 | 16 | python3 setup.py py2app 17 | 18 | """ 19 | 20 | 21 | appversion='0.7.1' # update this and build.sh for new release 22 | 23 | 24 | from setuptools import setup, Extension 25 | import py2app 26 | 27 | setup( 28 | app=["ASTranslate.py"], 29 | data_files=["MainMenu.xib", "ASTranslateDocument.xib"], 30 | ext_modules = [ 31 | Extension('_astranslate', 32 | sources=['_astranslate.c'], 33 | extra_compile_args=['-DMAC_OS_X_VERSION_MIN_REQUIRED=MAC_OS_X_VERSION_10_12'], 34 | extra_link_args=['-framework', 'Carbon'], 35 | ), 36 | ], 37 | options=dict( 38 | py2app=dict( 39 | plist=dict( 40 | NSAppleEventsUsageDescription="Optionally sends Apple events to target applications.", 41 | CFBundleIdentifier="net.sourceforge.appscript.astranslate", 42 | CFBundleVersion=appversion, 43 | CFBundleShortVersionString=appversion, 44 | NSHumanReadableCopyright="", 45 | CFBundleDocumentTypes = [ 46 | dict( 47 | CFBundleTypeExtensions=[], 48 | CFBundleTypeName="Text File", 49 | CFBundleTypeRole="Editor", 50 | NSDocumentClass="ASTranslateDocument" 51 | ) 52 | ] 53 | ), 54 | resources=['ASTranslate.icns'], 55 | iconfile='ASTranslate.icns' 56 | ) 57 | ) 58 | ) 59 | 60 | -------------------------------------------------------------------------------- /py-appscript/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | Index 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 26 | 27 | 28 | 29 |
30 | 31 |

Manuals

32 | 33 | 39 | 40 |
41 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /py-appscript/sample/aem/make_and_set.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # create new TextEdit document containing 'Hello World' and current time 4 | 5 | from time import strftime 6 | 7 | from aem import * 8 | 9 | textedit = Application('/System/Applications/TextEdit.app') # make Application object 10 | 11 | ####### 12 | 13 | # tell app "TextEdit" to activate 14 | textedit.event(b'miscactv').send() 15 | 16 | # tell app "TextEdit" to make new document at end of documents with properties {text:"hello world\n\n\n\n"} 17 | e = textedit.event(b'corecrel', { 18 | b'kocl':AEType(b'docu'), 19 | b'insh':app.elements(b'docu').end, 20 | b'prdt':{AEType(b'ctxt'):'Hello World\n\n\n\n\n\n'} 21 | }) 22 | 23 | print(e.send()) 24 | # Result: TextEdit returns a reference to the object created: 25 | # 26 | # app.elements(b'docu').byindex(1) 27 | 28 | 29 | ####### 30 | 31 | # tell app "TextEdit" to set paragraph 3 of text of first document to strftime("%c") 32 | textedit.event(b'coresetd', { 33 | b'----':app.elements(b'docu').first.property(b'ctxt').elements(b'cpar').byindex(3), 34 | b'data':strftime('%c') 35 | }).send() 36 | 37 | 38 | # tell app "TextEdit" to set every paragraph of text of first document where it = "\n" to "...\n" 39 | textedit.event(b'coresetd', { 40 | b'----':app.elements(b'docu').first.property(b'ctxt').elements(b'cpar').byfilter(its.eq('\n')), 41 | b'data':'...\n' 42 | }).send() 43 | 44 | # Result should be a TextEdit document containing: 45 | # 46 | # Hello World 47 | # ... 48 | # [current date] 49 | # ... 50 | # ... 51 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/Music_missing_track_finder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Cleans up Music' Library playlist by deleting tracks with missing files. 4 | 5 | # Modelled on Bob Ippolito's missingTrackFinder.py script. 6 | # 7 | # Note: Bob says 'Be extra careful about running this if you have MP3s over a 8 | # network, if the drive isn't mounted it may report these tracks as missing!' 9 | 10 | 11 | from appscript import * 12 | 13 | 14 | # Notes: 15 | # 16 | # The following line should delete all Music file tracks whose file is missing: 17 | # 18 | # app('Music').sources['Library'].library_playlists['Library'] \ 19 | # .file_tracks[its.location == k.missing_value].delete() 20 | # 21 | # Alas; Music' scripting support is a bit limited, so we have to do it 22 | # the slow and tedious way instead: 23 | 24 | for track in app('Music').sources['Library'] \ 25 | .library_playlists['Library'].file_tracks.get(): 26 | if track.location.get() == k.missing_value: 27 | track.delete() 28 | 29 | 30 | # Footnote: To get better efficiency, get lists of all file tracks and all file 31 | # track locations up-front and work on these.This'll significantly reduce the 32 | # number of Apple events that the script sends (a common performance 33 | # bottleneck). 34 | # 35 | # tracksref = app('Music').sources['Library'] \ 36 | # .library_playlists['Library'].file_tracks 37 | # tracks = tracksref.get() 38 | # locs = tracksref.location.get() 39 | # for i in range(len(tracks)): 40 | # if locs[i] == k.missing_value: 41 | # tracks[i].delete() -------------------------------------------------------------------------------- /py-appscript/setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, Extension 3 | 4 | import os, sys 5 | import re 6 | 7 | if sys.version_info < (3,0): 8 | raise RuntimeError("Python 3.x required.") 9 | 10 | # Version Number 11 | with open(os.path.join(os.path.dirname(__file__), 'lib', 'appscript', '__init__.py')) as f: 12 | version = re.compile(r".*__version__ = '(.*?)'", re.S).match(f.read()).group(1) 13 | 14 | 15 | setup( 16 | name = "appscript", 17 | version = version, 18 | description = "Control AppleScriptable applications from Python.", 19 | url='http://appscript.sourceforge.net', 20 | license='Public Domain', 21 | platforms=['Mac OS X'], 22 | install_requires=['lxml >= 4.7.1'], 23 | ext_modules = [ 24 | Extension('aem.ae', 25 | sources=['ext/ae.c'], 26 | extra_compile_args=[ 27 | '-DMAC_OS_X_VERSION_MIN_REQUIRED=MAC_OS_X_VERSION_10_12', 28 | '-D__LP64__', # build fails on 10.14 due to Carbon.h issues unless this is explicitly declared 29 | ], 30 | extra_link_args=[ 31 | '-framework', 'CoreFoundation', 32 | '-framework', 'ApplicationServices', 33 | '-framework', 'Carbon'], 34 | ), 35 | ], 36 | packages = [ 37 | 'aem', 38 | 'appscript', 39 | ], 40 | py_modules=[ 41 | 'mactypes', 42 | 'osax', 43 | ], 44 | extra_path = "aeosa", 45 | package_dir = {'': 'lib'}, 46 | classifiers = [ 47 | 'License :: Public Domain', 48 | 'Development Status :: 5 - Production/Stable', 49 | 'Operating System :: MacOS :: MacOS X', 50 | 'Programming Language :: Python :: 3', 51 | ], 52 | 53 | ) 54 | -------------------------------------------------------------------------------- /py-appscript/doc/mactypes-manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-mactypes manual 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

Contents

33 | 34 |
    35 |
  1. Introduction
  2. 36 |
  3. Alias class
  4. 37 |
  5. File class
  6. 38 |
  7. Units class
  8. 39 | 40 |
41 | 42 |
43 | 44 | 45 | 46 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /py-appscript/README.rst: -------------------------------------------------------------------------------- 1 | About appscript 2 | =============== 3 | 4 | Appscript is a high-level, user-friendly Apple event bridge that allows 5 | you to control AppleScriptable Mac OS X applications from Python. 6 | 7 | 8 | Requirements 9 | ------------ 10 | 11 | Appscript supports Python 3.7 and later. 12 | 13 | Appscript requires macOS 10.9 or later. 14 | 15 | 16 | Installation 17 | ------------ 18 | 19 | To download and install appscript from PyPI using pip, run: 20 | 21 | pip3 install appscript 22 | 23 | To install appscript from the downloaded source files, cd to the 24 | appscript-1.1.0 directory and run: 25 | 26 | python3 setup.py install 27 | 28 | Building appscript from source requires Apple's Xcode IDE (available 29 | from the App Store) or Command Line Tools for Xcode (available from 30 | ). 31 | 32 | 33 | Notes 34 | ----- 35 | 36 | - Python 3.x documentation and sample scripts can be found in the 37 | doc and sample directories. 38 | 39 | - Developer tools for exporting application dictionaries (ASDictionary) 40 | and converting application commands from AppleScript to appscript 41 | syntax (ASTranslate) are available separately: 42 | 43 | http://appscript.sourceforge.net/tools.html 44 | 45 | ASDictionary 0.13.2 or later is also required to use appscript's built-in 46 | help() method. If ASDictionary isn't installed, interactive help won't be 47 | available but appscript will continue to operate as normal. 48 | 49 | 50 | Copyright 51 | --------- 52 | 53 | Appscript is released into the public domain, except for portions of ae.c, 54 | which is Copyright (C) the original authors; see code for details. 55 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/dom/appscripttypes.py: -------------------------------------------------------------------------------- 1 | """appscripttypes -- Provides tables for converting AEM-defined AE type codes to appscript Keyword names when parsing terminology.""" 2 | 3 | from osaterminology.defaultterminology import getterms 4 | 5 | __all__ = ['typetables'] 6 | 7 | ###################################################################### 8 | # PRIVATE 9 | ###################################################################### 10 | 11 | _cache = {} 12 | 13 | class TypeTables: 14 | def __init__(self, style): 15 | typedefs = getterms(style) 16 | # build tables used to provide built-in names for [usually] type codes used in dictionaries 17 | self.enumerationbycode = dict(typedefs.enumerations) 18 | self.typebycode = {} 19 | for defs in [typedefs.types, typedefs.pseudotypes]: 20 | for name, code in defs: 21 | self.typebycode[code] = name 22 | # build tables used for collision checking (appscript only) where an application-defined name 23 | # overlaps a built-in name, but has a different code 24 | self.typecodebyname = {} 25 | self.commandcodebyname = {} 26 | for _, enumerators in typedefs.enumerations: 27 | for name, code in enumerators: 28 | self.typecodebyname[name] = code 29 | for defs in [typedefs.types, typedefs.pseudotypes, typedefs.properties]: 30 | for name, code in defs: 31 | self.typecodebyname[name] = code 32 | for name, code, params in typedefs.commands: 33 | self.commandcodebyname[name] = code 34 | 35 | 36 | ###################################################################### 37 | # PUBLIC 38 | ###################################################################### 39 | 40 | 41 | def typetables(style='py-appscript'): 42 | if not style in _cache: 43 | _cache[style] = TypeTables(style) 44 | return _cache[style] 45 | 46 | 47 | -------------------------------------------------------------------------------- /ASDictionary/README: -------------------------------------------------------------------------------- 1 | About ASDictionary 2 | ================== 3 | 4 | ASDictionary exports scriptable Mac applications' dictionaries in HTML format. 5 | Requires Mac OS X 10.14 or later. 6 | 7 | Exporting application dictionaries 8 | ---------------------------------- 9 | 10 | 1. Use the 'Dictionary' menu to select one or more scriptable applications, 11 | or drag-and-drop one or more application files onto ASDictionary's 12 | application icon or Export window. 13 | 14 | 2. Select one or both file formats: single-file HTML and/or frame-based HTML. 15 | Check the 'Compact classes' option to combine duplicate classes into one. 16 | (For example, TextEdit defines a 'document' class in its Standard Suite, 17 | and again in its TextEdit Suite.) Check the 'Show invisibles' option to 18 | include hidden classes and commands in HTML output. 19 | 20 | 3. Select one or more terminology styles. 21 | 22 | 4. Click 'Export', and select a destination folder for the generated files. 23 | 24 | 25 | Notes 26 | ----- 27 | 28 | - Caution: ASDictionary will overwrite any existing files when exporting 29 | dictionaries. 30 | 31 | - Exporting larger dictionaries may take several seconds; GUI responsiveness 32 | may be limited during this time. 33 | 34 | - The appscript bridges for Python and Ruby depend on ASDictionary to support 35 | their built-in help systems. You can specify text wrapping options for help 36 | system output in ASDictionary's Preferences window. 37 | 38 | - Many thanks to the following for comments, suggestions and bug reports: 39 | Philip Aker, Emmanuel Levy, Tim Mansour, Matt Neuburg, Jake Pietrykowski, 40 | Courtney Schwartz, Felix Zumstein 41 | 42 | 43 | Copyright 44 | --------- 45 | 46 | ASDictionary is released into the public domain. 47 | 48 | https://appscript.sourceforge.io/ 49 | -------------------------------------------------------------------------------- /py-appscript/doc/osax-manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-osax manual 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

Contents

33 | 34 |
Scripting additions are severely restricted in macOS 10.14 and later for security. Use of the osax module is strongly discouraged.
35 | 36 |
    37 |
  1. Introduction
  2. 38 |
  3. Interface
  4. 39 |
  5. Examples
  6. 40 |
  7. Notes
  8. 41 | 42 |
43 | 44 |
45 | 46 | 47 | 48 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/dom/applescripttypes.py: -------------------------------------------------------------------------------- 1 | """applescripttypes -- used to get default AppleScript type/class/enum names from AppleScript component; used by aeteparser.AppleScriptParser, sdefparser.AppscriptHandler""" 2 | 3 | from aem.ae import getsysterminology 4 | from osaterminology.sax import aeteparser 5 | 6 | __all__ = ['typebycode', 'enumerationbycode', 'typebyname'] 7 | 8 | ###################################################################### 9 | # PRIVATE 10 | ###################################################################### 11 | 12 | class AeutTypesParser(aeteparser.Receiver): 13 | def __init__(self): 14 | self._typesbycode = {} 15 | self._typesbyname = {} 16 | self._enumerationsbycode = {} 17 | 18 | def start_class(self, code, name, description): 19 | self._name = name 20 | self._code = code 21 | self._isplural = False 22 | 23 | def is_plural(self): 24 | self._isplural = True 25 | 26 | def end_class(self): 27 | if not self._isplural: 28 | self._typesbycode[self._code] = self._name 29 | self._typesbyname[self._name] = self._code 30 | 31 | def start_enumeration(self, code): 32 | self._enumerationsbycode[code] = self._enumeration = [] 33 | 34 | def add_enumerator(self, code, name, description): 35 | self._enumeration.append((name, code)) 36 | 37 | def result(self): 38 | # add some definitions not in AS dictionary 39 | self._typesbycode[b'furl'] = 'file' 40 | self._typesbyname['file'] = b'furl' 41 | return self._typesbycode, self._enumerationsbycode, self._typesbyname 42 | 43 | ## 44 | 45 | p = AeutTypesParser() 46 | aeteparser.parse(getsysterminology(b'ascr'), p) 47 | 48 | ###################################################################### 49 | # PUBLIC 50 | ###################################################################### 51 | 52 | typebycode, enumerationbycode, typebyname = p.result() 53 | 54 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | Contents 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 | 31 |
32 | 33 |

Contents

34 | 35 |
    36 |
  1. Introduction
  2. 37 |
  3. API overview
  4. 38 |
  5. Packing and unpacking data
  6. 39 |
  7. References
  8. 40 |
  9. Targeting applications
  10. 41 |
  11. Building and sending events
  12. 42 |
  13. Locating applications
  14. 43 |
  15. Examples
  16. 44 |
45 | 46 | 47 |
48 | 49 | 50 | 51 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /py-appscript/doc/osax-manual/02_interface.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-osax manual | 2. Interface 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

2. Interface

33 | 34 |

The OSAX class represents the Standard Additions scripting addition and its target application. OSAX is a subclass of appscript's Application class, and its constructor is the same:

35 | 36 |
OSAX(name=None, id=None, url=None, terms=True)
37 |     name : str -- name or path of application, e.g. 'System Events', 
38 |             '/System/Library/CoreServices/System Events.app'
39 |     id : str -- bundle id of application,
40 |             e.g. 'com.apple.systemevents'
41 |     url : str -- eppc URL of remote process,
42 |             e.g. 'eppc://G5.local/System%20Events'
43 | 44 |
45 | 46 | 47 | 48 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /py-appscript/doc/osax-manual/03_examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-osax manual | 3. Examples 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

3. Examples

33 | 34 | 35 |
from osax import *
36 | 
37 | # Create an OSAX instance that contains Standard Additions
38 | # terminology and targets the host process
39 | sa = OSAX() 
40 | 
41 | # Beep
42 | sa.beep()
43 | 
44 | # Speak a phrase
45 | sa.say("Hello World!")
46 | 
47 | # Get path to user's Scripts folder
48 | print(sa.path_to(k.scripts_folder))
49 | # Result: mactypes.Alias("/Users/foo/Library/Scripts/")
50 | 
51 | # Display a dialog
52 | print(sa.display_dialog("Python says hello!",
53 |                         buttons=["Hi!", "Howdy!", "Duuuude!"],
54 |                         default_button=3))
55 | # Result: {k.button_returned: "Howdy!"}
56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/07_findapp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | 7. Locating applications 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

7. Locating applications

33 | 34 |

The findapp module

35 | 36 |

The findapp module is used to obtain the full path to an application given its file name or bundle ID. It exports the following functions:

37 | 38 |
byname(name) -- Find the application with the given name. 
39 |     name : str -- application's name, e.g. 'Finder.app'. The '.app' suffix
40 |             is optional. Absolute paths are also accepted.
41 |     Result : str -- full path to application
42 | 
43 | byid(id) -- Find the application with the given bundle id.
44 |     id : str -- bundle id, e.g. 'com.apple.textedit'
45 |     Result : str -- full path to application
46 | 47 | 48 |
49 | 50 | 51 | 52 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /py-appscript/lib/aem/findapp.py: -------------------------------------------------------------------------------- 1 | """findapp -- Support module for obtaining the full path to a local application given its file name or bundle id. If application isn't found, an ApplicationNotFoundError exception is raised. """ 2 | 3 | from os.path import exists 4 | 5 | from .ae import findapplicationforinfo, MacOSError 6 | 7 | __all__ = ['byname', 'byid'] 8 | 9 | ###################################################################### 10 | # PRIVATE 11 | ###################################################################### 12 | 13 | def _findapp(name=None, id=None): 14 | try: 15 | return findapplicationforinfo(b'????', id, name) 16 | except MacOSError as err: 17 | if err.args[0] == -10814: 18 | raise ApplicationNotFoundError(name or id) from err 19 | else: 20 | raise 21 | 22 | 23 | ###################################################################### 24 | # PUBLIC 25 | ###################################################################### 26 | 27 | class ApplicationNotFoundError(Exception): 28 | def __init__(self, name): 29 | self.name = name 30 | Exception.__init__(self, name) 31 | 32 | def __str__(self): 33 | return 'Local application {!r} not found.'.format(self.name) 34 | 35 | 36 | def byname(name): 37 | """Find the application with the given name and return its full path. 38 | 39 | Absolute paths are also accepted. An '.app' suffix is optional. 40 | 41 | Examples: 42 | byname('TextEdit') 43 | byname('Finder.app') 44 | """ 45 | if not name.startswith('/'): # application name only, not its full path 46 | try: 47 | name = _findapp(name) 48 | except ApplicationNotFoundError: 49 | if name.lower().endswith('.app'): 50 | raise 51 | name = _findapp(name + '.app') 52 | if not exists(name) and not name.lower().endswith('.app') and exists(name + '.app'): 53 | name += '.app' 54 | if not exists(name): 55 | raise ApplicationNotFoundError(name) 56 | return name 57 | 58 | 59 | def byid(id): 60 | """Find the application with the given bundle id and return its full path. 61 | 62 | Examples: 63 | byid('com.apple.textedit') 64 | """ 65 | return _findapp(id=id) 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /py-appscript/test/test_osax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest, subprocess 4 | import osax, mactypes, aem 5 | 6 | class TC_OSAX(unittest.TestCase): 7 | 8 | def test_1(self): 9 | sa = osax.OSAX() 10 | 11 | self.assertEqual(65, sa.ASCII_number('A')) 12 | 13 | self.assertEqual(mactypes.Alias("/Applications/"), sa.path_to(osax.k.applications_folder)) 14 | 15 | self.assertEqual(mactypes.Alias("/Library/Scripts/"), 16 | sa.path_to(osax.k.scripts_folder, from_=osax.k.local_domain)) 17 | 18 | self.assertRaises(AttributeError, getattr, sa, 'non_existent_command') 19 | 20 | 21 | def test_2(self): 22 | sa = osax.OSAX(name='Finder') 23 | sa.activate() 24 | self.assertEqual({osax.k.button_returned: '', osax.k.gave_up: True}, sa.display_dialog('test', giving_up_after=1)) 25 | self.assertEqual(mactypes.Alias("/System/Library/CoreServices/Finder.app/"), sa.path_to(None)) 26 | 27 | def test_4(self): 28 | sa = osax.OSAX(id='com.apple.finder') 29 | sa.activate() 30 | self.assertEqual({osax.k.button_returned: '', osax.k.gave_up: True}, sa.display_dialog('test', giving_up_after=1, with_icon=osax.k.stop)) 31 | self.assertEqual(mactypes.Alias("/System/Library/CoreServices/Finder.app/"), sa.path_to(None)) 32 | 33 | 34 | def test_5(self): 35 | p = subprocess.Popen("top -l1 | grep ' Finder ' | awk '{ print $1 }'", 36 | shell=True, stdout=subprocess.PIPE, close_fds=True) 37 | out, err = p.communicate() 38 | pid = int(out) 39 | sa = osax.OSAX(pid=pid) 40 | self.assertEqual(mactypes.Alias("/System/Library/CoreServices/Finder.app/"), sa.path_to(None)) 41 | sa.activate() 42 | self.assertEqual({osax.k.button_returned: '', osax.k.gave_up: True}, sa.display_dialog('test', giving_up_after=1)) 43 | 44 | 45 | def test_6(self): 46 | sa = osax.OSAX(aemapp=aem.Application("/System/Library/CoreServices/Finder.app/")) 47 | sa.activate() 48 | self.assertEqual({osax.k.button_returned: '', osax.k.gave_up: True}, sa.display_dialog('test', giving_up_after=1, with_icon=osax.k.note)) 49 | self.assertEqual(mactypes.Alias("/System/Library/CoreServices/Finder.app/"), sa.path_to(None)) 50 | 51 | 52 | 53 | if __name__ == '__main__': 54 | unittest.main() 55 | 56 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/makeidentifier/rbappscript.py: -------------------------------------------------------------------------------- 1 | """makeidentifier.rbappscript -- Reserved keywords for rb-scpt""" 2 | 3 | # Important: the following must be reserved: 4 | # 5 | # - names of properties and methods used in AS::Application and AS::Reference classes 6 | # - names of built-in keyword arguments in AS::Reference._send 7 | 8 | kReservedWords = [ 9 | "==", 10 | "===", 11 | "=~", 12 | "AS_aem_reference", 13 | "AS_aem_reference=", 14 | "AS_app_data", 15 | "AS_app_data=", 16 | "AS_resolve", 17 | "ID", 18 | "[]", 19 | "__id__", 20 | "__send__", 21 | "_aem_application_class", 22 | "_call", 23 | "_resolve_range_boundary", 24 | "_send_command", 25 | "abort_transaction", 26 | "after", 27 | "and", 28 | "any", 29 | "before", 30 | "by_creator", 31 | "by_id", 32 | "by_name", 33 | "by_pid", 34 | "by_url", 35 | "class", 36 | "clone", 37 | "commands", 38 | "contains", 39 | "current", 40 | "display", 41 | "does_not_contain", 42 | "does_not_end_with", 43 | "does_not_begin_with", 44 | "dup", 45 | "elements", 46 | "end", 47 | "end_transaction", 48 | "ends_with", 49 | "eq", 50 | "eql?", 51 | "equal?", 52 | "extend", 53 | "first", 54 | "freeze", 55 | "frozen?", 56 | "ge", 57 | "gt", 58 | "hash", 59 | "help", 60 | "id", 61 | "ignore", 62 | "inspect", 63 | "instance_eval", 64 | "instance_of?", 65 | "instance_variable_get", 66 | "instance_variable_set", 67 | "instance_variables", 68 | "is_a?", 69 | "is_in", 70 | "is_not_in", 71 | "is_running?", 72 | "keywords", 73 | "kind_of?", 74 | "last", 75 | "launch", 76 | "le", 77 | "lt", 78 | "method", 79 | "method_missing", 80 | "methods", 81 | "middle", 82 | "ne", 83 | "next", 84 | "nil?", 85 | "not", 86 | "object_id", 87 | "or", 88 | "parameters", 89 | "previous", 90 | "private_methods", 91 | "properties", 92 | "protected_methods", 93 | "public_methods", 94 | "respond_to?", 95 | "result_type", 96 | "send", 97 | "singleton_methods", 98 | "beginning", 99 | "begin_transaction", 100 | "begins_with", 101 | "taint", 102 | "tainted?", 103 | "timeout", 104 | "to_a", 105 | "to_s", 106 | "type", 107 | "untaint", 108 | "wait_reply", 109 | ] 110 | 111 | -------------------------------------------------------------------------------- /py-appscript/doc/mactypes-manual/01_introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-mactypes manual | 1. Introduction 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 |

1. Introduction

32 | 33 |

About mactypes

34 | 35 |

The mactypes module provides user-friendly wrappers for Apple event descriptors that do not have direct equivalents in the Python language. It contains Python classes for specifying filesystem objects and locations, and unit type values (lengths, weights, etc.), and is used in conjunction with the appscript, osax and/or aem modules.

36 | 37 |

Notes

38 | 39 |

Be aware that most scriptable applications do not use or understand POSIX paths, and while the Apple Event Manager does provide some built-in coercions for converting between path strings and alias/file objects, these work with HFS paths only. Therefore, when specifying files and folders to scriptable applications, use mactypes.Alias and mactypes.File objects - not path strings - unless otherwise indicated.

40 | 41 | 42 |
43 | 44 | 45 | 46 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/01_introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | 1. Introduction 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

1. Introduction

33 | 34 |

About AEM

35 | 36 |

The aem package implements a mid-level object-oriented wrapper around the low-level Carbon Apple Event Manager APIs. It provides the following services:

37 | 38 | 44 | 45 |

AEM provides a direct foundation for the high-level appscript package. It can also be used directly by developers and end-users for controlling scriptable applications in situations where appscript is unavailable or unsuitable.

46 | 47 |

Note that this documentation is an API reference, not a full user guide. Some familiarity with Apple events and the Apple Event Manager is required in order to understand and use AEM.

48 | 49 |
50 | 51 | 52 | 53 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /py-appscript/doc/osax-manual/04_notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-osax manual | 4. Notes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 27 | 28 | 29 |
30 | 31 |

4. Notes

32 | 33 | 34 |

Event loops

35 | 36 |

Python-based applications that use a Carbon/Cocoa event loop can import the osax module as normal, but should not use it before the event loop has started as sending Apple events outwith the main event loop can disrupt the process's normal event handling.

37 | 38 | 39 |

GUI interaction

40 | 41 |

When using scripting addition commands that require GUI access (e.g. display_dialog) targeted at the command-line Python interpreter, the osax module will automatically convert the non-GUI interpreter process into a full GUI process to allow these commands to operate correctly. If you want to avoid this, target these commands at a faceless GUI application such as System Events instead:

42 | 43 |
sa = OSAX(name="System Events")
44 | sa.activate() # give focus to System Events
45 | print(sa.display_dialog("Python says hello!",
46 |                         buttons=["Hi!", "Howdy!", "Duuuude!"],
47 |                         default_button=3))
48 | # Result: {k.button_returned: "Duuuude!"}
49 | 50 | 51 |
52 | 53 | 54 | 55 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | Contents 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

Contents

33 | 34 |
    35 |
  1. Introduction
  2. 36 |
  3. About application scripting
  4. 37 |
  5. Quick tutorial
  6. 38 |
  7. Getting help
  8. 39 |
  9. Keyword conversion
  10. 40 |
  11. Classes and enumerated types
  12. 41 |
  13. Application objects
  14. 42 |
  15. Real vs generic references
  16. 43 |
  17. Reference forms
  18. 44 |
  19. Reference examples
  20. 45 |
  21. Application commands
  22. 46 |
  23. Command examples
  24. 47 |
  25. Performance issues
  26. 48 |
  27. Notes
  28. 49 |
50 | 51 |
52 | 53 | 54 | 55 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /ASTranslate/README: -------------------------------------------------------------------------------- 1 | About ASTranslate 2 | ================= 3 | 4 | A simple tool for converting application commands written in AppleScript into 5 | their py-appscript/rb-scpt/nodeautomation equivalent. 6 | 7 | 8 | Usage 9 | ----- 10 | 11 | 1. Launch ASTranslate and type or paste one or more AppleScript commands into 12 | the top half of the window, e.g.: 13 | 14 | tell application "TextEdit" to get text of every document 15 | 16 | with timeout of 10 seconds 17 | tell application "Finder" to folder 1 of home as alias 18 | end timeout 19 | 20 | 2. Select Document > Translate. The AppleScript code is compiled and executed, 21 | and the bottom pane displays each Apple event sent by AppleScript as 22 | appscript code, e.g.: 23 | 24 | app('TextEdit').documents.text.get() 25 | 26 | app('Finder').home.folders[1].get(resulttype=k.alias, timeout=10) 27 | 28 | Click on the Python, Ruby, and Node.js tabs below the bottom pane to switch 29 | translations. 30 | 31 | The 'Send events to app' checkbox can be unchecked to prevent Apple events 32 | being sent to the target application. This is particularly useful when 33 | obtaining translations of potentially destructive commands such as 'delete'. 34 | 35 | 36 | Notes 37 | ----- 38 | 39 | - ASTranslate only sniffs outgoing Apple events sent by AppleScript; it is 40 | not a full transpiler. The output is not intended to be production-ready 41 | code, but should be helpful when figuring out how to translate a particular 42 | reference or command from AppleScript to Python/Ruby/Node.js. 43 | 44 | - ASTranslate translates application commands only. Standard Additions 45 | commands are not translated. 46 | 47 | - If the 'Send Apple Events' option is checked, remember that all Apple events 48 | sent by AppleScript will be passed to applications to be handled as normal. 49 | i.e. Destructive commands (e.g. 'tell "Finder" to delete some file') will 50 | still do their destructive thing; unsuccessful commands will cause 51 | AppleScript to raise an error, etc. 52 | 53 | - Unchecking the 'Send Apple Events' option also prevents application commands 54 | returning a result. If the AppleScript code expects a command to return a 55 | value (e.g. for use in another command), this will likely result in an 56 | AppleScript error. 57 | 58 | 59 | Copyright 60 | --------- 61 | 62 | ASTranslate is released into the public domain. 63 | 64 | https://appscript.sourceforge.io/ 65 | -------------------------------------------------------------------------------- /py-appscript/test/test_mactypes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest, os, os.path, tempfile 4 | from aem.ae import MacOSError 5 | from aem import mactypes 6 | 7 | class TC_MacTypes(unittest.TestCase): 8 | 9 | dir = '/private/tmp' 10 | 11 | def setUp(self): 12 | self.path1 = tempfile.mkstemp('', 'py-mactypes-test.', self.dir)[1] # tempnam raises a security warning re. security; it's part of the test code, not mactypes, so ignore it 13 | open(self.path1, 'w').close() 14 | fname = os.path.split(self.path1)[1] 15 | self.path2 = os.path.join(self.dir, 'moved-' + fname) 16 | # print "path: %r" % self.path1 # e.g. /private/tmp/py-mactypes-test.VLrUW7 17 | 18 | def test_alias(self): 19 | # make alias 20 | self.f = mactypes.Alias(self.path1) 21 | 22 | path1 = self.path1 23 | if not path1.startswith('/private/'): 24 | path1 = '/private' + path1 # KLUDGE: allow for altered temp path 25 | 26 | self.assertEqual("mactypes.Alias(%r)" % path1, repr(self.f)) 27 | 28 | #print "alias path 1: %s" % f.path # e.g. /private/tmp/py-mactypes-test.VLrUW7 29 | self.assertEqual(path1, self.f.path) 30 | 31 | # get desc 32 | #print `f.desc.type, f.desc.data` # alis, [binary data] 33 | self.assertEqual(b'alis', self.f.desc.type) 34 | 35 | 36 | # check alias keeps track of moved file 37 | os.rename(path1, self.path2) 38 | # print "alias path 2: %r" % f.path # /private/tmp/moved-py-mactypes-test.VLrUW7 39 | self.assertEqual(self.path2, self.f.path) 40 | 41 | self.assertEqual("mactypes.Alias(%r)" % self.path2, repr(self.f)) 42 | 43 | # check a FileNotFoundError is raised if getting path/FileURL for a filesystem object that no longer exists 44 | os.remove(self.path2) 45 | self.assertRaises(MacOSError, lambda:self.f.path) # File not found. 46 | self.assertRaises(MacOSError, lambda:self.f.file) # File not found. 47 | 48 | 49 | def test_fileURL(self): 50 | 51 | g = mactypes.File('/non/existent path') 52 | 53 | self.assertEqual('/non/existent path', g.path) 54 | 55 | self.assertEqual(b'furl', g.desc.type) 56 | self.assertEqual('file://localhost/non/existent%20path', g.desc.data.decode('utf8')) 57 | 58 | self.assertEqual("mactypes.File('/non/existent path')", repr(g.file)) 59 | 60 | # check a not-found error is raised if getting Alias for a filesystem object that doesn't exist 61 | self.assertRaises(MacOSError, lambda:g.alias) # File "/non/existent path" not found. 62 | 63 | 64 | if __name__ == '__main__': 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /py-appscript/lib/osax.py: -------------------------------------------------------------------------------- 1 | """osax.py -- Allows scripting additions (a.k.a. OSAXen) to be called from Python. """ 2 | 3 | 4 | from appscript import * 5 | from appscript import reference, terminology 6 | import aem 7 | 8 | 9 | __all__ = ['OSAX', 'ApplicationNotFoundError', 'CommandError', 'k', 'mactypes'] 10 | 11 | 12 | ###################################################################### 13 | # PRIVATE 14 | ###################################################################### 15 | 16 | 17 | _osaxpath = '/System/Library/ScriptingAdditions/StandardAdditions.osax' 18 | 19 | _terms = None 20 | 21 | 22 | ###################################################################### 23 | # PUBLIC 24 | ###################################################################### 25 | 26 | def scriptingadditions(): 27 | return ['StandardAdditions'] 28 | 29 | 30 | class OSAX(reference.Application): 31 | 32 | def __init__(self, *, name=None, id=None, pid=None, url=None, aemapp=None): 33 | global _terms 34 | if not _terms: 35 | _terms = terminology.tablesforsdef(terminology.sdefforurl(aem.ae.convertpathtourl(_osaxpath, 0))) 36 | reference.Application.__init__(self, name, id, pid, url, aemapp, _terms) 37 | try: 38 | self.AS_appdata.target().event(b'ascrgdut').send(300) # make sure target application has loaded event handlers for all installed OSAXen 39 | except aem.EventError as e: 40 | if e.errornumber != -1708: # ignore 'event not handled' error 41 | raise 42 | def _help(*args): 43 | raise NotImplementedError("Built-in help isn't available for scripting additions.") 44 | self.AS_appdata.help = _help 45 | 46 | def __str__(self): 47 | if self.AS_appdata.constructor == 'current': 48 | return 'OSAX()'.format() 49 | else: 50 | return 'OSAX({}={!r})'.format(self.AS_appdata.constructor, self.AS_appdata.identifier) 51 | 52 | def __getattr__(self, name): 53 | command = reference.Application.__getattr__(self, name) 54 | if isinstance(command, reference.Command): 55 | def osaxcommand(*args, **kargs): 56 | try: 57 | return command(*args, **kargs) 58 | except CommandError as e: 59 | if int(e) == -1713: # 'No user interaction allowed' error (e.g. user tried to send a 'display dialog' command to a non-GUI python process), so convert the target process to a full GUI process and try again 60 | aem.ae.transformprocesstoforegroundapplication() 61 | self.activate() 62 | return command(*args, **kargs) 63 | raise 64 | return osaxcommand 65 | else: 66 | return command 67 | 68 | __repr__ = __str__ 69 | 70 | -------------------------------------------------------------------------------- /ASTranslate/src/ae.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ae.h 3 | * 4 | * Provides access to the following aem.ae C functions: 5 | * 6 | * AE_AEDesc_New // wrap AEDesc; Python takes ownership 7 | * AE_AEDesc_NewBorrowed // wrap AEDesc; caller retains ownership 8 | * AE_AEDesc_Convert // unwrap AEDesc; Python retains ownership 9 | * AE_AEDesc_ConvertDisown // unwrap AEDesc; caller takes ownership 10 | * AE_GetMacOSErrorException // returns aem.ae.MacOSError type 11 | * AE_MacOSError // raise aem.ae.MacOSError exception 12 | * AE_GetOSType // convert 4-byte bytes object to OSType 13 | * AE_BuildOSType // convert OSType to 4-byte bytes object 14 | * 15 | * Extensions that need to use these functions should include ae.h 16 | * 17 | */ 18 | 19 | #ifndef AE_MODULE_H 20 | #define AE_MODULE_H 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | /* Header file for ae module */ 31 | 32 | #ifdef AE_MODULE_C 33 | /* This section is used when compiling ae.c */ 34 | 35 | static PyObject * AE_AEDesc_New (AEDesc *); 36 | static PyObject * AE_AEDesc_NewBorrowed (AEDesc *); 37 | static int AE_AEDesc_Convert (PyObject *, AEDesc *); 38 | static int AE_AEDesc_ConvertDisown (PyObject *, AEDesc *); 39 | static PyObject * AE_GetMacOSErrorException (void); 40 | static PyObject * AE_MacOSError (int); 41 | static int AE_GetOSType (PyObject *, OSType *); 42 | static PyObject * AE_BuildOSType (OSType); 43 | 44 | #else /* AE_MODULE_C */ 45 | /* This section is used in modules that use the ae module's API */ 46 | 47 | static void **AE_API; 48 | 49 | #define AE_AEDesc_New (*(PyObject * (*) (AEDesc *) )AE_API[0]) 50 | #define AE_AEDesc_NewBorrowed (*(PyObject * (*) (AEDesc *) )AE_API[1]) 51 | #define AE_AEDesc_Convert (*(int (*) (PyObject *, AEDesc *))AE_API[2]) 52 | #define AE_AEDesc_ConvertDisown (*(int (*) (PyObject *, AEDesc *))AE_API[3]) 53 | #define AE_GetMacOSErrorException (*(PyObject * (*) (void) )AE_API[4]) 54 | #define AE_MacOSError (*(PyObject * (*) (int) )AE_API[5]) 55 | #define AE_GetOSType (*(int (*) (PyObject *, OSType *))AE_API[6]) 56 | #define AE_BuildOSType (*(PyObject * (*) (OSType) )AE_API[7]) 57 | 58 | /* Return -1 on error, 0 on success. 59 | * PyCapsule_Import will set an exception if there's an error. 60 | */ 61 | static int 62 | import_ae(void) 63 | { 64 | AE_API = (void **)PyCapsule_Import("aem.ae._C_API", 0); 65 | return (AE_API != NULL) ? 0 : -1; 66 | } 67 | 68 | #endif /* AE_MODULE_C */ 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | 74 | #endif /* AE_MODULE_H */ 75 | -------------------------------------------------------------------------------- /py-appscript/ext/ae.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ae.h 3 | * 4 | * Provides access to the following aem.ae C functions: 5 | * 6 | * AE_AEDesc_New // wrap AEDesc; Python takes ownership 7 | * AE_AEDesc_NewBorrowed // wrap AEDesc; caller retains ownership 8 | * AE_AEDesc_Convert // unwrap AEDesc; Python retains ownership 9 | * AE_AEDesc_ConvertDisown // unwrap AEDesc; caller takes ownership 10 | * AE_GetMacOSErrorException // returns aem.ae.MacOSError type 11 | * AE_MacOSError // raise aem.ae.MacOSError exception 12 | * AE_GetOSType // convert 4-byte bytes object to OSType 13 | * AE_BuildOSType // convert OSType to 4-byte bytes object 14 | * 15 | * Extensions that need to use these functions should include ae.h 16 | * 17 | */ 18 | 19 | #ifndef AE_MODULE_H 20 | #define AE_MODULE_H 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | /* Header file for ae module */ 31 | 32 | #ifdef AE_MODULE_C 33 | /* This section is used when compiling ae.c */ 34 | 35 | static PyObject * AE_AEDesc_New (AEDesc *); 36 | static PyObject * AE_AEDesc_NewBorrowed (AEDesc *); 37 | static int AE_AEDesc_Convert (PyObject *, AEDesc *); 38 | static int AE_AEDesc_ConvertDisown (PyObject *, AEDesc *); 39 | static PyObject * AE_GetMacOSErrorException (void); 40 | static PyObject * AE_MacOSError (int); 41 | static int AE_GetOSType (PyObject *, OSType *); 42 | static PyObject * AE_BuildOSType (OSType); 43 | 44 | #else /* AE_MODULE_C */ 45 | /* This section is used in modules that use the ae module's API */ 46 | 47 | static void **AE_API; 48 | 49 | #define AE_AEDesc_New (*(PyObject * (*) (AEDesc *) )AE_API[0]) 50 | #define AE_AEDesc_NewBorrowed (*(PyObject * (*) (AEDesc *) )AE_API[1]) 51 | #define AE_AEDesc_Convert (*(int (*) (PyObject *, AEDesc *))AE_API[2]) 52 | #define AE_AEDesc_ConvertDisown (*(int (*) (PyObject *, AEDesc *))AE_API[3]) 53 | #define AE_GetMacOSErrorException (*(PyObject * (*) (void) )AE_API[4]) 54 | #define AE_MacOSError (*(PyObject * (*) (int) )AE_API[5]) 55 | #define AE_GetOSType (*(int (*) (PyObject *, OSType *))AE_API[6]) 56 | #define AE_BuildOSType (*(PyObject * (*) (OSType) )AE_API[7]) 57 | 58 | /* Return -1 on error, 0 on success. 59 | * PyCapsule_Import will set an exception if there's an error. 60 | */ 61 | static int 62 | import_ae(void) 63 | { 64 | AE_API = (void **)PyCapsule_Import("aem.ae._C_API", 0); 65 | return (AE_API != NULL) ? 0 : -1; 66 | } 67 | 68 | #endif /* AE_MODULE_C */ 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | 74 | #endif /* AE_MODULE_H */ 75 | -------------------------------------------------------------------------------- /py-appscript/lib/appscript/genericreference.py: -------------------------------------------------------------------------------- 1 | """genericreference -- allows user to construct relative (con- and its- based) references without immediate access to application terminology. """ 2 | 3 | import aem 4 | 5 | ###################################################################### 6 | # PUBLIC 7 | ###################################################################### 8 | 9 | class GenericReference(object): 10 | def __init__(self, call): 11 | self._call = call # list of recorded call information (inital value is ['app'], ['con'] or ['its']) 12 | 13 | def __getattr__(self, i): 14 | return GenericReference(self._call + [('__getattr__', i, '.{}')]) 15 | 16 | def __getitem__(self, i): 17 | return GenericReference(self._call + [('__getitem__', i, '[{!r}]')]) 18 | 19 | def __call__(self, *args, **kargs): 20 | return GenericReference(self._call + [('__call__', (args, kargs), None)]) 21 | 22 | def __gt__(self, i): 23 | return GenericReference(self._call + [('AS__gt__', i, ' > {!r}')]) 24 | 25 | def __ge__(self, i): 26 | return GenericReference(self._call + [('AS__ge__', i, ' >= {!r}')]) 27 | 28 | def __eq__(self, i): 29 | return GenericReference(self._call + [('AS__eq__', i, ' == {!r}')]) 30 | 31 | def __ne__(self, i): 32 | return GenericReference(self._call + [('AS__ne__', i, ' != {!r}')]) 33 | 34 | def __lt__(self, i): 35 | return GenericReference(self._call + [('AS__lt__', i, ' < {!r}')]) 36 | 37 | def __le__(self, i): 38 | return GenericReference(self._call + [('AS__le__', i, ' <= {!r}')]) 39 | 40 | def __hash__(self): 41 | return hash(self._call) 42 | 43 | def __repr__(self): 44 | s = self._call[0] 45 | for method, args, repstr in self._call[1:]: 46 | if method == '__call__': 47 | s += '({})'.format(', '.join(['{!r}'.format(i) for i in args[0]] + ['{}={!r}'.format(i) for i in args[1].items()])) 48 | elif method == '__getitem__' and isinstance(args, slice): 49 | s+= '[{!r}:{!r}]'.format(args.start, args.stop) 50 | elif method == '__getattr__' and args in ['AND', 'OR', 'NOT']: 51 | s = '({}).{}'.format(s, args) 52 | else: 53 | s += repstr.format(args) 54 | return s 55 | 56 | def AS_resolve(self, Reference, appdata): 57 | # (Note: reference.Reference class is passed as argument simply to avoid circular import between that module and this) 58 | ref = Reference(appdata, {'app':aem.app, 'con':aem.con, 'its':aem.its}[self._call[0]]) 59 | for method, args, repstr in self._call[1:]: 60 | if method == '__getattr__': 61 | ref = getattr(ref, args) 62 | elif method == '__call__': 63 | ref = ref(*args[0], **args[1]) 64 | else: 65 | ref = getattr(ref, method)(args) 66 | return ref 67 | 68 | ## 69 | 70 | con = GenericReference(['con']) 71 | its = GenericReference(['its']) 72 | # 'app' is defined in reference module 73 | 74 | -------------------------------------------------------------------------------- /py-appscript/doc/mactypes-manual/04_unitsclass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-mactypes manual | 4. Units class 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 27 | 28 | 29 |
30 | 31 |

4. The Units class

32 | 33 |

The Units class represents a measurement of some sort, e.g. 3 inches, 98.5 degrees Fahrenheit.

34 | 35 |
class Units
36 | 
37 |     Constructor:
38 | 
39 |         Units(value, type)
40 |             value : int | float -- the amount, e.g. 3.5
41 |             type : str -- the unit of measurement, e.g. 'centimeters'
42 | 
43 |     Properties:
44 | 
45 |         value : int | float -- returns the amount
46 | 
47 |         type : str -- returns the unit of measurement
48 | 49 |

The following unit types are defined as standard:

50 | 51 |
'centimeters'                  'cubic_inches'
52 | 'meters'                       'cubic_feet'
53 | 'kilometers'                   'cubic_yards'
54 | 'inches'                       
55 | 'feet'                         'liters'
56 | 'yards'                        'quarts'
57 | 'miles'                        'gallons'
58 |                               
59 | 'square_meters'                'grams'
60 | 'square_kilometers'            'kilograms'
61 | 'square_feet'                  'ounces'
62 | 'square_yards'                 'pounds'
63 | 'square_miles'                 
64 |                                'degrees_Celsius'
65 | 'cubic_centimeters'            'degrees_Fahrenheit'
66 | 'cubic_meters'                 'degrees_Kelvin'
67 | 68 |

Additional application-specific unit types can be added if needed.

69 | 70 | 71 |

Examples

72 | 73 |
mactypes.Units(14, 'inches')
74 | 
75 | mactypes.Units(3.5, 'square_meters')
76 | 77 | 78 |
79 | 80 | 81 | 82 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build Pipeline 2 | 3 | on: 4 | # Run on all pushed commits and when a new release is created 5 | # Prevents duplicated pipeline runs as a release also pushes a tag 6 | push: 7 | branches: 8 | - '**' 9 | tags-ignore: 10 | - '**' 11 | release: 12 | types: 13 | - created 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [macos-13] 21 | python-version: ['3.14'] 22 | architecture: ['x64'] 23 | steps: 24 | - uses: actions/checkout@v5 25 | - name: Set up Python 26 | uses: actions/setup-python@v6 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | architecture: ${{ matrix.architecture }} 30 | - name: Set version string 31 | shell: bash 32 | run: | 33 | if [ ${GITHUB_REF::9} = "refs/tags" ]; then 34 | version_string=${GITHUB_REF:10} 35 | else 36 | version_string=0.0.0+${GITHUB_SHA::8} 37 | fi; 38 | sed -i '' -e "s/__version__ = 'dev'/__version__ = '$version_string'/" ./py-appscript/lib/appscript/__init__.py 39 | - name: Install dependencies 40 | run: | 41 | python -m pip install wheel twine build cibuildwheel setuptools 42 | - name: Build Wheels 43 | run: | 44 | cd ./py-appscript 45 | python setup.py sdist 46 | python -m cibuildwheel --output-dir dist 47 | env: 48 | CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-* cp314-*" 49 | CIBW_TEST_SKIP: "*_arm64" 50 | CIBW_ARCHS_MACOS: "x86_64 universal2" 51 | - name: Upload artifacts 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: dist 55 | path: ./py-appscript/dist/ 56 | 57 | # - uses: actions/download-artifact@v5 58 | # with: 59 | # name: dist 60 | # path: dist 61 | # - name: Display structure of downloaded files 62 | # run: ls -R dist 63 | 64 | publish: 65 | name: Publish 66 | needs: build 67 | if: github.event_name == 'release' 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v5 71 | - name: Set up Python 72 | uses: actions/setup-python@v6 73 | with: 74 | python-version: '3.14' 75 | architecture: 'x64' 76 | - name: Install Python dependencies 77 | run: python -m pip install twine 78 | - name: Download source package and wheels from previous job 79 | uses: actions/download-artifact@v5 80 | with: 81 | name: dist 82 | path: dist 83 | - name: Publish source pacakge and wheels to PyPI 84 | run: twine upload --skip-existing ./dist/* 85 | env: 86 | TWINE_USERNAME: __token__ 87 | TWINE_PASSWORD: ${{ secrets.pypi_password }} 88 | -------------------------------------------------------------------------------- /py-appscript/doc/osax-manual/01_introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-osax manual | 1. Introduction 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

1. Introduction

33 | 34 |

The osax module is deprecated and its use is not recommended.

35 | 36 |

The osax module provides an easy way to call macOS's Standard Additions scripting addition from Python. It exports a single public class, OSAX. For convenience, it also re-exports appscript's ApplicationNotFoundError and CommandError classes and k variable.

37 | 38 |

An OSAX instance represents a running process into which the Standard Additions scripting addition has been automatically loaded. It is similar to an appscript application object, except that it defines commands for Standard Additions instead of the application's normal commands.

39 | 40 |

Once you've created a OSAX instance, you can invoke its commands in exactly the same way as you would call a scriptable application's commands in appscript.

41 | 42 |

For example:

43 | 44 |
import osax
45 | 
46 | sa = osax.OSAX()
47 | 
48 | sa.say("Hello world", using="Victoria")
49 | 50 |

The default application commands (run, activate, quit, etc.) are also available; see the appscript manual for details on those.

51 | 52 |

By default, OSAX objects are targeted at the current application. You can specify another application by supplying one of the following optional keyword arguments: name, id, or url. These arguments are the same as for appscript.

53 | 54 | 55 |
56 | 57 | 58 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/08_examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | 8. Examples 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 27 | 28 | 29 |
30 | 31 |

8. Examples

32 | 33 |

Identifying Applications

34 | 35 |
# application "Macintosh HD:System:Applications:TextEdit.app"
36 | textedit = aem.Application('/System/Applications/TextEdit.app')
37 | 
38 | # application "TextEdit"
39 | textedit = aem.Application(aem.findapp.byname('TextEdit'))
40 | 
41 | # application "TextEdit" of machine "eppc://my-mac.local"
42 | textedit = aem.Application('eppc://my-mac.local/TextEdit')
43 | 44 | 45 |

Building References

46 | 47 |
# name (of application)
48 | aem.app.property(b'pnam')
49 | 
50 | # text of every document
51 | aem.app.elements(b'docu').property(b'ctxt')
52 | 
53 | # end of every paragraph of text of document 1
54 | aem.app.elements(b'docu').byindex(1).property(b'ctxt').elements(b'cpar').end
55 | 
56 | # paragraphs 2 thru last of first document
57 | aem.app.elements(b'docu').first.elements(b'cpar').byrange(
58 |         aem.con.elements(b'cpar').byindex(2), 
59 |         aem.con.elements(b'cpar').last)
60 | 
61 | # paragraphs of document 1 where it != "\n"
62 | aem.app.elements(b'docu').byindex(1).elements(b'cpar').byfilter(aem.its.ne('\n'))
63 | 64 | 65 |

Sending Events

66 | 67 |
# quit TextEdit
68 | textedit.event(b'corequit').send()
69 | 
70 | # name of TextEdit
71 | print textedit.event(b'coregetd', {b'----': aem.app.property(b'pnam')}).send()
72 | 
73 | # count documents of TextEdit
74 | print textedit.event(b'corecnte', {b'----': aem.app.elements(b'docu')}).send()
75 | 
76 | # make new document at end of documents of TextEdit
77 | textedit.event(b'corecrel', {
78 |         b'kocl': aem.AEType(b'docu'), 
79 |         b'insh': aem.app.elements(b'docu').end
80 |         }).send()
81 | 82 | 83 | 84 |
85 | 86 | 87 | 88 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ASTranslate/src/pythonrenderer.py: -------------------------------------------------------------------------------- 1 | """ pythonrenderer -- render Apple events as Python 3.x code """ 2 | 3 | import os.path 4 | 5 | from aem import kae 6 | from appscript import referencerenderer, terminology 7 | import appscript 8 | 9 | from constants import * 10 | 11 | ###################################################################### 12 | # PRIVATE 13 | ###################################################################### 14 | 15 | 16 | _commandscache = {} 17 | 18 | _originalformatter = referencerenderer._Formatter 19 | 20 | class ReFormatter(_originalformatter): 21 | def __init__(self, appdata, nested=False): 22 | _originalformatter.__init__(self, appdata, nested) 23 | # appscript shows full app path in references, but just want visible name here for simplicity 24 | if not nested and self._appdata.constructor == 'path': 25 | name = os.path.basename(appdata.identifier) 26 | if name.lower().endswith('.app'): 27 | name = name[:-4] 28 | self.root = 'app(%r)' % name 29 | 30 | referencerenderer._Formatter = ReFormatter 31 | 32 | 33 | def renderobject(obj): 34 | if isinstance(obj, list): 35 | return '[%s]' % ', '.join([renderobject(o) for o in obj]) 36 | elif isinstance(obj, dict): 37 | return '{%s}' % ', '.join(['%s: %s' % (renderobject(k), renderobject(v)) for k, v in list(obj.items())]) 38 | elif isinstance(obj, appscript.Reference): 39 | return referencerenderer.renderreference(obj.AS_appdata, obj.AS_aemreference, True) 40 | else: 41 | return repr(obj) 42 | 43 | 44 | ###################################################################### 45 | # PUBLIC 46 | ###################################################################### 47 | 48 | 49 | def renderCommand(appPath, addressdesc, eventcode, 50 | targetRef, directParam, params, 51 | resultType, modeFlags, timeout, 52 | appdata): 53 | args = [] 54 | if (addressdesc.type, addressdesc.data) not in _commandscache: 55 | _commandscache[(addressdesc.type, addressdesc.data)] = dict([(data[1][0], (name, 56 | dict([(v, k) for (k, v) in list(data[1][-1].items())]) 57 | )) for (name, data) in list(appdata.referencebyname().items()) if data[0] == terminology.kCommand]) 58 | commandsbycode = _commandscache[(addressdesc.type, addressdesc.data)] 59 | try: 60 | commandName, argNames = commandsbycode[eventcode] 61 | except KeyError: 62 | raise UntranslatedKeywordError('event', eventcode, 'Python command') 63 | if directParam is not kNoParam: 64 | args.append(renderobject(directParam)) 65 | for key, val in list(params.items()): 66 | try: 67 | args.append('%s=%s' % (argNames[key], renderobject(val))) 68 | except KeyError: 69 | raise UntranslatedKeywordError('parameter', k, 'Python command') 70 | if resultType: 71 | args.append('resulttype=%s' % renderobject(resultType)) 72 | if modeFlags & kae.kAEWaitReply != kae.kAEWaitReply: 73 | args.append('waitreply=False') 74 | if timeout != -1: 75 | args.append('timeout=%i' % (timeout / 60)) 76 | return '%r.%s(%s)' % (targetRef, commandName, ', '.join(args)) 77 | 78 | 79 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/03_packingandunpackingdata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | 3. Packing and unpacking data 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

3. Packing and unpacking data

33 | 34 |

Codecs

35 | 36 |

The aem.Codecs class provides methods for converting Python data to aem.ae.AEDesc objects, and vice-versa.

37 | 38 | 39 |
Codecs
40 | 
41 |     Constructor:
42 |         __init__(self)
43 | 
44 |     Methods:
45 |         pack(self, data) -- convert Python data to an AEDesc; will
46 |                 raise a TypeError if data's type/class is unsupported
47 |             data : anything
48 |             Result : AEDesc
49 | 
50 |         unpack(self, desc) -- convert an AEDesc to Python data; 
51 |                 will return AEDesc if it's an unsupported type
52 |             desc : AEDesc
53 |             Result : anything
54 | 55 | 56 | 57 |

AE types

58 | 59 |

The Apple Event Manager defines several types for representing type/class names, enumerator names, etc. that have no direct equivalent in Python. Accordingly, AEM defines several classes to represent these types on the Python side. All share a common abstract base class, AETypeBase:

60 | 61 |

62 | 
63 | AETypeBase -- Abstract base class
64 | 
65 |     Constructor:
66 |         __init__(self, code)
67 |             code : bytes -- a four-character Apple event code
68 | 
69 |     Properties:
70 |         code : bytes (read-only) -- Apple event code
71 | 72 | 73 |

The four concrete classes are:

74 | 75 |
AEType(AETypeBase) -- represents an AE object of typeType
76 | 
77 | 
78 | AEEnum(AETypeBase) -- represents an AE object of typeEnumeration
79 | 
80 | 
81 | AEProp(AETypeBase) -- represents an AE object of typeProperty
82 | 
83 | 
84 | AEKey(AETypeBase) -- represents an AE object of typeKeyword
85 | 86 | 87 | 88 |
89 | 90 | 91 | 92 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /py-appscript/doc/full.css: -------------------------------------------------------------------------------- 1 | body {font-family:Arial,sans-serif; line-height:140%; color:#000; background-color:#fff; margin: 0 12% 0 8%;} 2 | 3 | /*headings*/ 4 | 5 | h2, h3, h4 {font-family: Palatino, Georgia, Serif; color:#00457e; background-color:transparent; font-weight:bold;} 6 | 7 | h1 {font-family:Arial,sans-serif;color:#00457e; background-color:transparent; font-weight:bold;} 8 | 9 | h1 {line-height:110%; padding: 0; margin-top:.7em; margin-bottom: .5em;} 10 | h2 {font-size:1.6em; padding:0 0 1px; border-bottom:solid #f77700 2px; margin:1.4em 0 1em;} 11 | h3 {font-size:1.3em; padding:0 0 0px; border-bottom:dotted #f77700 1px; margin:2em 0 0.8em 0;} 12 | h4 {font-size:1.1em; margin:1.8em 0 0.6em 0;} 13 | 14 | 15 | /*body text*/ 16 | 17 | 18 | p, li {margin: 1em 0;} 19 | pre {font-size:1em;} 20 | 21 | 22 | dl, pre { 23 | line-height:130%; color:#000; background-color:#cedbef; 24 | padding:1.8em 2em 2em; margin:1.2em 0em; border-bottom:solid #92b4db 6px; 25 | } 26 | 27 | dt, code {color:#00457e; background-color:transparent;} 28 | 29 | dt {font-weight:bold;} 30 | 31 | 32 | .hilitebox {padding:0.5em 0 0.55em; margin:1.3em 2em 1.4em; border:solid #f77700; border-width: 2px 0 2px; } 33 | 34 | code {font-family:Courier,Monospace;} 35 | 36 | pre code {color:#00406e; background-color:transparent;} 37 | 38 | 39 | dd pre, .hilitebox pre { 40 | color:#000; background-color:#cedbef; 41 | padding:1.8em 2em 2em; margin:1.2em 2em 1.2em 0; 42 | } 43 | 44 | 45 | dd+dt {margin-top:1em;} 46 | h3+p {margin-top:-0.3em;} 47 | h2+h3 {margin-top: 1.2em;} 48 | h4+p {margin-top:-0.35em;} 49 | 50 | 51 | .comment {color:#666; background-color:transparent;} 52 | 53 | hr {height: 1px; background-color: #00457e; border: 0px solid #00457e; margin-top:3em;} 54 | 55 | dl hr { 56 | color:#e8e8ff; background-color:transparent; height:1px; 57 | border:dashed #00457e; border-width: 1px 0 0; margin:0 -2em; 58 | } 59 | 60 | table { 61 | line-height:130%; width:100%; color:#00457e; background-color:#cedbef; 62 | border-bottom:solid #cedbef 10px; margin:1.2em 0em 2.4em; 63 | border-collapse:collapse; padding: 0 0 2em; 64 | } 65 | 66 | tr, th, td {padding: 0.4em 1.6em; margin: 0; border-width: 0;} 67 | 68 | th {text-align:left; font-size:0.95em; color:#00457e; background-color:#92b4db;} 69 | 70 | thead {background-color:#ccd;} 71 | 72 | 73 | /*links*/ 74 | 75 | /*a {font-style:italic;}*/ 76 | 77 | 78 | a:link {color:#00457e; background-color:transparent;} 79 | a:visited {color:#445555; background-color:transparent;} 80 | 81 | a img {border-width:0;} 82 | 83 | /* navigation*/ 84 | 85 | .navbar { 86 | font-size:0.9em; font-weight:bold; color:#e06000; background-color: #cedbef; 87 | padding: 0.45em 13px 0.3em; margin:0 0 0; border-bottom:solid #92b4db 6px; 88 | } 89 | 90 | .navbar span {float:right; font-weight:normal;} 91 | 92 | .navbar span strong a {color: #e06000;} 93 | 94 | 95 | .navbar a:link, .navbar a:visited, .footer a:link, .footer a:visited {font-style:normal; text-decoration:none;} 96 | .navbar a:hover, .navbar a:active, .footer a:hover, .footer a:active {text-decoration:underline;} 97 | 98 | /*footer*/ 99 | 100 | .footer { 101 | font-size:0.9em; font-weight:bold; color:#e06000; background-color: #cedbef; 102 | text-align:left; padding: 0.3em 13px 0.5em; margin:3.8em 0 0; border-top:solid #92b4db 6px; 103 | } 104 | 105 | .footer span {float:right; font-weight: normal; color:#00457e; background-color:#cedbef;} 106 | 107 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/05_keywordconversion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | 5. Keyword conversion 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

5. Keyword conversion

33 | 34 |

Keyword conversion

35 | 36 |

Because application terminology resources specify AppleScript-style keywords for class, property, command, etc. names, appscript uses the following rules to translate these keywords to legal Python identifiers:

37 | 38 |
    39 |
  • Characters a-z, A-Z, 0-9 and underscores (_) are preserved.
  • 40 | 41 |
  • Spaces, hyphens (-) and forward slashes (/) are replaced with underscores.
  • 42 | 43 |
  • Ampersands (&) are replaced by the word 'and'.
  • 44 | 45 |
  • All other characters are converted to 0x00-style hexadecimal representations.
  • 46 | 47 |
  • Names that match Python keywords or names reserved by appscript have an underscore appended. The following names are reserved by appscript: 48 | 49 |
    aborttransaction              help
    50 | after                         ID
    51 | AND                           ignore
    52 | any                           isin
    53 | before                        isnotin
    54 | beginning                     last
    55 | beginswith                    middle
    56 | begintransaction              next
    57 | contains                      NOT
    58 | doesnotbeginwith              OR
    59 | doesnotcontain                previous
    60 | doesnotendwith                relaunchmode
    61 | end                           resulttype
    62 | endswith                      timeout
    63 | endtransaction                waitreply
    64 | first
    65 | 66 | See Python's keyword module for a list of reserved Python keywords.
  • 67 | 68 |
  • Appscript defines default terminology for standard type classes such as integer and unicode_text, and standard commands such as open and quit. If an application-defined name matches a built-in name but has a different Apple event code, appscript will append an underscore to the application-defined name.
  • 69 |
70 | 71 |

You can use ASDictionary or appscript's built-in help method to export or view application terminology in appscript format. See the Getting Help chapter for more information.

72 | 73 |
74 | 75 | 76 | 77 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/08_realvsgenericreferences.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | 8. Real vs Generic References 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

8. Real vs generic references

33 | 34 |

Real vs. generic references

35 | 36 |

References may be either 'real' or 'generic'. A real reference relates to a specific application, while a generic reference doesn't. Generic references provide a convenient shortcut for writing literal references without having to specify an application each time.

37 | 38 |

A real reference begins with an app object that identifies the application whose object(s) it refers to, e.g.:

39 | 40 |
app('TextEdit').documents.end
41 | app(url='eppc://my-mac.local/Finder').home.folders.name
42 | 43 |

A generic reference begins with app, con or its and does not identify the application to which it relates, e.g.:

44 | 45 |
app.documents.end
46 | con.word[3]
47 | its.name.beginswith('d')
48 | 49 |

Generic references are only evaluated when used used within real references as selectors:

50 | 51 |
app('Finder').home.folders[its.name.beginswith('d')].get()
52 | 
53 | app('Tex-Edit Plus').windows[1].text[con.words[2]:con.words[-2]].get()
54 | 55 |

or as command parameters:

56 | 57 |
app('TextEdit').make(new=k.word,
58 |     at=app.documents[1].words.end, with_data='Hello')
59 | 
60 | app('Finder').desktop.duplicate(to=app.home.folders['Desktop Copy'])
61 | 62 | 63 |

Comparing and hashing references

64 | 65 |

Real application references can be compared for equality and are hashable (so can be used as dictionary keys). For two references to be considered equal, both must have the same application path or url and reference structure. Examples:

66 | 67 |
print(app('TextEdit').documents[1] == \
68 |     app(id='com.apple.textedit').documents[1].get())
69 | # Result: True; both references evaluate to the same
70 | #     application path and reference
71 | 
72 | print(app('Finder').home == \
73 |     app('Finder').home.get())
74 | # Result: False; app('Finder').home.get() returns a
75 | #     different reference to the same application object
76 | 77 |

Generic references cannot be compared or hashed.

78 | 79 |
80 | 81 | 82 | 83 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /ASTranslate/src/ASTranslate.py: -------------------------------------------------------------------------------- 1 | """ ASTranslate -- render Apple events sent by AppleScript in appscript syntax """ 2 | 3 | from AppKit import * 4 | import objc 5 | from PyObjCTools import AppHelper 6 | 7 | import aem 8 | 9 | import _astranslate, eventformatter 10 | from constants import * 11 | 12 | 13 | # TO DO: `tell app "TextEdit" to count documents` -> app('TextEdit').count(None, each=k.document) but None should be `app` or omitted entirely 14 | 15 | 16 | ####### 17 | 18 | _userDefaults = NSUserDefaults.standardUserDefaults() 19 | 20 | if not _userDefaults.integerForKey_('defaultOutputLanguage'): 21 | _userDefaults.setInteger_forKey_(0, 'defaultOutputLanguage') 22 | 23 | _standardCodecs = aem.Codecs() 24 | 25 | 26 | ####### 27 | 28 | class ASTranslateDocument(NSDocument): 29 | 30 | codeView = objc.IBOutlet('codeView') 31 | resultView = objc.IBOutlet('resultView') 32 | 33 | _currentStyle = 0 34 | 35 | def setCurrentStyle_(self, v): 36 | self._currentStyle = v 37 | self.resultView.setString_('\n\n'.join(self._resultStores[v])) 38 | _userDefaults.setInteger_forKey_(v, 'defaultOutputLanguage') 39 | setCurrentStyle_ = objc.accessor(setCurrentStyle_) 40 | 41 | def currentStyle(self): 42 | return self._currentStyle 43 | currentStyle = objc.accessor(currentStyle) 44 | 45 | def _addResult_to_(self, kind, val): 46 | if kind == kLangAll: 47 | for lang in self._resultStores: 48 | lang.append(val) 49 | else: 50 | self._resultStores[kind].append(val) 51 | if kind == self.currentStyle() or kind == kLangAll: 52 | self.resultView.textStorage().mutableString().appendString_( 53 | ('%s\n\n' % self._resultStores[self.currentStyle()][-1])) 54 | self.resultView.setTextColor_(NSColor.textColor()) 55 | 56 | 57 | def windowNibName(self): # a default NSWindowController is created automatically 58 | return "ASTranslateDocument" 59 | 60 | def windowControllerDidLoadNib_(self, controller): 61 | self._resultStores = [[] for _ in range(eventformatter.kLanguageCount)] 62 | self.setCurrentStyle_(_userDefaults.integerForKey_('defaultOutputLanguage')) 63 | 64 | @objc.IBAction 65 | def runScript_(self, sender): 66 | self.resultView.setString_('') 67 | for lang in self._resultStores: 68 | while lang: 69 | lang.pop() 70 | try: 71 | sourceDesc = _standardCodecs.pack(self.codeView.string()) 72 | handler = eventformatter.makeCustomSendProc( 73 | self._addResult_to_, _userDefaults.boolForKey_('sendEvents')) 74 | result = _astranslate.translate(sourceDesc, handler) # returns tuple; first item indicates if ok 75 | if result[0]: # script result 76 | script, _ = (_standardCodecs.unpack(desc) for desc in result[1:]) 77 | self.codeView.setString_(script) 78 | self._addResult_to_(kLangAll, 'OK') 79 | else: # script error info 80 | script, errorNum, errorMsg, pos = (_standardCodecs.unpack(desc) for desc in result[1:]) 81 | start, end = (pos[aem.AEType(k)] for k in [b'srcs', b'srce']) 82 | if script: 83 | errorKind = 'Runtime' 84 | self.codeView.setString_(script) 85 | else: 86 | errorKind = 'Compilation' 87 | self._addResult_to_(kLangAll, 88 | '%s Error:\n%s (%i)' % (errorKind, errorMsg, errorNum)) 89 | self.codeView.setSelectedRange_((start, end - start)) 90 | except aem.ae.MacOSError as e: 91 | self._addResult_to_(kLangAll, 'OS Error: %i' % e.args[0]) 92 | except Exception as e: 93 | self._addResult_to_(kLangAll, 'Unexpected Error: {}'.format(e)) 94 | 95 | def isDocumentEdited(self): 96 | return False 97 | 98 | 99 | ####### 100 | 101 | AppHelper.runEventLoop() 102 | 103 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/01_introduction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | 1. Introduction 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

1. Introduction

33 | 34 |

About appscript

35 | 36 |

Python appscript (py-appscript) is an easy-to-use Apple event bridge that allows 'AppleScriptable' applications to be controlled by ordinary Python scripts. Appscript makes Python an excellent alternative to Apple's own AppleScript language for automating your Mac.

37 | 38 |

For example, to get the value of the first paragraph of the topmost document in TextEdit:

39 | 40 |
app('TextEdit').documents['Read Me'].paragraphs[1].get()
41 | 42 |

This is equivalent to the AppleScript statement:

43 | 44 |
tell application "TextEdit"
45 |     get paragraph 1 of document "Read Me"
46 | end tell
47 | 48 | 49 |

Before you start...

50 | 51 |

In order to use appscript effectively, you will need to understand the differences between the Apple event and Python object systems.

52 | 53 |

In contrast to the familiar object-oriented approach of other inter-process communication systems such as COM and Distributed Objects, Apple event IPC is based on a combination of remote procedure calls and first-class queries - somewhat analogous to using XPath over XML-RPC.

54 | 55 |

While appscript uses an object-oriented-like syntax for conciseness and readability, like AppleScript, it behaves according to Apple event rules. As a result, Python users will discover that some things work differently in appscript from what they're used to. For example:

56 | 57 |
    58 |
  • object elements are one-indexed, not zero-indexed like Python lists
  • 59 | 60 |
  • referencing a property of an application object does not automatically return the property's value (you need a get command for that)
  • 61 | 62 |
  • many applications allow a single command to operate on multiple objects at the same time, providing significant performance benefits when manipulating large numbers of application objects.
  • 63 | 64 |
65 | 66 |

Chapters 2 and 3 of this manual provide further information on how Apple event IPC works and a tutorial-based introduction to the Python appscript bridge. Chapter 4 describes various ways of getting help when scripting applications. Chapters 5 through 12 cover the appscript API, and chapter 13 discusses techniques for optimising performance.

67 | 68 | 69 |
70 | 71 | 72 | 73 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/makeidentifier/__init__.py: -------------------------------------------------------------------------------- 1 | """makeidentifier -- Convert AppleScript keywords to identifiers.""" 2 | 3 | import keyword, string 4 | 5 | try: 6 | set 7 | except: # Python 2.3 8 | from sets import Set as set 9 | 10 | from . import pyappscript, rbappscript, nodeautomation 11 | 12 | 13 | ###################################################################### 14 | # PRIVATE 15 | ###################################################################### 16 | 17 | class _Converter: 18 | # Special conversions for selected characters in AppleScript keywords; makes appscript users' lives easier. Note this is a lossy conversion, but chances of this causing name collisions are very low unless application developers are very stupid in choosing keyword names in their dictionaries. (Mind, this wouldn't have been a problem had Apple restricted them all to using only alphanumeric and space characters to begin with, which would've allowed simple, unambiguous conversion to C-style identifiers and back.) 19 | 20 | _specialConversions = { 21 | ' ': '_', 22 | '-': '_', 23 | '&': 'and', 24 | '/': '_', 25 | } 26 | 27 | _legalChars = string.ascii_letters + '_' 28 | _alphanum = _legalChars + string.digits 29 | 30 | def __init__(self, reservedWords): 31 | self._cache = {} 32 | self._reservedWords = set(reservedWords) 33 | 34 | 35 | 36 | class CamelCaseConverter(_Converter): 37 | 38 | def convert(self, s): 39 | """Convert string to identifier. 40 | s : str 41 | Result : str 42 | """ 43 | if s not in self._cache: 44 | legal = self._legalChars 45 | res = '' 46 | uppercaseNext = False 47 | for c in s.strip(): 48 | if c in legal: 49 | if uppercaseNext: 50 | c = c.upper() 51 | uppercaseNext = False 52 | res += c 53 | elif c in ' -/': 54 | uppercaseNext = True 55 | elif c == '&': 56 | res += 'And' 57 | else: 58 | if res == '': 59 | res = '_' # avoid creating an invalid identifier 60 | res += '0x%2.2X' % ord(c) 61 | legal = self._alphanum 62 | if res in self._reservedWords or res.startswith('_') or res.startswith('AS_') or not res: 63 | res += '_' 64 | self._cache[s] = str(res) 65 | return self._cache[s] 66 | 67 | 68 | 69 | class UnderscoreConverter(_Converter): 70 | 71 | def convert(self, s): 72 | """Convert string to identifier. 73 | s : str 74 | Result : str 75 | """ 76 | if s not in self._cache: 77 | legal = self._legalChars 78 | res = '' 79 | for c in s: 80 | if c in legal: 81 | res += c 82 | elif c in self._specialConversions: 83 | res += self._specialConversions[c] 84 | else: 85 | if res == '': 86 | res = '_' # avoid creating an invalid identifier 87 | res += '0x%2.2X' % ord(c) 88 | legal = self._alphanum 89 | if res in self._reservedWords or res.startswith('_') or res.startswith('AS_') or not res: 90 | res += '_' 91 | self._cache[s] = str(res) 92 | return self._cache[s] 93 | 94 | 95 | 96 | _converters = { 97 | 'applescript': lambda s:s, 98 | 'py-appscript': UnderscoreConverter(pyappscript.kReservedWords).convert, 99 | 'rb-scpt': UnderscoreConverter(rbappscript.kReservedWords).convert, 100 | 'nodeautomation': CamelCaseConverter(nodeautomation.kReservedWords).convert, 101 | } 102 | 103 | 104 | ###################################################################### 105 | # PUBLIC 106 | ###################################################################### 107 | 108 | 109 | def getconverter(name='py-appscript'): 110 | try: 111 | return _converters[name] 112 | except: 113 | raise KeyError("Keyword converter not found: %r" % name) 114 | 115 | -------------------------------------------------------------------------------- /py-appscript/doc/aem-manual/02_apioverview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-aem manual | 2. API overview 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

2. API overview

33 | 34 |

Classes

35 | 36 |

The main AEM classes are as follows:

37 | 38 |
39 |
Application
40 |
Represents a scriptable application, and provides an event method for constructing Apple events.
41 | 42 |
Event
43 |
Represents a ready-to-send Apple event, and provides a send method for sending it. Returned by the Application class's event method.
44 | 45 |
EventError
46 |
Represents an error raised by the target application or the Apple Event Manager.
47 | 48 |
Codecs
49 |
Provides pack and unpack methods for converting Python values to AE types, and vice-versa. Clients usually don't need to access this class directly.
50 | 51 |
AEType, AEEnum
52 |
Represent Apple event type and enumerator values.
53 | 54 |
55 | 56 |

In addition, there are a number of classes used to represent application references, although the user does not instantiate these directly.

57 | 58 | 59 |

Attributes

60 | 61 |

AEM exports three top-level attributes for use in constructing application references:

62 | 63 |
64 |
app
65 |
Base object used to construct absolute references.
66 | 67 |
con
68 |
Base object used to construct relative reference to container object (used in range specifiers).
69 | 70 |
its
71 |
Base object used to construct relative reference to object being tested (used in filter specifiers).
72 |
73 | 74 | 75 |

References are constructed from these base objects using chained property/method calls.

76 | 77 | 78 |

Modules

79 | 80 |

AEM also exports the following support modules:

81 | 82 |
83 |
findapp
84 |
Provides functions for locating applications by name or bundle id.
85 | 86 |
ae
87 |
Low-level extension that defines an AEDesc type representing Carbon Apple event descriptors (AEDescs). Also provides a number of support functions used by AEM, appscript and related packages.
88 | 89 |
kae
90 |
Exports constants defined by Apple Event Manager and Open Scripting APIs.
91 | 92 |
93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /py-appscript/doc/mactypes-manual/02_aliasclass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-mactypes manual | 2. Alias class 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

2. The Alias class

33 | 34 |

The Alias class represents a persistent reference to a filesystem object. Aliases keep track of filesystem objects even if they're renamed or moved to another location on the same disk.

35 | 36 | 37 |

Methods

38 | 39 |
Alias -- A persistent reference to a filesystem object. Aliases keep
 40 |         track of filesystem objects as they're moved around the disk
 41 |         or renamed. Provides a variety of properties and constructors
 42 |         for converting to and from other Python types.
 43 | 
 44 |     Constructors:
 45 | 
 46 |         Alias(path) -- make Alias object from POSIX path
 47 | 
 48 | 	File.makewithhfspath(path) -- make Alias object from HFS path
 49 | 
 50 |         Alias.makewithurl(url) -- make Alias object from file URL
 51 | 
 52 |         Alias.makewithdesc(desc) -- make Alias object from an
 53 |                 AEDesc of typeAlias
 54 | 
 55 |     Properties:
 56 | 
 57 |         path : unicode -- POSIX path
 58 | 
 59 | 	hfspath : unicode -- HFS path
 60 | 
 61 |         url : string -- file URL
 62 | 
 63 |         file : mactypes.File
 64 | 
 65 |         alias : mactypes.Alias -- itself
 66 | 
 67 |         desc : aem.ae.AEDesc
68 | 69 | 70 |

Examples

71 | 72 |
from appscript import *
 73 | from mactypes import *
 74 | 
 75 | f = Alias('/Users/foo/some file')
 76 | 
 77 | print f
 78 | # mactypes.Alias("/Users/foo/some file")
 79 | 
 80 | print f.path
 81 | # /Users/foo/some file
 82 | 
 83 | app('TextEdit').open(f)
 84 | # opens document in TextEdit
 85 | 
 86 | Alias('/some/non/existent/location')
 87 | # ValueError: Can't make mactypes.Alias as file doesn't exist: 
 88 | #     '/some/non/existent/location' 
89 | 90 | 91 |

Notes

92 | 93 |

When creating an Alias instance, POSIX paths may be either relative or absolute and are automatically normalised using os.path.abspath. Paths to non-existent filesystem locations will result in a ValueError being raised.

94 | 95 |

Note that comparing an Alias object against a File object always returns false, even if both point to the same location.

96 | 97 |

Remember that aliases can change when the corresponding filesystem object is moved, so take care when using Alias objects in situations that involve comparing or hashing them (e.g. as dict keys).

98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /py-appscript/doc/mactypes-manual/03_fileclass.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-mactypes manual | 3. File class 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 | 33 |

3. The File class

34 | 35 |

The File class represents a fixed filesystem location which may or may not already exist.

36 | 37 |

Methods

38 | 39 |
File -- A reference to a fixed filesystem location which may or may not 
 40 |         already exist. Provides a variety of properties and constructors
 41 |         for converting to and from other Python types.
 42 | 
 43 |     Constructors:
 44 | 
 45 |         File(path) -- make File object from POSIX path
 46 | 
 47 | 	File.makewithhfspath(path) -- make File object from HFS path
 48 | 
 49 |         File.makewithurl(url) -- make File object from file URL
 50 | 
 51 |         File.makewithdesc(desc) -- make File object from
 52 |                 aem.ae.AEDesc of typeFileURL
 53 | 
 54 |     Properties:
 55 | 
 56 |         path : unicode -- POSIX path
 57 | 
 58 | 	hfspath : unicode -- HFS path
 59 | 
 60 |         url : string -- file URL
 61 | 
 62 |         file : mactypes.File -- a new File object
 63 | 
 64 |         alias : mactypes.Alias
 65 | 
 66 |         desc : aem.ae.AEDesc
67 | 68 | 69 |

Examples

70 | 71 |
from appscript import *
 72 | from mactypes import *
 73 | 
 74 | f = File('/Users/foo/some file')
 75 | 
 76 | print f
 77 | # mactypes.File("/Users/foo/some file")
 78 | 
 79 | print f.path
 80 | # /Users/foo/some file
 81 | 
 82 | print f.url
 83 | # file://localhost/Users/foo/some%20file
 84 | 
 85 | app('TextEdit').documents[1].save(in_=f)
 86 | # saves front TextEdit document at the given location
87 | 88 | 89 |

Notes

90 | 91 |

When creating a File instance, POSIX paths may be either relative or absolute and are automatically normalised using os.path.abspath.

92 | 93 |

When comparing File objects for equality, File.__eq__ will perform a case-sensitive comparison of their file paths. Client that need to perform (for example) case-insensitive comparisons should obtain path strings from each File object, then normalise and compare those values as necessary.

94 | 95 |

The File class normally wraps AEDescs of typeFileURL, which can represent both existing and non-existing locations. For legacy compatibility with older Carbon apps, it can also wrap existing AEDescs of typeFSRef or typeFSS, although these obsolete Carbon types are long since deprecated and should not be used now.

96 | 97 |
98 | 99 | 100 | 101 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/renderers/relationships.py: -------------------------------------------------------------------------------- 1 | """relationships""" 2 | 3 | from sys import stdout 4 | 5 | ###################################################################### 6 | # PRIVATE 7 | ###################################################################### 8 | 9 | class _Out: 10 | # Default target for writing help text; prints to stdout. 11 | def write(self, s): 12 | stdout.write( s.encode('utf8', 'replace')) 13 | 14 | 15 | ###################################################################### 16 | # PUBLIC 17 | ###################################################################### 18 | 19 | class TextRenderer: 20 | def __init__(self, out=_Out()): 21 | self.output = out 22 | self.indent = '' 23 | self._pad = False 24 | 25 | def add(self, name, type, ismany, islast, tbc=False): 26 | if self._pad: 27 | print(' ' + self.indent, file=self.output) # insert a line of extra padding after a subtree 28 | self._pad = False 29 | print(' %s%s%s%s%s' % (self.indent, 30 | ismany and '=' or '-', name, type and ' <%s>' % type or '', tbc and ' ->' or ''), file=self.output) 31 | if islast and self.indent: 32 | self.indent = self.indent[:-4] + ' ' 33 | 34 | def down(self): 35 | self.indent += ' |' 36 | 37 | def up(self): 38 | self.indent = self.indent[:-4] 39 | self._pad = True 40 | 41 | 42 | ## 43 | 44 | class RelationshipGrapher: 45 | def __init__(self, terms, renderer): 46 | self.terms = terms 47 | self.renderer = renderer 48 | self.relationshipcache = {} 49 | 50 | def _relationships(self, klass): 51 | if klass.name not in self.relationshipcache: 52 | klass = klass.full() 53 | properties = [o for o in klass.properties() if o.type.realvalue().kind == 'class'] # TO DO: add isrelationship method to osadictionary.Property? 54 | elements = list(klass.elements()) 55 | self.relationshipcache[klass.name] = (properties, elements) 56 | return self.relationshipcache[klass.name] 57 | 58 | def _hasrelationships(self, klass): 59 | p, e = self._relationships(klass) 60 | return bool(p or e) 61 | 62 | 63 | def draw(self, classname='application', maxdepth=3): 64 | def render(klass, visitedproperties, visitedelements, maxdepth): 65 | properties, elements = self._relationships(klass) 66 | if properties or elements: 67 | # a recurring relationship is shown in full at the shallowest level, and deeper repetitions of the same relationship are marked 'tbc' (e.g. in Finder, folders contain folders) to avoid duplication, so next two lines are used to keep track of repetitions 68 | allvisitedproperties= visitedproperties + [o.type for o in properties] 69 | allvisitedelements = visitedelements + [o.type for o in elements] 70 | self.renderer.down() 71 | for i, prop in enumerate(properties): 72 | propclass = prop.type.realvalue() # TO DO: asking for realvalue is dodgy; need to ensure we get a class definition 73 | iscontinued = (prop.type in visitedproperties or prop.type in allvisitedelements \ 74 | or maxdepth < 2) and self._hasrelationships(propclass) 75 | self.renderer.add(prop.name, propclass.name, False, 76 | i == len(properties) and not elements, iscontinued) 77 | if not iscontinued: 78 | render(propclass, allvisitedproperties, allvisitedelements, maxdepth - 1) 79 | for i, elem in enumerate(elements): 80 | elemclass = elem.type.realvalue() # TO DO: asking for realvalue is dodgy; need to ensure we get a class definition 81 | iscontinued = (elem.type in visitedelements or maxdepth < 2) \ 82 | and self._hasrelationships(elemclass) 83 | self.renderer.add(elem.name, None, True, 84 | i == len(elements), iscontinued) 85 | if not iscontinued: 86 | render(elemclass, allvisitedproperties, allvisitedelements, maxdepth - 1) 87 | self.renderer.up() 88 | klass = self.terms.classes().byname(classname) 89 | self.renderer.add(classname, None, False, False) 90 | render(klass, [], [], maxdepth) 91 | 92 | -------------------------------------------------------------------------------- /ASTranslate/src/eventformatter.py: -------------------------------------------------------------------------------- 1 | """ eventformatter -- creates custom sendprocs to render Apple events in appscript syntax """ 2 | 3 | import traceback 4 | 5 | from Foundation import NSBundle 6 | 7 | from aem import kae, ae 8 | import appscript, aem, mactypes 9 | import appscript.reference 10 | 11 | import pythonrenderer, rubyrenderer, noderenderer 12 | from constants import * 13 | 14 | _standardCodecs = aem.Codecs() 15 | 16 | _appCache = {} 17 | 18 | ####### 19 | 20 | def _unpackEventAttributes(event): 21 | atts = [] 22 | for code in [kae.keyEventClassAttr, kae.keyEventIDAttr, kae.keyAddressAttr]: 23 | atts.append(_standardCodecs.unpack(event.getattr(code, kae.typeWildCard))) 24 | return atts[0].code + atts[1].code, atts[2] 25 | 26 | 27 | def makeCustomSendProc(addResultFn, isLive): 28 | def customSendProc(event, modeFlags, timeout): 29 | # unpack required attributes 30 | try: 31 | eventcode, addressdesc = _unpackEventAttributes(event) 32 | appPath = ae.addressdesctopath(addressdesc) 33 | 34 | # reject events sent to self otherwise they'll block main event loop 35 | if appPath == NSBundle.mainBundle().bundlePath(): 36 | raise MacOSError(-1708) 37 | 38 | # get app instance and associated data 39 | if (addressdesc.type, addressdesc.data) not in _appCache: 40 | if addressdesc.type != kae.typeProcessSerialNumber: 41 | raise OSAError(10000, 42 | "Can't identify application (addressdesc descriptor not typeProcessSerialNumber)") 43 | app = appscript.app(appPath) 44 | appData = app.AS_appdata 45 | 46 | _appCache[(addressdesc.type, addressdesc.data)] = (app, appData) 47 | app, appData = _appCache[(addressdesc.type, addressdesc.data)] 48 | 49 | # unpack parameters 50 | desc = event.coerce(kae.typeAERecord) 51 | params = {} 52 | for i in range(desc.count()): 53 | key, value = desc.getitem(i + 1, kae.typeWildCard) 54 | params[key] = appData.unpack(value) 55 | resultType = params.pop(b'rtyp', None) 56 | directParam = params.pop(b'----', kNoParam) 57 | try: 58 | subject = appData.unpack(event.getattr(kae.keySubjectAttr, kae.typeWildCard)) 59 | except Exception: 60 | subject = None 61 | 62 | # apply special cases 63 | if subject is not None: 64 | targetRef = subject 65 | elif eventcode == b'coresetd': 66 | # Special case: for 'set' command, use direct parameter as target reference and use 'to' parameter for direct argument 67 | targetRef = directParam 68 | directParam = params.pop(b'data') 69 | elif isinstance(directParam, appscript.reference.Reference): 70 | # Special case: if direct parameter is a reference, use this as target reference 71 | targetRef = directParam 72 | directParam = kNoParam 73 | else: 74 | targetRef = app 75 | 76 | # kludge: timeOutInTicks constants (kAEDefaultTimeout = -1, kNoTimeOut = -2) screw up when represented as 64-bit instead of 32-bit longs 77 | _timeout = timeout 78 | if timeout == 0xffffffff or timeout > 0x888888888888: 79 | _timeout = -1 # default timeout 80 | elif timeout == 0xfffffffe: 81 | _timeout = -2 # no timeout 82 | 83 | # render 84 | for key, renderer in [(kLangPython, pythonrenderer), (kLangRuby, rubyrenderer), (kLangNode, noderenderer)]: 85 | try: 86 | addResultFn(key, renderer.renderCommand( 87 | appPath, addressdesc, eventcode, 88 | targetRef, directParam, params, 89 | resultType, modeFlags, _timeout, 90 | appData)) 91 | except (UntranslatedKeywordError, UntranslatedUserPropertyError) as e: 92 | s = 'Untranslated event %r\n%s' % (eventcode, e) 93 | addResultFn(key, s) 94 | except Exception as e: 95 | traceback.print_exc() 96 | s = 'Untranslated event %r' % eventcode 97 | addResultFn(key, s) 98 | 99 | except Exception as e: 100 | traceback.print_exc() 101 | s = 'Untranslated event %r' % eventcode 102 | addResultFn(kLangAll, s) 103 | 104 | # let Apple event execute as normal, if desired 105 | if isLive: 106 | return event.send(modeFlags, timeout) 107 | else: 108 | return ae.newdesc(kae.typeNull, b'') 109 | return customSendProc 110 | 111 | -------------------------------------------------------------------------------- /py-appscript/test/test_appscriptreference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import appscript as AS 5 | 6 | 7 | class TC_AppscriptReferences(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.te = AS.app('TextEdit') 11 | self.s = repr(self.te) 12 | 13 | def test_referenceForms(self): 14 | for val, res, unpackedVersion in [ 15 | [self.te.text, self.s+'.text', None], 16 | 17 | [self.te.documents, self.s+'.documents', None], 18 | 19 | [self.te.documents[1], 20 | self.s+'.documents[1]', None], 21 | [self.te.documents['foo'], 22 | self.s+".documents['foo']", None], 23 | [self.te.documents.ID(300), 24 | self.s+'.documents.ID(300)', None], 25 | 26 | [self.te.documents.next(AS.k.document), 27 | self.s+'.documents.next(k.document)', None], 28 | [self.te.documents.previous(AS.k.document), 29 | self.s+'.documents.previous(k.document)', None], 30 | 31 | [self.te.documents.first, self.s+'.documents.first', None], 32 | [self.te.documents.middle, self.s+'.documents.middle', None], 33 | [self.te.documents.last, self.s+'.documents.last', None], 34 | [self.te.documents.any, self.s+'.documents.any', None], 35 | 36 | [AS.con.documents[3], 'con.documents[3]', None], 37 | 38 | [self.te.documents[ 39 | AS.con.documents[3]: 40 | AS.con.documents['foo']], 41 | self.s+'.documents[' + 42 | 'con.documents[3]:' + 43 | "con.documents['foo']]", None], 44 | 45 | 46 | [(AS.its.name == 'foo').AND(AS.its.words == []), 47 | "(its.name == 'foo').AND(its.words == [])", None], 48 | 49 | [AS.its.words != [], 50 | 'its.words != []', 51 | (AS.its.words == []).NOT], # i.e. there isn't a KAENotEqual operator, so not-equal tests are actually packed as an equal test followed by not test 52 | 53 | [AS.its.words == None, 'its.words == None', None], 54 | [AS.its.words.size > 0, 55 | 'its.words.size > 0', None], 56 | [AS.its.words <= '', "its.words <= ''", None], 57 | [AS.its.words.beginswith('foo').NOT, 58 | "(its.words.beginswith('foo')).NOT", None], 59 | 60 | 61 | [AS.its.words.contains('foo'), "its.words.contains('foo')", None], 62 | [AS.its.words.isin('foo'), "its.words.isin('foo')", None], 63 | 64 | [self.te.documents[AS.its.size >= 42], 65 | self.s+'.documents[its.size >= 42]', None], 66 | 67 | [self.te.documents[1:'bar'], 68 | self.s+".documents[1:'bar']", 69 | self.te.documents[AS.con.documents[1]:AS.con.documents['bar']]], 70 | 71 | [self.te.documents[1].text \ 72 | .paragraphs.characters[ 73 | AS.con.characters[3]: 74 | AS.con.characters[55] 75 | ].next(AS.k.character).after, 76 | self.s+'.documents[1].text.paragraphs.characters' + 77 | '[con.characters[3]:con.characters[55]]' + 78 | '.next(k.character).after', None], 79 | 80 | [(AS.its.name != 'foo').AND(AS.its.words == []).NOT, 81 | "((its.name != 'foo').AND(its.words == [])).NOT", 82 | (AS.its.name == 'foo').NOT.AND(AS.its.words == []).NOT], 83 | 84 | [self.te.documents.beginning, self.s+'.documents.beginning', None], 85 | [self.te.documents.end, self.s+'.documents.end', None], 86 | [self.te.documents[3].before, self.s+'.documents[3].before', None], 87 | [self.te.documents['foo'].after, self.s+".documents['foo'].after", None], 88 | 89 | ]: 90 | 91 | self.assertEqual(res, repr(val)) 92 | d = self.te.AS_appdata.pack(val) 93 | val = unpackedVersion or val 94 | val2 = self.te.AS_appdata.unpack(d) 95 | if val.__class__ == self.te.AS_appdata.unpack(d).__class__: # note: Reference and GenericReference currently aren't comparable with each other, so the next test would always fail for those 96 | self.assertEqual(val, val2) 97 | val2 = self.te.AS_appdata.unpack(d) 98 | if val.__class__ == self.te.AS_appdata.unpack(d).__class__: # note: Reference and GenericReference currently aren't comparable with each other, so the next test would always fail for those 99 | self.assertEqual(val2, val) 100 | 101 | 102 | 103 | if __name__ == '__main__': 104 | unittest.main() 105 | -------------------------------------------------------------------------------- /py-appscript/sample/appscript/TextEdit_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Demonstrates various references and commands in action. 4 | 5 | from appscript import * 6 | 7 | textedit = app('TextEdit') # get an application object for TextEdit 8 | 9 | 10 | # tell application "TextEdit" to activate 11 | textedit.activate() 12 | 13 | 14 | # tell application "TextEdit" to make new document at end of documents 15 | textedit.documents.end.make(new=k.document) 16 | 17 | 18 | # tell application "TextEdit" to set text of document 1 to "Hello World\n" 19 | textedit.documents[1].text.set('Hello World\n') 20 | 21 | 22 | # tell application "TextEdit" to get a reference to every window 23 | print(textedit.windows) 24 | 25 | 26 | # tell application "TextEdit" to get a reference to document 1 27 | print(textedit.documents) 28 | 29 | 30 | # tell application "TextEdit" to get every document 31 | print(textedit.documents.get()) 32 | 33 | 34 | # tell application "TextEdit" to get a reference to document 1 35 | print(textedit.documents[1]) 36 | 37 | 38 | # tell application "TextEdit" to get document 1 39 | print(textedit.documents[1].get()) 40 | 41 | 42 | # tell application "TextEdit" to get window id 3210 43 | # print(textedit.windows.ID(3210).get()) 44 | 45 | 46 | # tell application "TextEdit" to get a reference to text of document 1 47 | print(textedit.documents[1].text) 48 | 49 | 50 | # tell application "TextEdit" to get text of document 1 51 | print(textedit.documents[1].text.get()) 52 | 53 | 54 | # tell application "TextEdit" to make new document at end of documents 55 | textedit.documents.end.make(new=k.document) 56 | 57 | 58 | # tell application "TextEdit" to set text of document 1 to "Happy Happy Joy Joy\n" 59 | textedit.documents[1].text.set('Happy Happy Joy Joy\n') 60 | 61 | 62 | # tell application "TextEdit" to get text of every document 63 | print(textedit.documents.text.get()) 64 | 65 | 66 | # tell application "TextEdit" to count each word of text of document 1 67 | print(textedit.documents[1].text.count(each=k.word)) 68 | 69 | 70 | # tell application "TextEdit" to get words 3 thru -1 of document 1 71 | print(textedit.documents[1].words[3:-1].get()) 72 | 73 | 74 | # tell application "TextEdit" to set size of character 1 of every word 75 | # of document 1 to 24 76 | textedit.documents[1].words.characters[1].size.set(24) 77 | 78 | 79 | # tell application "TextEdit" to set color of any word of document 1 to {65535, 0, 0} 80 | textedit.documents[1].words.any.color.set([65535, 0, 0]) 81 | 82 | 83 | # tell application "TextEdit" to make new paragraph at 84 | # (after last paragraph of text of document 1) with data "Silly Rabbit\n" 85 | textedit.documents[1].text.paragraphs.last.after.make( 86 | new=k.paragraph, with_data='Silly Rabbit\n') 87 | 88 | 89 | # tell application "TextEdit" to get paragraph after paragraph 1 of document 1 90 | print(textedit.documents[1].paragraphs[1].next(k.paragraph).get()) 91 | 92 | 93 | # tell application "TextEdit" to make new document at end of documents 94 | # with properties {text:"foo\nbar\n\n\nbaz\n\nfub\n"} 95 | textedit.documents.end.make(new=k.document, 96 | with_properties={k.text: 'foo\nbar\n\n\nbaz\n\nfub\n'}) 97 | 98 | 99 | # tell application "TextEdit" to get every paragraph of text of document 1 100 | print(textedit.documents[1].text.paragraphs.get()) 101 | 102 | 103 | # tell application "TextEdit" to get every paragraph of document 1 104 | # where it is not "\n" -- get non-empty paragraphs 105 | print(textedit.documents[1].paragraphs[its != '\n'].get()) 106 | 107 | 108 | # tell application "TextEdit" to get text of every document 109 | # whose text begins with "H" 110 | print(textedit.documents[its.text.beginswith('H')].text.get()) 111 | 112 | 113 | 114 | # The following examples don't work in TextEdit but will work in, 115 | # for example, Tex-Edit Plus: 116 | # 117 | # # tell application "Tex-Edit Plus" to get words (character 5) 118 | # # thru (paragraph 9) of document 1 119 | # print(app('Tex-Edit Plus').documents[1] \ 120 | # .words[con.characters[5]:con.paragraphs[9]].get()) 121 | # 122 | # 123 | # # tell application "Tex-Edit Plus" to get every word of text 124 | # # of document 1 whose color is {0, 0, 0} 125 | # print(app('Tex-Edit Plus').documents[1].text.paragraphs \ 126 | # [its.color.equals((0, 0, 0))].get()) -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/tables/tableparser.py: -------------------------------------------------------------------------------- 1 | """osaterminology.tables.tableparser -- builds tables similar to those used by appscript itself; based on py-appscript's terminologyparser module""" 2 | 3 | try: 4 | set 5 | except NameError: # Python 2.3 6 | from sets import Set as set 7 | 8 | from osaterminology.sax.aeteparser import parse, Receiver 9 | from osaterminology.makeidentifier import getconverter 10 | 11 | __all__ = ['buildtablesforaetes'] 12 | 13 | ###################################################################### 14 | # PRIVATE 15 | ###################################################################### 16 | 17 | class TerminologyTableReceiver(Receiver): 18 | 19 | def __init__(self, style): 20 | self.convert = getconverter(style) 21 | # terminology tables; order is significant where synonym definitions occur 22 | self.commands = {} 23 | self.properties = [] 24 | self.elements = [] 25 | self.classes = [] 26 | self.enumerators = [] 27 | # use sets to record previously found definitions, and avoid adding duplicates to lists 28 | # (i.e. '(name, code) not in ' is quicker than using '(name, code) not in ') 29 | self._foundproperties = set() 30 | self._foundelements = set() 31 | self._foundclasses = set() 32 | self._foundenumerators = set() 33 | # ideally, aetes should define both singular and plural names for each class, but 34 | # some define only one or the other so we need to fill in any missing ones afterwards 35 | self._spareclassnames = {} 36 | self._foundclasscodes = set() 37 | self._foundelementcodes = set() 38 | 39 | def start_command(self, code, name, description, directarg, reply): 40 | self.commandargs = [] 41 | if name not in self.commands or self.commands[name][1] == code: 42 | self.commands[name] = (self.convert(name), code, self.commandargs) 43 | 44 | def add_labelledarg(self, code, name, description, datatype, isoptional, islist, isenum): 45 | self.commandargs.append((self.convert(name), code)) 46 | 47 | def start_class(self, code, name, description): 48 | self.classname = self.convert(name) 49 | self.classcode = code 50 | self.isplural = False 51 | self._spareclassnames[code] = self.classname 52 | 53 | def is_plural(self): 54 | self.isplural = True 55 | 56 | def add_property(self, code, name, description, datatype, islist, isenum, ismutable): 57 | if (name, code) not in self._foundproperties: 58 | self.properties.append((self.convert(name), code)) 59 | self._foundproperties.add((name, code)) 60 | 61 | def end_class(self): 62 | name, code = self.classname, self.classcode 63 | if self.isplural: 64 | if (name, code) not in self._foundelements: 65 | self.elements.append((name, code)) 66 | self._foundelements.add((name, code)) 67 | self._foundelementcodes.add(code) 68 | else: 69 | if (name, code) not in self._foundclasses: 70 | self.classes.append((name, code)) 71 | self._foundclasses.add((name, code)) 72 | self._foundclasscodes.add(code) 73 | 74 | def add_enumerator(self, code, name, description): 75 | if (name, code) not in self._foundenumerators: 76 | self.enumerators.append((self.convert(name), code)) 77 | self._foundenumerators.add((name, code)) 78 | 79 | def result(self): 80 | # singular names are normally used in the classes table and plural names in the elements table. However, if an aete defines a singular name but not a plural name then the missing plural name is substituted with the singular name; and vice-versa if there's no singular equivalent for a plural name. 81 | missingElements = self._foundclasscodes - self._foundelementcodes 82 | missingClasses = self._foundelementcodes - self._foundclasscodes 83 | for code in missingElements: 84 | self.elements.append((self._spareclassnames[code], code)) 85 | for code in missingClasses: 86 | self.classes.append((self._spareclassnames[code], code)) 87 | return (self.classes, self.enumerators, self.properties, self.elements, list(self.commands.values())) 88 | 89 | 90 | ###################################################################### 91 | # PUBLIC 92 | ###################################################################### 93 | 94 | def buildtablesforaetes(aetes, style='py-appscript'): 95 | """ 96 | aetes : list of AEDesc 97 | style : str 98 | """ 99 | receiver = TerminologyTableReceiver(style) 100 | parse(aetes, receiver) 101 | return receiver.result() 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/renderers/typerenderers.py: -------------------------------------------------------------------------------- 1 | """typerenderers""" 2 | 3 | from osaterminology.dom.osadictionary import kAll, Nodes 4 | 5 | from codecs import getencoder 6 | 7 | strtohex = getencoder('hex_codec') 8 | 9 | 10 | ###################################################################### 11 | 12 | 13 | class TypeRendererBase: 14 | 15 | def __init__(self): 16 | self._renderedtypes = {} # cache 17 | 18 | def render(self, types, sep=' | '): # TO DO: rename (typeorenum? typename?) 19 | # oldvis = types.setvisibility(kAll) # TO DO: find right place for these calls 20 | res = [] 21 | if not isinstance(types, (list, Nodes)): 22 | types = [types] 23 | for otype in types: 24 | type = otype.realvalue() # TO DO: this might throw up weird results if there's a mix (not that there should be if the dictionary is properly designed...) 25 | if type.code: 26 | if type.code not in self._renderedtypes: 27 | self._renderedtypes[type.code] = self._render(type) 28 | s = self._renderedtypes[type.code] 29 | else: 30 | s = type.name 31 | if otype.islist: 32 | s = 'list of '+s 33 | res.append(s) 34 | # types.setvisibility(oldvis) 35 | return sep.join(res) 36 | 37 | 38 | ###################################################################### 39 | 40 | 41 | class AppscriptTypeRenderer(TypeRendererBase): 42 | 43 | def _render(self, type): 44 | if type.kind == 'enumeration': 45 | return ' / '.join([e.name and self._keyword % e.name or self._enum % self.escapecode(e.code) 46 | for e in type.enumerators()]) 47 | else: 48 | return type.name or self._type % self.escapecode(type.code) 49 | 50 | def escapecode(self, s): # TO DO: how is s arg represented? 51 | # format non-ASCII characters as '\x00' hex values for readability (also backslash and single and double quotes) 52 | if isinstance(s, bytes): s = str(s, 'macroman') 53 | res = '' 54 | for c in s: 55 | n = ord(c) 56 | if 31 < n < 128 and c not in '\\\'"': 57 | res += c 58 | else: 59 | res += '\\x%02x' % n 60 | return res 61 | 62 | def elementname(self, type): # appscript uses plural names for elements 63 | type = type.realvalue() 64 | return getattr(type, 'pluralname', type.name) or self._render(type) 65 | 66 | 67 | class PyAppscriptTypeRenderer(AppscriptTypeRenderer): 68 | _type = 'AEType(%r)' 69 | _enum = 'AEEnum(%r)' 70 | _keyword = 'k.%s' 71 | 72 | 73 | class RbAppscriptTypeRenderer(AppscriptTypeRenderer): 74 | _type = 'AEType.new("%s")' 75 | _enum = 'AEEnum.new("%s")' 76 | _keyword = ':%s' 77 | 78 | 79 | class NodeAutomationTypeRenderer(TypeRendererBase): 80 | _type = 'k.fromTypeCode(%s)' 81 | _enum = 'k.fromEnumCode(%s)' 82 | _keyword = 'k.%s' 83 | 84 | def _render(self, type): 85 | if type.kind == 'enumeration': 86 | return ' / '.join([e.name and self._keyword % e.name or self._enum % self.escapecode(e.code) 87 | for e in type.enumerators()]) 88 | else: 89 | return type.name or self._type % self.escapecode(type.code) 90 | 91 | def escapecode(self, s): 92 | if isinstance(s, bytes): s = str(s, 'macroman') 93 | res = '' 94 | for c in s: 95 | n = ord(c) 96 | if 31 < n < 128 and c not in '\\\'"': 97 | res += c 98 | else: 99 | n = 0 100 | for c in s[::-1]: 101 | n *= 256 102 | n += ord(c) 103 | return '0x{:08x}'.format(n) 104 | return "'#{}'".format(res) 105 | 106 | def elementname(self, type): # appscript uses plural names for elements 107 | type = type.realvalue() 108 | return getattr(type, 'pluralname', type.name) or self._render(type) 109 | 110 | 111 | ###################################################################### 112 | 113 | 114 | class ApplescriptTypeRenderer(TypeRendererBase): 115 | 116 | def _render(self, type): 117 | if type.kind == 'enumeration': 118 | return ' / '.join([e.name or '' % self.escapecode(e.code) 119 | for e in type.enumerators()]) 120 | else: 121 | return type.name or '' % self.escapecode(type.code) 122 | 123 | def escapecode(self, s): 124 | return str(s, 'macroman') 125 | 126 | def elementname(self, type): # AppleScript uses singular names for elements 127 | return self._render(type.realvalue()) 128 | 129 | 130 | ###################################################################### 131 | 132 | typerenderers = { 133 | 'applescript': ApplescriptTypeRenderer, 134 | 'appscript': PyAppscriptTypeRenderer, 135 | 'py-appscript': PyAppscriptTypeRenderer, 136 | 'rb-scpt': RbAppscriptTypeRenderer, 137 | 'nodeautomation': NodeAutomationTypeRenderer, 138 | } 139 | 140 | ###################################################################### 141 | 142 | 143 | def gettyperenderer(name): 144 | try: 145 | return typerenderers[name]() 146 | except KeyError: 147 | raise KeyError("Couldn't find a type renderer named %r." % name) 148 | 149 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/12_commandexamples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | 12. Command examples 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 28 | 29 | 30 |
31 | 32 |

12. Command examples

33 | 34 |

get

35 | 36 |

Get the name of every folder in the user's home folder:

37 | 38 |
# tell application "Finder" to get name of every folder of home
 39 | 
 40 | app('Finder').get(app.home.folders.name)
41 | 42 |

Note that if the direct parameter is omitted from the parameter list, the reference that the command is invoked on is used instead. For example, the above example would normally be written as:

43 | 44 |
app('Finder').home.folders.name.get()
45 | 46 |

Also note that the .get portion can be omitted for convenience:

47 | 48 |
app('Finder').home.folders.name()
49 | 50 | 51 |

set

52 | 53 |

Set the content of a TextEdit document:

54 | 55 |
# tell application "TextEdit" to set text of document 1 to "Hello World"
 56 | 
 57 | app('TextEdit').documents[1].text.set('Hello World')
58 | 59 | 60 |

count

61 | 62 |

Count the words in a TextEdit document:

63 | 64 |
# tell application "TextEdit" to count words of document 1
 65 | 
 66 | app('TextEdit').documents[1].words.count()
67 | 68 |

Count the items in the current user's home folder:

69 | 70 |
#tell application "Finder" to count items of home
 71 | 
 72 | app('Finder').home.count(each=k.item)
73 | 74 |

(Note that the each parameter is required in Finder's count command.)

75 | 76 | 77 |

make

78 | 79 |

Create a new TextEdit document:

80 | 81 |
# tell application "TextEdit" to make new document ¬
 82 | #     with properties {text:"Hello World\n"}
 83 | 
 84 | app('TextEdit').make(
 85 |     new=k.document,
 86 |     with_properties={k.text:'Hello World\n'})
87 | 88 |

Append text to a TextEdit document:

89 | 90 |
# tell application "TextEdit" to make new paragraph ¬
 91 | #     at end of text of document 1 ¬
 92 | #     with properties {text:"Yesterday\nToday\nTomorrow\n"}
 93 | 
 94 | app('TextEdit').make(
 95 |     new=k.paragraph,
 96 |     at=app.documents[1].text.end,
 97 |     with_data='Yesterday\nToday\nTomorrow\n')
98 | 99 |

Note that the make command's at parameter can be omitted for convenience, in which case the reference that the command is invoked on is used instead:

100 | 101 |
app('TextEdit').documents[1].text.end.make(
102 |     new=k.paragraph,
103 |     with_data='Yesterday\nToday\nTomorrow\n')
104 | 105 | 106 |

duplicate

107 | 108 |

Duplicate a folder to a disk, replacing an existing item if one exists:

109 | 110 |
# tell application "Finder" to ¬
111 | #     duplicate folder "Projects" of home to disk "Work" with replacing
112 | 
113 | app('Finder').home.folders['Projects'].duplicate(
114 |     to=app.disks['Work'], replacing=True)
115 | 116 | 117 |

add

118 | 119 |

Add every person with a known birthday to a group named "Birthdays":

120 | 121 |
# tell application "Contacts" to add ¬
122 | #     every person whose birth date is not missing value ¬
123 | #     to group "Birthdays"
124 | 
125 | app('Contacts').people[
126 |     its.birth_date != k.missing_value
127 |     ].add(to=app.groups['Birthdays'])
128 | 129 | 130 | 131 |
132 | 133 | 134 | 135 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /ASDictionary/src/dictionaryexporter.py: -------------------------------------------------------------------------------- 1 | """ dictionaryexporter -- Dictionary export function and corresponding Apple event handler. """ 2 | 3 | import os 4 | 5 | from AppKit import * 6 | 7 | from aem import * 8 | from aemreceive import * 9 | from osaterminology import makeidentifier 10 | from osaterminology.dom import aeteparser, sdefparser 11 | from osaterminology.renderers import htmldoc, htmldoc2 12 | 13 | 14 | ###################################################################### 15 | # PRIVATE 16 | ###################################################################### 17 | 18 | 19 | kStyleToSuffix = { 20 | 'applescript': '-AS', 21 | 'py-appscript': '-py', 22 | 'rb-scpt': '-rb', 23 | 'nodeautomation': '-node', 24 | } 25 | 26 | 27 | def _makeDestinationFolder(outFolder, styleSubfolderName, formatSubfolderName, fileName): 28 | destFolder = os.path.join(outFolder, styleSubfolderName, formatSubfolderName or '') 29 | if not os.path.exists(destFolder): 30 | os.makedirs(destFolder) 31 | return os.path.join(destFolder, fileName) 32 | 33 | 34 | ###################################################################### 35 | # define handler for 'export dictionaries' events # TO DO: junk this? 36 | 37 | kSingleHTML = b'SHTM' 38 | kFrameHTML = b'FHTM' 39 | kASStyle = b'AScr' 40 | kPyStyle = b'PyAp' 41 | kRbStyle = b'RbAp' 42 | kNodeStyle = b'NoAp' 43 | 44 | kAECodeToStyle = { 45 | kASStyle: 'applescript', 46 | kPyStyle: 'py-appscript', 47 | kRbStyle: 'rb-scpt', 48 | kNodeStyle: 'nodeautomation', 49 | } 50 | 51 | class AEProgress: 52 | 53 | kClassKey = AEType(b'pcls') 54 | kClassValue = AEType(b'ExpR') 55 | #kNameKey = AEType(b'pnam') 56 | kSuccessKey = AEType(b'Succ') 57 | kSourceKey = AEType(b'Sour') 58 | kDestKey = AEType(b'Dest') 59 | kErrorKey = AEType(b'ErrS') 60 | kMissingValue = AEType(b'msng') 61 | 62 | def __init__(self, itemcount, stylecount, formatcount, controller): 63 | self._results = [] 64 | 65 | def shouldcontinue(self): 66 | return True 67 | 68 | def nextitem(self, name, inpath): 69 | self._results.append({ 70 | self.kClassKey:self.kClassValue, 71 | #self.kNameKey:name, 72 | self.kSourceKey:mactypes.File(inpath)}) 73 | 74 | def nextoutput(self, outpath): 75 | self._results[-1][self.kDestKey] = mactypes.File(outpath) 76 | 77 | def didsucceed(self): 78 | self._results[-1][self.kSuccessKey] = True 79 | self._results[-1][self.kErrorKey] = self.kMissingValue 80 | 81 | def didfail(self, errormessage): 82 | self._results[-1][self.kSuccessKey] = False 83 | self._results[-1][self.kDestKey] = self.kMissingValue 84 | self._results[-1][self.kErrorKey] = errormessage 85 | 86 | def didfinish(self): 87 | return self._results 88 | 89 | 90 | ###################################################################### 91 | # PUBLIC 92 | ###################################################################### 93 | 94 | 95 | def export(items, styles, singleHTML, frameHTML, options, outFolder, exportToSubfolders, progress): 96 | styleInfo = [(style, kStyleToSuffix[style]) for style in styles] 97 | # process each item 98 | for i, item in enumerate(items): 99 | sdef = aetes = None 100 | name, path = item['name'], item['path'] 101 | if path == NSBundle.mainBundle().bundlePath(): 102 | continue 103 | progress.nextitem(name, path) 104 | try: 105 | sdef = ae.scriptingdefinitionfromurl(ae.convertpathtourl(path, 0)) 106 | except Exception as e: 107 | progress.didfail("Can't get terminology for application (%r): %s" % (path, e)) 108 | continue 109 | try: 110 | if not sdef: 111 | progress.didfail("No terminology found.") 112 | continue 113 | for style, suffix in styleInfo: 114 | styleSubfolderName = exportToSubfolders and style or '' 115 | if not progress.shouldcontinue(): 116 | for item in items[i:]: 117 | progress.didfail("User cancelled.") 118 | progress.nextapp(item['name'], item['path']) 119 | progress.didfail("User cancelled.") 120 | progress.didfinish() 121 | return 122 | if singleHTML or frameHTML: 123 | terms = sdefparser.parsexml(sdef, path, style) 124 | if singleHTML: 125 | outputPath = _makeDestinationFolder(outFolder, styleSubfolderName, 126 | exportToSubfolders and 'html', name + suffix + '.html') 127 | progress.nextoutput('%s' % outputPath) 128 | html = htmldoc.renderdictionary(terms, style, options) 129 | with open(outputPath, 'w', encoding='utf-8') as f: 130 | f.write(str(html)) 131 | if frameHTML: 132 | outputPath = _makeDestinationFolder(outFolder, styleSubfolderName, 133 | exportToSubfolders and 'frame-html', name + suffix) 134 | progress.nextoutput('%s' % outputPath) 135 | htmldoc2.renderdictionary(terms, outputPath, style, options) 136 | except Exception as err: 137 | from traceback import format_exc 138 | progress.didfail('Unexpected error:/n%s' % format_exc()) 139 | else: 140 | progress.didsucceed() 141 | return progress.didfinish() 142 | 143 | -------------------------------------------------------------------------------- /py-appscript/test/test_aemreference.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import aem 5 | 6 | DefaultCodecs = aem.Codecs() 7 | 8 | 9 | class TC_AEMReferences(unittest.TestCase): 10 | 11 | def test_referenceForms(self): 12 | for val, res, unpackedVersion in [ 13 | [aem.app.property(b"ctxt"), "app.property(b'ctxt')", None], 14 | 15 | [aem.app.elements(b"docu"), "app.elements(b'docu')", None], 16 | 17 | [aem.app.elements(b"docu").byindex(1), 18 | "app.elements(b'docu').byindex(1)", None], 19 | [aem.app.elements(b"docu").byname("foo"), 20 | "app.elements(b'docu').byname('foo')", None], 21 | [aem.app.elements(b"docu").byid(300), 22 | "app.elements(b'docu').byid(300)", None], 23 | 24 | [aem.app.elements(b"docu").next(b"docu"), 25 | "app.elements(b'docu').next(b'docu')", None], 26 | [aem.app.elements(b"docu").previous(b"docu"), 27 | "app.elements(b'docu').previous(b'docu')", None], 28 | 29 | [aem.app.elements(b"docu").first, "app.elements(b'docu').first", None], 30 | [aem.app.elements(b"docu").middle, "app.elements(b'docu').middle", None], 31 | [aem.app.elements(b"docu").last, "app.elements(b'docu').last", None], 32 | [aem.app.elements(b"docu").any, "app.elements(b'docu').any", None], 33 | 34 | [aem.con.elements(b'docu').byindex(3), "con.elements(b'docu').byindex(3)", None], 35 | 36 | [aem.app.elements(b"docu").byrange( 37 | aem.con.elements(b"docu").byindex(3), 38 | aem.con.elements(b"docu").byname("foo")), 39 | "app.elements(b'docu').byrange(" + 40 | "con.elements(b'docu').byindex(3), " + 41 | "con.elements(b'docu').byname('foo'))", None], 42 | 43 | [aem.its.property(b"name").eq("foo").AND(aem.its.elements(b"cwor").eq([])), 44 | "its.property(b'name').eq('foo').AND(its.elements(b'cwor').eq([]))", None], 45 | 46 | [aem.its.elements(b"cwor").ne([]), 47 | "its.elements(b'cwor').ne([])", 48 | aem.its.elements(b'cwor').eq([]).NOT], # i.e. there isn"t a kAENotEqual operator, so not-equal tests are actually packed as an equal test followed by not test 49 | 50 | [aem.its.elements(b"cwor").eq(None), "its.elements(b'cwor').eq(None)", None], 51 | [aem.its.elements(b"cwor").property(b"leng").gt(0), 52 | "its.elements(b'cwor').property(b'leng').gt(0)", None], 53 | [aem.its.elements(b"cwor").le(""), "its.elements(b'cwor').le('')", None], 54 | [aem.its.elements(b"cwor").beginswith("foo").NOT, 55 | "its.elements(b'cwor').beginswith('foo').NOT", None], 56 | 57 | 58 | [aem.its.elements(b"cwor").contains("foo"), "its.elements(b'cwor').contains('foo')", None], 59 | [aem.its.elements(b"cwor").isin("foo"), "its.elements(b'cwor').isin('foo')", None], 60 | 61 | [aem.app.elements(b"docu").byfilter(aem.its.property(b"size").ge(42)), 62 | "app.elements(b'docu').byfilter(its.property(b'size').ge(42))", None], 63 | 64 | [aem.app.elements(b"docu").byindex(1).property(b"ctxt") \ 65 | .elements(b"cpar").elements(b"cha ").byrange( 66 | aem.con.elements(b"cha ").byindex(3), 67 | aem.con.elements(b"cha ").byindex(55) 68 | ).next(b"cha ").after, 69 | "app.elements(b'docu').byindex(1).property(b'ctxt').elements(b'cpar').elements(b'cha ')" + 70 | ".byrange(con.elements(b'cha ').byindex(3), con.elements(b'cha ').byindex(55))" + 71 | ".next(b'cha ').after", None], 72 | 73 | [aem.its.property(b"pnam").ne("foo").AND(aem.its.elements(b"cfol").eq([])).NOT, 74 | "its.property(b'pnam').ne('foo').AND(its.elements(b'cfol').eq([])).NOT", 75 | aem.its.property(b"pnam").eq("foo").NOT.AND(aem.its.elements(b"cfol").eq([])).NOT], 76 | 77 | [aem.app.elements(b"docu").beginning, "app.elements(b'docu').beginning", None], 78 | [aem.app.elements(b"docu").end, "app.elements(b'docu').end", None], 79 | [aem.app.elements(b"docu").byindex(3).before, "app.elements(b'docu').byindex(3).before", None], 80 | [aem.app.elements(b"docu").byname("foo").after, "app.elements(b'docu').byname('foo').after", None], 81 | 82 | ]: 83 | self.assertEqual(res, str(val)) 84 | d = DefaultCodecs.pack(val) 85 | val = unpackedVersion and unpackedVersion or val 86 | #print val, d 87 | val2 = DefaultCodecs.unpack(d) 88 | self.assertEqual(val, val2) 89 | val2 = DefaultCodecs.unpack(d) 90 | self.assertEqual(val2, val) 91 | self.assertNotEqual(aem.app.property(b'ctxt').property(b'ctxt'), aem.con.property(b'ctxt').property(b'ctxt')) 92 | self.assertNotEqual(aem.app.property(b'foob').property(b'ctxt'), aem.app.property(b'ctxt').property(b'ctxt')) 93 | self.assertNotEqual(aem.app.elements(b'ctxt').property(b'ctxt'), aem.app.property(b'ctxt').property(b'ctxt')) 94 | self.assertNotEqual(aem.app.elements(b'ctxt').property(b'ctxt'), 333) 95 | self.assertNotEqual(333, aem.app.property(b'ctxt').property(b'ctxt')) 96 | # by-filter references do basic type checking to ensure a reference is given 97 | self.assertRaises(TypeError, aem.app.elements(b'docu').byfilter, 1) 98 | 99 | 100 | 101 | 102 | if __name__ == '__main__': 103 | unittest.main() 104 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/renderers/inheritance.py: -------------------------------------------------------------------------------- 1 | """inheritance""" 2 | 3 | from sys import stdout 4 | 5 | ###################################################################### 6 | # PRIVATE 7 | ###################################################################### 8 | 9 | class _Out: 10 | # Default target for writing help text; prints to stdout. 11 | def write(self, s): 12 | stdout.write( s.encode('utf8', 'replace')) 13 | 14 | 15 | ###################################################################### 16 | # PUBLIC 17 | ###################################################################### 18 | 19 | class TextRenderer: 20 | def __init__(self, out=_Out()): 21 | self._output = out 22 | self._indent = '' 23 | self._pad = False 24 | 25 | def add(self, name, islast, hilite=False, tbc=False): 26 | if self._pad: 27 | print(' ' + self._indent, file=self._output) # insert a line of extra padding after a subtree 28 | self._pad = False 29 | print(' %s %s-%s%s' % (hilite and '->' or ' ', self._indent, name, tbc and ' ->' or ''), file=self._output) 30 | if islast and self._indent: 31 | self._indent = self._indent[:-4] + ' ' 32 | 33 | def down(self): 34 | self._indent += ' |' 35 | 36 | def up(self): 37 | self._indent = self._indent[:-4] 38 | self._pad = True 39 | 40 | 41 | ## 42 | 43 | class InheritanceGrapher: 44 | # Provides help for the specified application 45 | 46 | def __init__(self, terms, renderer): 47 | self.terms = terms 48 | self.renderer = renderer 49 | self.childrenByClassName = self._findChildren() 50 | 51 | 52 | def _findChildren(self): 53 | # returns a dict; keys = class names, values = subclass names for each class 54 | classes = self.terms.classes() 55 | childrenByClassName = dict([(name, []) for name in classes.names()]) 56 | for klass in classes: 57 | for superclass in klass.parents(): 58 | try: 59 | lst = childrenByClassName[superclass.name] 60 | except KeyError: # ignore bad superclasses (e.g. Mail 2.0.7, iCal 2.0.3 wrongly specify AEM types, not application classes) 61 | continue 62 | if klass.name not in lst and klass.name != superclass.name: 63 | lst.append(klass.name) 64 | return childrenByClassName 65 | 66 | def _findParents(self, klass, children, result): 67 | # get list(s) of superclass names for given class 68 | # (some classes may have multiple parents, hence the result is a list of zero or more lists) 69 | # (note: last item in each sublist is name of the given class) 70 | if klass.parents(): 71 | for parentClass in klass.parents(): 72 | lst = children[:] 73 | if parentClass.name not in lst: 74 | lst.insert(0, parentClass.name) 75 | self._findParents(parentClass, lst, result) 76 | else: 77 | result.append(children) 78 | 79 | 80 | def _renderSubclasses(self, names, visited): 81 | # render specified class's subclasses 82 | # (remembers already-visited classes to avoid infinite recursion caused by 83 | # circular inheritance in some apps' dictionaries, e.g. MS Word X) 84 | for i, name in enumerate(names): 85 | islast = i+1 == len(names) 86 | self.renderer.add(name, islast, tbc=name in visited and self.childrenByClassName[name]) 87 | if name not in visited: 88 | visited.append(name) 89 | if self.childrenByClassName.get(name): 90 | self.renderer.down() 91 | self._renderSubclasses(self.childrenByClassName[name], visited) 92 | self.renderer.up() 93 | 94 | ## 95 | 96 | def draw(self, classname=None): 97 | # draw the inheritance tree(s) for a given class, or else for the entire application 98 | # note: this is complicated by the need to represent multiple inheritance 99 | # (mi is shown by rendering all except one tree in truncated form, avoiding duplication) 100 | # note that multiple classes of the same name are collapsed into one for neatness 101 | if classname: 102 | thisClass = self.terms.classes().byname(classname).collapse() 103 | parentLists = [] 104 | self._findParents(thisClass, [thisClass.name], parentLists) 105 | for i, lst in enumerate(parentLists): 106 | # render all superclass trees 107 | for name in lst[:-1]: # render all parents in a single superclass tree 108 | self.renderer.add(name, True) 109 | self.renderer.down() 110 | # TO DO: decide if should just show all trees in full 111 | if not i: # first superclass tree, so render subclasses as well 112 | self.renderer.add(thisClass.name, True, True) # render classname 113 | self.renderer.down() 114 | self._renderSubclasses(self.childrenByClassName[classname], []) 115 | self.renderer.up() 116 | else: # for remaining trees, don't bother showing subclasses for space 117 | self.renderer.add(thisClass.name, True, False, True) # render classname, marking it as 'to be continued' 118 | for _ in lst[:-1]: 119 | self.renderer.up() 120 | else: # print full tree 121 | for klass in self.terms.classes(): 122 | if not [o for o in klass.parents() if o.kind == 'class']: # is it a root class? (ignores invalid superclasses, e.g. Mail 2.0.7) 123 | self._renderSubclasses([klass.name], []) 124 | 125 | -------------------------------------------------------------------------------- /py-appscript/doc/appscript-manual/14_notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | py-appscript manual | 14. Notes 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

py-appscript

14 | 15 | 16 | 27 | 28 | 29 |
30 | 31 |

14. Notes

32 | 33 |

Security issues

34 | 35 |

System Integrity Protection

36 | 37 |

In macOS 10.14+, System Integrity Protection helps to protect users from malicious AppleScripts by blocking (most) Apple events until and unless the user explicitly grants permission for one application to control another:

38 | 39 | Permission to Automate dialog in macOS 10.14 40 | 41 |

Be aware that these permissions apply at the GUI application level, not to specific python (or osascript) scripts or processes, so once granted for one script will apply to all scripts subsequently run by the same application. Standard commands (Apple events) recognized by all Mac applications (run, open, quit, etc.) are not affected.

42 | 43 |

The Automation controls in the Security & Privacy panel's Privacy tab in System Preferences may be used to alter an application's existing Automation (Apple event) permissions at any time:

44 | 45 | Automation permission preferences in macOS 10.14 46 | 47 |

To delete all existing Automation permissions in Terminal:

48 | 49 |
tccutil reset AppleEvents
50 | 51 |

(See also: AppleScript-related changes in macOS 10.14.)

52 | 53 | 54 |

GUI Scripting

55 | 56 |

Non-scriptable applications with macOS Accessibility support may be controlled from Python by using the System Events application to manipulate their graphical interfaces directly. In macOS 10.9 and later, the user will be prompted to allow/deny the current application access to Accessibility features upon first use.

57 | 58 |

The Accessibility controls in the Security & Privacy panel's Privacy tab in System Preferences may be used to add or remove an application's Accessibility permissions at any time:

59 | 60 | Accessibility permission preferences in macOS 10.14 61 | 62 |

Older Mac OS versions had less granular security model, allowing any application to use GUI Scripting once the "Enable access for assistive devices" checkbox in the Universal Access panel of System Preferences was enabled.

63 | 64 | 65 |

Remote Apple Events

66 | 67 |

If including user names and/or passwords in remote application URLs (not recommended), please note that appscript retains these URLs in both the terminology module's internal cache and in the app objects created with those URLs. Security here is the user's responsibility, as it's their code that creates and retains these objects.

68 | 69 | 70 |

AEDescs

71 | 72 |

Some applications (e.g. QuarkXpress) may return values which appscript cannot convert to equivalent Python types. These values are usually of types which are defined, used and understood only by that particular application, and will be represented in Python as raw aem.ae.AEDesc objects. While there's not much you can do with raw AEDesc objects within Python (it's best just to treat them as opaque types), subsequent commands can pass them back to the application for further use and/or conversion just like any other value.

73 | 74 | 75 |

Event loops

76 | 77 |

Python-based applications that use a Carbon/Cocoa event loop can import the appscript module as normal, but should not use it before the event loop has started as sending Apple events outwith the main event loop can disrupt the process's normal event handling.

78 | 79 | 80 |

Credits

81 | 82 |

Many thanks to Philip Aker, Ben Artin, Jack Jansen, Jordan K Hubbard, Bob Ippolito, Thomas Loredo, Jacob Kaplan-Moss, Georges Martin, Nick Matsakis, Larry McMunn, Chris Nandor, Chris Nebel, Matt Neuburg, Ronald Oussoren, David Person, Donovan Preston, Jon Pugh, Jay Soffian, Clayton Wheeler, Kevin Van Vechten, and all the appscript users who've provided comments, criticisms and encouragement along the way. Special thanks to those who've donated to the development and upkeep of this project.

83 | 84 | 85 |

Website

86 | 87 |

http://appscript.sourceforge.net

88 | 89 |
90 | 91 | 92 | 93 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /py-appscript/test/test_codecs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest, struct, datetime 4 | import aem, mactypes 5 | 6 | isSmallEndian = struct.pack('H', 1) == b"\001\000" 7 | 8 | def num(s): 9 | if isSmallEndian: 10 | return s[::-1] 11 | else: 12 | return s 13 | 14 | def ut16(s): 15 | if isSmallEndian: 16 | return str(s, 'utf-16be').encode('utf-16le') 17 | else: 18 | return s 19 | 20 | 21 | 22 | 23 | class TC_Codecs(unittest.TestCase): 24 | 25 | def setUp(self): 26 | self.c = aem.Codecs() 27 | 28 | def test_none(self): 29 | d = self.c.pack(None) 30 | self.assertEqual(aem.kae.typeNull, d.type) 31 | self.assertEqual(b'', d.data) 32 | self.assertEqual(None, self.c.unpack(d)) 33 | 34 | def test_bool(self): 35 | self.assertEqual(True, self.c.unpack(aem.ae.newdesc(aem.kae.typeBoolean, b"\xfe"))) 36 | self.assertEqual(False, self.c.unpack(aem.ae.newdesc(aem.kae.typeBoolean, b"\x00"))) 37 | self.assertEqual(True, self.c.unpack(aem.ae.newdesc(aem.kae.typeTrue, b''))) 38 | self.assertEqual(False, self.c.unpack(aem.ae.newdesc(aem.kae.typeFalse, b''))) 39 | 40 | def test_num(self): 41 | for val, data, type, expected in [ # (mostly testing at threshold points where Codecs switches types when packing integers) 42 | [0, b"\x00\x00\x00\x00", aem.kae.typeInteger, None], 43 | [2, b"\x00\x00\x00\x02", aem.kae.typeInteger, None], 44 | [-9, b"\xff\xff\xff\xf7", aem.kae.typeInteger, None], 45 | [2**31-1, b"\x7f\xff\xff\xff", aem.kae.typeInteger, None], 46 | [-2**31, b"\x80\x00\x00\x00", aem.kae.typeInteger, None], 47 | [2**31, b"\x00\x00\x00\x00\x80\x00\x00\x00", aem.kae.typeSInt64, None], 48 | [2**32-1, b"\x00\x00\x00\x00\xff\xff\xff\xff", aem.kae.typeSInt64, None], 49 | [2**32, b"\x00\x00\x00\x01\x00\x00\x00\x00", aem.kae.typeSInt64, None], 50 | [-2**32, b"\xff\xff\xff\xff\x00\x00\x00\x00", aem.kae.typeSInt64, None], 51 | [2**63-1, b"\x7f\xff\xff\xff\xff\xff\xff\xff", aem.kae.typeSInt64, None], 52 | [-2**63, b"\x80\x00\x00\x00\x00\x00\x00\x00", aem.kae.typeSInt64, None], 53 | [-2**63+1, b"\x80\x00\x00\x00\x00\x00\x00\x01", aem.kae.typeSInt64, None], 54 | [2**63, b"C\xe0\x00\x00\x00\x00\x00\x00", aem.kae.typeFloat, None], 55 | [-2**63-1, b"\xc3\xe0\x00\x00\x00\x00\x00\x00", aem.kae.typeFloat, float(-2**63-1)], 56 | [0.1, b"?\xb9\x99\x99\x99\x99\x99\x9a", aem.kae.typeFloat, None], 57 | [-0.9e-9, b"\xbe\x0e\xec{\xd5\x12\xb5r", aem.kae.typeFloat, None], 58 | [2**300, b"R\xb0\x00\x00\x00\x00\x00\x00", aem.kae.typeFloat, None], 59 | ]: 60 | if type == aem.kae.typeInteger: 61 | val = int(val) 62 | data = num(data) 63 | d = self.c.pack(val) 64 | self.assertEqual(type, d.type) 65 | self.assertEqual(data, d.data) 66 | if expected is None: 67 | expected = val 68 | self.assertEqual(expected, self.c.unpack(d)) 69 | 70 | def test_unicode(self): 71 | for val, data in [ 72 | # note: UTF16 BOM must be omitted when packing UTF16 data into typeUnicodeText AEDescs, as a BOM will upset stupid apps like iTunes 7 that don't recognise it as a BOM and treat it as character data instead 73 | ['', b''], 74 | ['hello', b"\000h\000e\000l\000l\000o"], 75 | [str(b"\xc6\x92\xe2\x88\x82\xc2\xae\xd4\xb7\xd5\x96\xd4\xb9\xe0\xa8\x89\xe3\x82\xa2\xe3\x84\xbb", 'utf8'), 76 | b"\x01\x92\"\x02\x00\xae\x057\x05V\x059\n\t0\xa21;"], 77 | ]: 78 | data = ut16(data) 79 | d = self.c.pack(val) 80 | self.assertEqual(aem.kae.typeUnicodeText, d.type) 81 | self.assertEqual(data, d.data) 82 | self.assertEqual(val, self.c.unpack(d)) 83 | 84 | def test_date(self): 85 | # note: not testing on ST-DST boundaries; this is known to have out-by-an-hour problems due to LongDateTime type being crap 86 | for t, data in [ 87 | [datetime.datetime(2005, 12, 11, 15, 40, 43), b"\x00\x00\x00\x00\xbf\xc1\xf8\xfb"], 88 | [datetime.datetime(2005, 5, 1, 6, 51, 7), b"\x00\x00\x00\x00\xbe\x9a\x2c\xdb"], 89 | ]: 90 | data = num(data) 91 | d = self.c.pack(t) 92 | self.assertEqual(aem.kae.typeLongDateTime, d.type) 93 | self.assertEqual(data, d.data) 94 | self.assertEqual(t, self.c.unpack(aem.ae.newdesc(aem.kae.typeLongDateTime, data))) 95 | 96 | def test_file(self): 97 | path = '/System/Applications/TextEdit.app' 98 | d = self.c.pack(mactypes.Alias(path)) 99 | self.assertEqual(path, self.c.unpack(d).path) 100 | 101 | path = '/System/Applications/TextEdit.app' 102 | d = self.c.pack(mactypes.File(path)) 103 | self.assertEqual(path, self.c.unpack(d).path) 104 | 105 | def test_typewrappers(self): 106 | for val in [ 107 | aem.AEType(b"docu"), 108 | aem.AEEnum(b'yes '), 109 | aem.AEProp(b'pnam'), 110 | aem.AEKey(b'ABCD'), 111 | ]: 112 | d = self.c.pack(val) 113 | val2 = self.c.unpack(d) 114 | self.assertEqual(val, val2) 115 | self.assertEqual(val2, val) 116 | self.assertRaises(TypeError, aem.AEType, 3) 117 | self.assertRaises(ValueError, aem.AEType, b"docum") 118 | 119 | def test_dict(self): 120 | val = {'foo': 1, aem.AEType(b'foob'): 2, aem.AEProp(b'barr'): 3} 121 | expectedVal = {'foo': 1, aem.AEType(b'foob'): 2, aem.AEType(b'barr'): 3} # note that four-char-code keys are always unpacked as AEType 122 | d = self.c.pack(val) 123 | self.assertEqual(expectedVal, self.c.unpack(d)) 124 | 125 | def test_units(self): 126 | val = mactypes.Units(3.3, 'inches') 127 | self.assertEqual('inches', val.type) 128 | self.assertEqual(3.3, val.value) 129 | d = self.c.pack(val) 130 | self.assertEqual(b'inch', d.type) 131 | self.assertEqual(3.3, self.c.unpack(d.coerce(aem.kae.typeFloat))) 132 | val2 = self.c.unpack(d) 133 | self.assertEqual(val, val2) 134 | self.assertEqual('inches', val2.type) 135 | self.assertEqual(3.3, val2.value) 136 | 137 | 138 | if __name__ == '__main__': 139 | unittest.main() 140 | -------------------------------------------------------------------------------- /py-appscript/test/test_appscriptcommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest, subprocess 4 | import appscript, aem, mactypes 5 | 6 | class TC_appscriptNewApp(unittest.TestCase): 7 | 8 | def test_by_name(self): 9 | for name in [ 10 | '/System/Applications/TextEdit.app', 11 | 'Finder.app', 12 | 'System Events' 13 | ]: 14 | a = appscript.app(name) 15 | self.assertNotEqual(None, a) 16 | self.assertEqual(appscript.reference.Application, a.__class__) 17 | self.assertEqual(appscript.reference.Reference, a.name.__class__) 18 | 19 | self.assertEqual("app('/System/Applications/TextEdit.app')", str(appscript.app('TextEdit'))) 20 | self.assertEqual("app('/System/Applications/TextEdit.app')", str(appscript.app(name='TextEdit'))) 21 | 22 | self.assertRaises(appscript.ApplicationNotFoundError, appscript.app, '/non-existent/app') 23 | self.assertRaises(appscript.ApplicationNotFoundError, appscript.app, 'non-existent.app') 24 | 25 | 26 | def test_by_id(self): 27 | for name in [ 28 | 'com.apple.textedit', 29 | 'com.apple.finder', 30 | ]: 31 | a = appscript.app(id=name) 32 | self.assertNotEqual(None, a) 33 | self.assertEqual(appscript.reference.Application, a.__class__) 34 | self.assertEqual(appscript.reference.Reference, a.name.__class__) 35 | 36 | self.assertEqual("app('/System/Applications/TextEdit.app')", str(appscript.app(id='com.apple.textedit'))) 37 | 38 | self.assertRaises(appscript.ApplicationNotFoundError, appscript.app, id='non.existent.app') 39 | 40 | 41 | def test_by_pid(self): 42 | p = subprocess.Popen("top -l1 | grep ' Finder ' | awk '{ print $1 }'", 43 | shell=True, stdout=subprocess.PIPE, close_fds=True) 44 | out, err = p.communicate() 45 | pid = int(out) 46 | a = appscript.app(pid=pid) 47 | self.assertEqual(appscript.reference.Application, a.__class__) 48 | self.assertEqual(appscript.reference.Reference, a.name.__class__) 49 | self.assertEqual("app(pid=%i)" % pid, str(a)) 50 | self.assertEqual('Finder', a.name.get()) 51 | 52 | 53 | def test_by_aem_app(self): 54 | a = appscript.app(aemapp=aem.Application('/System/Applications/TextEdit.app')) 55 | self.assertEqual(appscript.reference.Application, a.__class__) 56 | self.assertEqual(appscript.reference.Reference, a.name.__class__) 57 | self.assertEqual("app(aemapp=aem.Application('/System/Applications/TextEdit.app'))", str(a)) 58 | 59 | 60 | 61 | 62 | class TC_appscriptCommands(unittest.TestCase): 63 | 64 | def setUp(self): 65 | self.te = appscript.app('TextEdit') 66 | self.f = appscript.app('Finder') 67 | 68 | 69 | def test_commands_1(self): 70 | self.assertEqual('TextEdit', self.te.name.get()) 71 | d = self.te.make(new=appscript.k.document, at=appscript.app.documents.end, 72 | with_properties={appscript.k.text:'test test_commands'}) 73 | self.assertEqual(appscript.reference.Reference, d.__class__) 74 | d.text.end.make(new=appscript.k.word, with_data=' test2') 75 | self.assertEqual('test test_commands test2', d.text.get()) 76 | self.assertEqual(str, 77 | type(d.text.get(ignore=[appscript.k.diacriticals, appscript.k.punctuation, 78 | appscript.k.whitespace, appscript.k.expansion], timeout=10))) 79 | self.assertEqual(None, d.get(waitreply=False)) 80 | 81 | d.text.set('\xa9 M. Lef\xe8vre') 82 | self.assertEqual('\xa9 M. Lef\xe8vre', d.text.get()) 83 | 84 | d.close(saving=appscript.k.no) 85 | 86 | 87 | def test_commands_2(self): 88 | d = self.te.make(new=appscript.k.document, at=self.te.documents.end) 89 | 90 | self.te.set(d.text, to= 'test1') 91 | self.assertEqual('test1', d.text.get()) 92 | 93 | self.te.set(d.text, to= 'test2') 94 | self.te.make(new=appscript.k.word, at=appscript.app.documents[1].paragraphs.end, with_data=' test3') 95 | self.assertEqual('test2 test3', d.text.get()) 96 | 97 | d.close(saving=appscript.k.no) 98 | 99 | self.assertRaises(appscript.CommandError, self.te.documents[10000].get) 100 | 101 | self.assertEqual(int, type(self.te.documents.count())) 102 | self.assertEqual(self.te.documents.count(), self.te.count(each=appscript.k.document)) 103 | 104 | 105 | def test_commands_3(self): 106 | self.assertEqual('Finder', self.f.name.get()) 107 | val = self.f.home.folders['Desktop'].get(resulttype=appscript.k.alias) 108 | self.assertEqual(mactypes.Alias, val.__class__) 109 | self.assertEqual(val, self.f.desktop.get(resulttype=appscript.k.alias)) 110 | self.assertEqual(list, type(self.f.disks.get())) 111 | 112 | r = self.f.home.get() 113 | f = r.get(resulttype=appscript.k.file_ref) 114 | self.assertEqual(r, self.f.items[f].get()) 115 | 116 | self.assertEqual(self.f.home.items.get(), self.f.home.items.get()) 117 | self.assertNotEqual(self.f.disks['non-existent'], self.f.disks[1].get()) 118 | 119 | 120 | def test_command_error(self): 121 | try: 122 | self.f.items[10000].get() 123 | except appscript.CommandError as e: 124 | self.assertEqual(-1728, int(e)) 125 | s = [ 126 | "Command failed:\n\t\tOSERROR: -1728\n\t\tMESSAGE: Can't get reference.\n\t\t" 127 | "OFFENDING OBJECT: 10000\n\t\tEXPECTED TYPE: k.file_url\n\t\t" 128 | "COMMAND: app('/System/Library/CoreServices/Finder.app').items[10000].get()", # 10.6 129 | "Command failed:\n\t\tOSERROR: -1728\n\t\tMESSAGE: Can't get reference.\n\t\t" 130 | "OFFENDING OBJECT: app('/System/Library/CoreServices/Finder.app').items[10000]\n\t\t" 131 | "COMMAND: app('/System/Library/CoreServices/Finder.app').items[10000].get()", # 10.5 132 | "Command failed:\n\t\tOSERROR: -1728\n\t\tMESSAGE: Can't get reference.\n\t\t" 133 | "COMMAND: app('/System/Library/CoreServices/Finder.app').items[10000].get()" # 10.3-4 134 | ] 135 | self.assertTrue(str(e) in s, '%s not in %s' % (repr(str(e)), s)) 136 | self.assertEqual(aem.EventError, e.realerror.__class__) 137 | 138 | 139 | 140 | 141 | if __name__ == '__main__': 142 | unittest.main() 143 | 144 | -------------------------------------------------------------------------------- /py-osaterminology/lib/osaterminology/defaultterminology/nodeautomation.py: -------------------------------------------------------------------------------- 1 | 2 | types = [ 3 | # Human-readable names for commonly used AE types. 4 | # Most of these names are equivalent to AS names, though 5 | # a few are adjusted to be more 'programmer friendly', 6 | # e.g. 'float' instead of 'real', and a few have no AS equivalent, 7 | # e.g. 'UTF8Text' 8 | ('anything', b'****'), 9 | 10 | ('boolean', b'bool'), 11 | 12 | ('shortInteger', b'shor'), 13 | ('integer', b'long'), 14 | ('unsignedInteger', b'magn'), 15 | ('doubleInteger', b'comp'), 16 | ("unsignedShortInteger", b'ushr'), # no AS keyword 17 | ("unsignedInteger", b'magn'), 18 | ("unsignedDoubleInteger", b'ucom'), # no AS keyword 19 | 20 | ('fixed', b'fixd'), 21 | ('longFixed', b'lfxd'), 22 | ('decimalStruct', b'decm'), 23 | 24 | ('smallReal', b'sing'), 25 | ('real', b'doub'), 26 | # ('extendedReal', b'exte'), 27 | ('largeReal', b'ldbl'), 28 | 29 | ('string', b'TEXT'), 30 | ('UnicodeText', b'utxt'), 31 | ('UTF8Text', b'utf8'), # typeUTF8Text 32 | ('UTF16Text', b'ut16'), # typeUTF16ExternalRepresentation 33 | 34 | ('version', b'vers'), 35 | ('date', b'ldt '), 36 | ('list', b'list'), 37 | ('record', b'reco'), 38 | ('data', b'rdat'), 39 | ('script', b'scpt'), 40 | 41 | ('locationReference', b'insl'), 42 | ('reference', b'obj '), 43 | 44 | ('alias', b'alis'), 45 | ('fileRef', b'fsrf'), 46 | # ('fileSpecification', b'fss '), 47 | ('bookmarkData', b'bmrk'), 48 | ('fileURL', b'furl'), 49 | 50 | ('point', b'QDpt'), 51 | ('boundingRectangle', b'qdrt'), 52 | ('fixedPoint', b'fpnt'), 53 | ('fixedRectangle', b'frct'), 54 | ('longPoint', b'lpnt'), 55 | ('longRectangle', b'lrct'), 56 | ('longFixedPoint', b'lfpt'), 57 | ('longFixedRectangle', b'lfrc'), 58 | 59 | ('EPSPicture', b'EPS '), 60 | ('GIFPicture', b'GIFf'), 61 | ('JPEGPicture', b'JPEG'), 62 | ('PICTPicture', b'PICT'), 63 | ('TIFFPicture', b'TIFF'), 64 | ('RGBColor', b'cRGB'), 65 | ('RGB16Color', b'tr16'), 66 | ('RGB96Color', b'tr96'), 67 | ('graphicText', b'cgtx'), 68 | ('colorTable', b'clrt'), 69 | ('pixelMapRecord', b'tpmm'), 70 | 71 | ('best', b'best'), 72 | ('typeClass', b'type'), 73 | ('constant', b'enum'), 74 | ('property', b'prop'), 75 | 76 | # AEAddressDesc types 77 | 78 | ('machPort', b'port'), 79 | ('kernelProcessID', b'kpid'), 80 | ('applicationBundleID', b'bund'), 81 | ('processSerialNumber', b'psn '), 82 | ('applicationSignature', b'sign'), 83 | ('applicationURL', b'aprl'), 84 | 85 | # misc. 86 | 87 | # ('missingValue', b'msng'), 88 | 89 | ('null', b'null'), 90 | 91 | ('machineLocation', b'mLoc'), 92 | ('machine', b'mach'), 93 | 94 | ('dashStyle', b'tdas'), 95 | ('rotation', b'trot'), 96 | 97 | ('item', b'cobj'), 98 | 99 | # month and weekday 100 | 101 | ('January', b'jan '), 102 | ('February', b'feb '), 103 | ('March', b'mar '), 104 | ('April', b'apr '), 105 | ('May', b'may '), 106 | ('June', b'jun '), 107 | ('July', b'jul '), 108 | ('August', b'aug '), 109 | ('September', b'sep '), 110 | ('October', b'oct '), 111 | ('November', b'nov '), 112 | ('December', b'dec '), 113 | 114 | ('Sunday', b'sun '), 115 | ('Monday', b'mon '), 116 | ('Tuesday', b'tue '), 117 | ('Wednesday', b'wed '), 118 | ('Thursday', b'thu '), 119 | ('Friday', b'fri '), 120 | ('Saturday', b'sat '), 121 | ] 122 | 123 | 124 | pseudotypes = [ # non-concrete types that are only used for documentation purposes; use to remap typesbycode 125 | ('file', b'file'), # typically FileURL, but could be other file types as well 126 | ('number', b'nmbr'), # any numerical type: Integer, Float, Long 127 | # ('text', b'ctxt'), # Word X, Excel X uses 'ctxt' instead of 'TEXT' or 'utxt' (TO CHECK: is this Excel's stupidity, or is it acceptable?) 128 | ] 129 | 130 | 131 | properties = [ 132 | ('class', b'pcls'), # used as a key in AERecord structures that have a custom class; also some apps (e.g. Jaguar Finder) may omit it from their dictionaries despite using it 133 | ('properties', b'pALL'), 134 | ('id', b'ID '), # some apps (e.g. iTunes) may omit 'id' property from terminology despite using it 135 | ] 136 | 137 | 138 | elements = [ 139 | ('items', b'cobj'), 140 | ('text', b'ctxt'), 141 | ] 142 | 143 | 144 | enumerations = [ 145 | ('savo', [ 146 | ('yes', b'yes '), 147 | ('no', b'no '), 148 | ('ask', b'ask '), 149 | ]), 150 | # constants used in commands' 'ignore' argument (note: most apps currently ignore these): 151 | ('cons', [ 152 | ('case', b'case'), 153 | ('diacriticals', b'diac'), 154 | ('expansion', b'expa'), 155 | ('punctuation', b'punc'), 156 | ('hyphens', b'hyph'), 157 | ('whitespace', b'whit'), 158 | ('numericStrings', b'nume'), 159 | ]), 160 | ] 161 | 162 | 163 | commands = [ 164 | # required suite 165 | ('run', b'aevtoapp', []), 166 | ('open', b'aevtodoc', []), 167 | ('print', b'aevtpdoc', []), 168 | ('quit', b'aevtquit', [('saving', b'savo')]), 169 | # 'reopen' and 'activate' aren't listed in required suite, but should be 170 | ('reopen', b'aevtrapp', []), 171 | ('activate', b'miscactv', []), 172 | # 'launch' is a special case not listed in the required suite and implementation is provided by 173 | # the Apple event bridge (not the target applications), which uses the Process Manager/ 174 | # LaunchServices to launch an application without sending it the usual run/open event. 175 | ('launch', b'ascrnoop', []), 176 | # 'get' and 'set' commands are often omitted from applications' core suites, even when used 177 | ('get', b'coregetd', []), 178 | ('set', b'coresetd', [('to', b'data')]), 179 | # some apps (e.g. Safari) which support GetURL events may omit it from their terminology; 180 | # 'open location' is the name Standard Additions defines for this event, so use it here 181 | ('openLocation', b'GURLGURL', [('window', b'WIND')]), 182 | ] 183 | --------------------------------------------------------------------------------