├── 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 | 
14 |
15 |
16 |
26 |
27 |
28 |
29 |
30 |
31 |
Manuals
32 |
33 |
34 | - py-appscript - high-level, user-friendly Apple event bridge
35 | - py-mactypes - extra classes representing file and unit types
36 | - py-osax - scripting addition support
37 | - py-aem - lower-level interface to Mac OS X's Apple Event Manager
38 |
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 | 
14 |
15 |
16 |
28 |
29 |
30 |
31 |
32 |
Contents
33 |
34 |
35 | - Introduction
36 | - Alias class
37 | - File class
38 | - Units class
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 | 
14 |
15 |
16 |
28 |
29 |
30 |
31 |
32 |
Contents
33 |
34 |
35 |
36 |
37 | - Introduction
38 | - Interface
39 | - Examples
40 | - Notes
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 | 
14 |
15 |
16 |
28 |
29 |
30 |
31 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
39 | - an object-oriented API for constructing Apple Event Object Model queries ("references")
40 | - data conversion between common Python and Apple event types
41 | - AEAddressDesc creation
42 | - Apple event construction and dispatch.
43 |
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 | 
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 | 
14 |
15 |
16 |
28 |
29 |
30 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |

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 |

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 |

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 |
--------------------------------------------------------------------------------