swift-sh
swift-sh copied to clipboard
Added support for @main attribute based scripts
Fixes #163.
Added Examples/async-main-count-lines
which is just this example from swift-argument-parser with #!/user/bin/swift sh
at the top:
#!/user/bin/swift sh
import Foundation
import ArgumentParser // apple/swift-argument-parser ~> 1.4.0
/// example borrowed from [here](https://swiftpackageindex.com/apple/swift-argument-parser/1.4.0/documentation/argumentparser/asyncparsablecommand)
@main
struct CountLines: AsyncParsableCommand {
@Argument(transform: URL.init(fileURLWithPath:))
var inputFile: URL
mutating func run() async throws {
let fileHandle = try FileHandle(forReadingFrom: inputFile)
let lineCount = try await fileHandle.bytes.lines.reduce(into: 0)
{ count, _ in count += 1 }
print(lineCount)
}
}
Added let mainStyle: ExecutableTargetMainStyle
property to Script
:
/// wheter the script has @main
public enum ExecutableTargetMainStyle {
/// script has @main, script source file cannot be named main.swift, as this would be compilation error
case mainAttribute
/// script doesn't have @main, and it's ok to have main.swift file as script source
case topLevelCode
}
To achieve successful compilation with @main
attribute needed to change swift-tools-version
to 5.5
, and swap .target
for .executableTarget
.
Also as comment mentions we can not have main.swift
as source
when we have @main
, so the resulting script source filename is changed to Root.swift
.
Also we cannot have #!
not in main.swift
so it's filtered out when Script
's mainStyle
is .mainAttribute
.
Tested this by running swift-sh
target in Xcode 15.4 and passing two input arguments which are both absolute path to added example.
Didn't test stdin
based input.
All of the aforementioned changes only happen when we found a line containing @main
and decided that Script
mainStyle
is .mainAttribute
.
@helje5 suggested to have following change to shebang line #!/usr/bin/swift sh #@main
, but i found it unnecessary, and currently check if script uses @main
like this:
/// line has `@main`, line is not a comment.
line.contains("@main") && !(line.contains("//") || line.contains("/*"))
Unfortunately linking file as was previously isn't an option when a file has @main
as only main.swift
is allowed to have shebang #!
and we need to remove that line in order for script having @main
to compile successfully. So @main
attribute and main.swift
are mutually exclusive.