有没有办法让 Swift 脚本使用多个文件
Is there a way to have a Swift script use multiple files
我正在尝试使用 Swift(不是 Xcode 项目)编写脚本。明确地说,我的文件的第一行是
#!/usr/bin/swift
我只是从命令行调用它。
但是,我不知道如何让该脚本使用另一个 .swift 文件中的代码。它不会从同一个目录中获取它,也无法到达我能看到的 import
。
支持吗?
我目前的解决方案是一个简单的 shell 脚本,它将所有文件连接成一个并执行连接的文件:
TMPFILE=`mktemp /tmp/Project.swift.XXXXXX` || exit 1
trap "rm -f $TMPFILE" EXIT
cat *.swift > $TMPFILE
swift $TMPFILE
我使用了 Marián 解决方案的变体:
cat A.swift B.swift main.swift | swift -
有更好的方法!
#!/usr/bin/swift -frontend -interpret -enable-source-import -I.
import other_file // this imports other_file.swift in the same folder
funcFromOtherFile()
如果你想从 ExampleFolder
导入文件,它就像:
#!/usr/bin/swift -frontend -interpret -enable-source-import -I./ExampleFolder
import other_file // this imports ./ExampleFolder/other_file.swift
funcFromOtherFile()
灵感来自 , I've written a simple Python script to replicate a gcc
-esque compilation process. You can find the gist here。
核心思想是 (1) 将所有 Swift 文件合并为一个,(2) 添加一些样板代码(如果适用),然后 (3) 将合并后的文本写入临时文件并具有 Swift 执行那个文件。
#!/usr/bin/env python3
"""
Simple bootstrap script for quickly prototyping
Swift-based macOS applications.
usage: swift-run.py [-h] [-c SWIFT_COMPILER] [-a ARGS] [-m MAIN] [-s SAVE]
file [file ...]
positional arguments:
file list of files to run
optional arguments:
-h, --help show this help message and exit
-c SWIFT_COMPILER, --swift-compiler SWIFT_COMPILER
swift compiler path (default: swift)
-a ARGS, --args ARGS swift compiler args (default: )
-m MAIN, --main MAIN main SwiftUI view (default:
ContentView().frame(maxWidth: .infinity, maxHeight:
.infinity))
-s SAVE, --save SAVE saves the joined swift files + boilerplate to this
file (default: )
"""
import argparse
import os
import subprocess
import tempfile
from typing import List
DEFAULTS = {
"main_view": "ContentView().frame(maxWidth: .infinity, maxHeight: .infinity)",
"swift_args": "",
"swift_exec": "swift",
}
def join_files(files: List[str], boilerplate: str = ""):
all_text = ""
for file in files:
with open(file, "r") as f:
all_text += f.read() + "\n\n"
all_text += boilerplate
return all_text
def exec_text(text: str, swift_exec: str = "", swift_args: str = "", script_out: str = None):
with tempfile.TemporaryDirectory() as tmp_dir:
if script_out is None or script_out.strip() == "":
script_out = os.path.join(tmp_dir, "main.swift")
with open(script_out, "w") as f:
f.write(text)
cmd = f"{swift_exec} {swift_args} {script_out}"
print(cmd)
subprocess.run(
cmd.split(),
check=True,
capture_output=True,
)
def get_boilerplate(main_view: str = "ContentView()"):
return (
"""
// Run any SwiftUI view as a Mac app.
import Cocoa
import SwiftUI
NSApplication.shared.run {
"""
+ main_view
+ """
}
extension NSApplication {
public func run<V: View>(@ViewBuilder view: () -> V) {
let appDelegate = AppDelegate(view())
NSApp.setActivationPolicy(.regular)
mainMenu = customMenu
delegate = appDelegate
run()
}
}
// Inspired by https://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
extension NSApplication {
var customMenu: NSMenu {
let appMenu = NSMenuItem()
appMenu.submenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
appMenu.submenu?.addItem(NSMenuItem(title: "About \(appName)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
let services = NSMenuItem(title: "Services", action: nil, keyEquivalent: "")
self.servicesMenu = NSMenu()
services.submenu = self.servicesMenu
appMenu.submenu?.addItem(services)
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Hide \(appName)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h"))
let hideOthers = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
hideOthers.keyEquivalentModifierMask = [.command, .option]
appMenu.submenu?.addItem(hideOthers)
appMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Quit \(appName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
let windowMenu = NSMenuItem()
windowMenu.submenu = NSMenu(title: "Window")
windowMenu.submenu?.addItem(NSMenuItem(title: "Minmize", action: #selector(NSWindow.miniaturize(_:)), keyEquivalent: "m"))
windowMenu.submenu?.addItem(NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: ""))
windowMenu.submenu?.addItem(NSMenuItem.separator())
windowMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "m"))
let mainMenu = NSMenu(title: "Main Menu")
mainMenu.addItem(appMenu)
mainMenu.addItem(windowMenu)
return mainMenu
}
}
class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate {
init(_ contentView: V) {
self.contentView = contentView
}
var window: NSWindow!
var hostingView: NSView?
var contentView: V
func applicationDidFinishLaunching(_ notification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
hostingView = NSHostingView(rootView: contentView)
window.contentView = hostingView
window.makeKeyAndOrderFront(nil)
window.delegate = self
NSApp.activate(ignoringOtherApps: true)
}
}
"""
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"files", metavar="file", type=str, nargs="+", help="list of files to run"
)
parser.add_argument(
"-c",
"--swift-compiler",
help="swift compiler path",
type=str,
default=DEFAULTS["swift_exec"],
)
parser.add_argument(
"-a",
"--args",
help="swift compiler args",
type=str,
default=DEFAULTS["swift_args"],
)
parser.add_argument(
"-m",
"--main",
help="main SwiftUI view",
type=str,
default=DEFAULTS["main_view"],
)
parser.add_argument(
"-s",
"--save",
help="saves the joined swift files + boilerplate to this file",
type=str,
default="",
)
args = parser.parse_args()
print(args)
exec_text(
join_files(args.files, boilerplate=get_boilerplate(args.main)),
swift_exec=args.swift_compiler,
swift_args=args.args,
script_out=args.save
)
我正在尝试使用 Swift(不是 Xcode 项目)编写脚本。明确地说,我的文件的第一行是
#!/usr/bin/swift
我只是从命令行调用它。
但是,我不知道如何让该脚本使用另一个 .swift 文件中的代码。它不会从同一个目录中获取它,也无法到达我能看到的 import
。
支持吗?
我目前的解决方案是一个简单的 shell 脚本,它将所有文件连接成一个并执行连接的文件:
TMPFILE=`mktemp /tmp/Project.swift.XXXXXX` || exit 1
trap "rm -f $TMPFILE" EXIT
cat *.swift > $TMPFILE
swift $TMPFILE
我使用了 Marián 解决方案的变体:
cat A.swift B.swift main.swift | swift -
有更好的方法!
#!/usr/bin/swift -frontend -interpret -enable-source-import -I.
import other_file // this imports other_file.swift in the same folder
funcFromOtherFile()
如果你想从 ExampleFolder
导入文件,它就像:
#!/usr/bin/swift -frontend -interpret -enable-source-import -I./ExampleFolder
import other_file // this imports ./ExampleFolder/other_file.swift
funcFromOtherFile()
灵感来自 gcc
-esque compilation process. You can find the gist here。
核心思想是 (1) 将所有 Swift 文件合并为一个,(2) 添加一些样板代码(如果适用),然后 (3) 将合并后的文本写入临时文件并具有 Swift 执行那个文件。
#!/usr/bin/env python3
"""
Simple bootstrap script for quickly prototyping
Swift-based macOS applications.
usage: swift-run.py [-h] [-c SWIFT_COMPILER] [-a ARGS] [-m MAIN] [-s SAVE]
file [file ...]
positional arguments:
file list of files to run
optional arguments:
-h, --help show this help message and exit
-c SWIFT_COMPILER, --swift-compiler SWIFT_COMPILER
swift compiler path (default: swift)
-a ARGS, --args ARGS swift compiler args (default: )
-m MAIN, --main MAIN main SwiftUI view (default:
ContentView().frame(maxWidth: .infinity, maxHeight:
.infinity))
-s SAVE, --save SAVE saves the joined swift files + boilerplate to this
file (default: )
"""
import argparse
import os
import subprocess
import tempfile
from typing import List
DEFAULTS = {
"main_view": "ContentView().frame(maxWidth: .infinity, maxHeight: .infinity)",
"swift_args": "",
"swift_exec": "swift",
}
def join_files(files: List[str], boilerplate: str = ""):
all_text = ""
for file in files:
with open(file, "r") as f:
all_text += f.read() + "\n\n"
all_text += boilerplate
return all_text
def exec_text(text: str, swift_exec: str = "", swift_args: str = "", script_out: str = None):
with tempfile.TemporaryDirectory() as tmp_dir:
if script_out is None or script_out.strip() == "":
script_out = os.path.join(tmp_dir, "main.swift")
with open(script_out, "w") as f:
f.write(text)
cmd = f"{swift_exec} {swift_args} {script_out}"
print(cmd)
subprocess.run(
cmd.split(),
check=True,
capture_output=True,
)
def get_boilerplate(main_view: str = "ContentView()"):
return (
"""
// Run any SwiftUI view as a Mac app.
import Cocoa
import SwiftUI
NSApplication.shared.run {
"""
+ main_view
+ """
}
extension NSApplication {
public func run<V: View>(@ViewBuilder view: () -> V) {
let appDelegate = AppDelegate(view())
NSApp.setActivationPolicy(.regular)
mainMenu = customMenu
delegate = appDelegate
run()
}
}
// Inspired by https://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
extension NSApplication {
var customMenu: NSMenu {
let appMenu = NSMenuItem()
appMenu.submenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
appMenu.submenu?.addItem(NSMenuItem(title: "About \(appName)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
let services = NSMenuItem(title: "Services", action: nil, keyEquivalent: "")
self.servicesMenu = NSMenu()
services.submenu = self.servicesMenu
appMenu.submenu?.addItem(services)
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Hide \(appName)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h"))
let hideOthers = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
hideOthers.keyEquivalentModifierMask = [.command, .option]
appMenu.submenu?.addItem(hideOthers)
appMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Quit \(appName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
let windowMenu = NSMenuItem()
windowMenu.submenu = NSMenu(title: "Window")
windowMenu.submenu?.addItem(NSMenuItem(title: "Minmize", action: #selector(NSWindow.miniaturize(_:)), keyEquivalent: "m"))
windowMenu.submenu?.addItem(NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: ""))
windowMenu.submenu?.addItem(NSMenuItem.separator())
windowMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "m"))
let mainMenu = NSMenu(title: "Main Menu")
mainMenu.addItem(appMenu)
mainMenu.addItem(windowMenu)
return mainMenu
}
}
class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate {
init(_ contentView: V) {
self.contentView = contentView
}
var window: NSWindow!
var hostingView: NSView?
var contentView: V
func applicationDidFinishLaunching(_ notification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
hostingView = NSHostingView(rootView: contentView)
window.contentView = hostingView
window.makeKeyAndOrderFront(nil)
window.delegate = self
NSApp.activate(ignoringOtherApps: true)
}
}
"""
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"files", metavar="file", type=str, nargs="+", help="list of files to run"
)
parser.add_argument(
"-c",
"--swift-compiler",
help="swift compiler path",
type=str,
default=DEFAULTS["swift_exec"],
)
parser.add_argument(
"-a",
"--args",
help="swift compiler args",
type=str,
default=DEFAULTS["swift_args"],
)
parser.add_argument(
"-m",
"--main",
help="main SwiftUI view",
type=str,
default=DEFAULTS["main_view"],
)
parser.add_argument(
"-s",
"--save",
help="saves the joined swift files + boilerplate to this file",
type=str,
default="",
)
args = parser.parse_args()
print(args)
exec_text(
join_files(args.files, boilerplate=get_boilerplate(args.main)),
swift_exec=args.swift_compiler,
swift_args=args.args,
script_out=args.save
)