在 Mac 上,如何为 python 脚本创建一个拖放应用程序,其中脚本名称而不是 Python 显示在菜单栏(等)中?
Posted
技术标签:
【中文标题】在 Mac 上,如何为 python 脚本创建一个拖放应用程序,其中脚本名称而不是 Python 显示在菜单栏(等)中?【英文标题】:On Mac, how to create a drag & drop app for a python script where the script name rather than Python shows in the MenuBar (etc.)? 【发布时间】:2013-07-03 00:29:58 【问题描述】:我有一个 Python 脚本,我想通过单击某个东西或拖放一个我想在该东西上打开的文件来启动它。我还想避免令人讨厌的 mac Python 问题,即解释器 (Python) 的名称而不是脚本的名称。我不想使用 py2app,因为我不想捆绑 Python 并希望 Python 文件保留为文本文件。我想要一个纯 Python(或最好的 shell 脚本解决方案。)This 展示了如何做所有事情,但拖放部分; this 展示了如何通过创建一个捆绑包来做同样的事情,它允许人们控制图标。
【问题讨论】:
【参考方案1】:我的问题有答案,但我希望有人能想出一些不那么复杂的东西。我想出的解决方案是一个 Python 脚本来创建一个 AppleScript(啊!),将其编译成一个包,然后修改包以更改允许的文件扩展名、图标等。
在没有 AppleScript(或使用 py2app)的情况下启动 wxpython 脚本时,我没有找到让拖放工作的方法,而且我无法让 AppleScript 在我创建的包中工作,我不得不使用osacompile 为我创建包。
我更喜欢here 或here 的方法,其中“手动”创建plist 文件而不是像我在这里那样修改外部创建的文件,但下面的脚本确实可以完成这项工作。
#!/usr/bin/env python
'''This script creates an AppleScript app to launch a python script.
The app is created in the current working directory. A softlink is
made to an app version of the current python (with name
.../Resources/Python.app/Contents/MacOS/Python and found in the
directory tree of calling python interpreter), but with the name of
the app to be created, this means that the app name shows up in the
Mac menus.
Run this script with one, two or three arguments:
<python script>
<project name>
<icon file>
The python script path may be specified relative to the current path
or given an absolute path, but will be accessed via an absolute path
in the AppleScript.
If the project name is not specified, it will be taken from the root
name of the script.
If the icon file is not specified, it will be assumed to be the same
as the python script, but with the extension changed from .py to
.icns. (Note that image files can be saved in ICNS format in
Preview.). No error occurs if the icon file does not exist.
'''
import sys, os, os.path, stat, shutil, subprocess, plistlib
allowedfiletypes = ['txt',]
allowedtypedesc = 'Text Files'
scripttype = 'Editor' # use 'Viewer' if the script does not change the file
def Usage():
print("\n\tUsage: python "+sys.argv[0]+" <python script> [<project name>] [<icon file>]\n")
sys.exit()
if not 2 <= len(sys.argv) <= 4:
Usage()
script = os.path.abspath(sys.argv[1])
if not os.path.exists(script):
print("\nFile "+script+" not found")
Usage()
if os.path.splitext(script)[1].lower() != '.py':
print("\nScript "+script+" does not have extension .py")
Usage()
# make sure we found it
if not os.path.exists(script):
print("\nFile "+script+" not found")
Usage()
if len(sys.argv) >= 3:
project = sys.argv[2]
else:
project = os.path.splitext(os.path.split(script)[1])[0]
if len(sys.argv) == 4:
iconfile = sys.argv[3]
else:
iconfile = os.path.splitext(script)[0]+'.icns'
# app will be created in current working directory
apppath = os.path.abspath(os.path.join('.',project+".app")) # full path to app bundle
# find the python application; which must be an OS X app
pythonpath,top = os.path.split(os.path.realpath(sys.executable))
while top:
if 'Resources' in pythonpath:
pass
elif os.path.exists(os.path.join(pythonpath,'Resources')):
break
pythonpath,top = os.path.split(pythonpath)
else:
print("\nSorry, failed to find a Resources directory associated with "+str(sys.executable))
sys.exit()
pythonapp = os.path.join(pythonpath,'Resources','Python.app','Contents','MacOS','Python')
if not os.path.exists(pythonapp):
print("\nSorry, failed to find a Python app in "+str(pythonapp))
sys.exit()
# new name to call python
newpython = os.path.join(apppath,"Contents","MacOS",project)
if os.path.exists(apppath): # cleanup
print("\nRemoving old "+project+" app ("+str(apppath)+")")
shutil.rmtree(apppath)
# create an AppleScript that launches python with the requested app
shell = os.path.join("/tmp/","appscrpt.script")
f = open(shell, "w")
f.write('''(* drag and drop AppleScript
It can launch a python script by double clicking or by dropping a data file
over the app. It runs the script in a terminal window.
*)
(* test if a file is present and exit with an error message if it is not *)
on TestFilePresent(appwithpath)
tell application "System Events"
if (file appwithpath exists) then
else
display dialog "Error: file " & appwithpath & " not found. If you have moved this file, recreate the AppleScript." with icon caution buttons "Quit"
return
end if
end tell
end TestFilePresent
(*
------------------------------------------------------------------------
this section responds to a double-click. No file is supplied
------------------------------------------------------------------------
*)
on run
set python to ":s"
set appwithpath to ":s"
TestFilePresent(appwithpath)
TestFilePresent(python)
tell application "Terminal"
activate
do script python & " " & appwithpath & "; exit"
end tell
end run
(*
-----------------------------------------------------------------------------------------------
this section handles starting with files dragged into the AppleScript
o it goes through the list of file(s) dragged in
o then it converts the colon-delimited macintosh file location to a POSIX filename
o for every non-directory file dragged into the icon, it starts GSAS-II, passing the file name
------------------------------------------------------------------------------------------------
*)
on open names
set python to ":s"
set appwithpath to ":s"
TestFilePresent(appwithpath)
repeat with filename in names
set filestr to (filename as string)
if filestr ends with ":" then
(* should not happen, skip directories *)
else
(* if this is an input file, open it *)
set filename to the quoted form of the POSIX path of filename
tell application "Terminal"
activate
do script python & " " & appwithpath & " " & filename & "; exit"
end tell
end if
end repeat
end open
'''.format(newpython,script,newpython,script))
f.close()
try:
subprocess.check_output(["osacompile","-o",apppath,shell],stderr=subprocess.STDOUT)
except subprocess.CalledProcessError, msg:
print('Error compiling AppleScript:')
print msg.output
sys.exit()
# create a link to the python app, but named to match the project
os.symlink(pythonapp,newpython)
# change the icon
oldicon = os.path.join(apppath,"Contents","Resources","droplet.icns")
if os.path.exists(iconfile) and os.path.exists(oldicon):
shutil.copyfile(iconfile,oldicon)
# Edit the app plist file to restrict the type of files that can be dropped
d = plistlib.readPlist(os.path.join(apppath,"Contents",'Info.plist'))
d['CFBundleDocumentTypes'] = [
'CFBundleTypeExtensions': allowedfiletypes,
'CFBundleTypeName': allowedtypedesc,
'CFBundleTypeRole': scripttype]
plistlib.writePlist(d,os.path.join(apppath,"Contents",'Info.plist'))
print("\nCreated "+project+" app ("+str(apppath)+
").\nViewing app in Finder so you can drag it to the dock if, you wish.")
subprocess.call(["open","-R",apppath])
有没有人在不使用 py2app 的情况下有更好的方法来做到这一点?
【讨论】:
【参考方案2】:可以通过覆盖 wx.App 对象的 MacOpenFiles 方法来处理应用程序图标上的文件的 DnD。
在 lib/python2.7/plat-mac 中的 Python 库中有一个名为 bundlebuilder.py 的脚本,可用于帮助您构建应用程序包。它来自 OSX 的早期版本,在某些方面可能已经过时,但在我上次尝试时它仍然有效。
【讨论】:
我对此有点困惑。当我最初尝试测试脚本时,我认为覆盖 MacOpenFiles 会做我想要的,但在我的项目中实现它之后,MacOpenFiles 似乎只响应应用程序已经运行时删除的文件。以上是关于在 Mac 上,如何为 python 脚本创建一个拖放应用程序,其中脚本名称而不是 Python 显示在菜单栏(等)中?的主要内容,如果未能解决你的问题,请参考以下文章