Files
Hamish Knight 03d8ea5248 Introduce swift-xcodegen
This is a tool specifically designed to generate
Xcode projects for the Swift repo (as well as a
couple of adjacent repos such as LLVM and Clang).
It aims to provide a much more user-friendly experience
than the CMake Xcode generation (`build-script --xcode`).
2024-11-05 22:42:10 +00:00

287 lines
5.9 KiB
Swift

//===--- Command.swift ----------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
struct Command: Hashable {
var executable: AnyPath
var args: [Argument]
init(executable: AnyPath, args: [Argument]) {
self.executable = executable
self.args = args
}
}
extension Command: Decodable {
init(from decoder: Decoder) throws {
let command = try decoder.singleValueContainer().decode(String.self)
self = try CommandParser.parseCommand(command)
}
}
extension Command {
var printedArgs: [String] {
[executable.rawPath.escaped] + args.flatMap(\.printedArgs)
}
var printed: String {
printedArgs.joined(separator: " ")
}
}
// MARK: Argument
extension Command {
enum Argument: Hashable {
case option(Option)
case flag(Flag)
case value(String)
}
}
extension Command.Argument {
static func option(
_ flag: Command.Flag, spacing: Command.OptionSpacing, value: String
) -> Self {
.option(.init(flag, spacing: spacing, value: value))
}
var flag: Command.Flag? {
switch self {
case .option(let opt):
opt.flag
case .flag(let flag):
flag
case .value:
nil
}
}
var value: String? {
switch self {
case .option(let opt):
opt.value
case .value(let value):
value
case .flag:
nil
}
}
var printedArgs: [String] {
switch self {
case .option(let opt):
opt.printedArgs
case .value(let value):
[value.escaped]
case .flag(let f):
[f.printed]
}
}
var printed: String {
printedArgs.joined(separator: " ")
}
func option(for flag: Command.Flag) -> Command.Option? {
switch self {
case .option(let opt) where opt.flag == flag:
opt
default:
nil
}
}
/// If there is a value, apply a transform to it.
func mapValue(_ fn: (String) throws -> String) rethrows -> Self {
switch self {
case .option(let opt):
.option(try opt.mapValue(fn))
case .value(let value):
.value(try fn(value))
case .flag:
// Nothing to map.
self
}
}
}
// MARK: Flag
extension Command {
struct Flag: Hashable {
var dash: Dash
var name: Name
}
}
extension Command.Flag {
static func dash(_ name: Name) -> Self {
.init(dash: .single, name: name)
}
static func doubleDash(_ name: Name) -> Self {
.init(dash: .double, name: name)
}
var printed: String {
"\(dash.printed)\(name.rawValue)"
}
}
extension Command.Flag {
enum Dash: Int, CaseIterable, Comparable {
case single = 1, double
static func < (lhs: Self, rhs: Self) -> Bool { lhs.rawValue < rhs.rawValue }
}
}
extension Command.Flag.Dash {
init?(numDashes: Int) {
self.init(rawValue: numDashes)
}
var printed: String {
switch self {
case .single:
return "-"
case .double:
return "--"
}
}
}
extension DefaultStringInterpolation {
mutating func appendInterpolation(_ flag: Command.Flag) {
appendInterpolation(flag.printed)
}
}
// MARK: Option
extension Command {
struct Option: Hashable {
var flag: Flag
var spacing: OptionSpacing
var value: String
init(_ flag: Flag, spacing: OptionSpacing, value: String) {
self.flag = flag
self.spacing = spacing
self.value = value
}
}
}
extension Command.Option {
func withValue(_ newValue: String) -> Self {
var result = self
result.value = newValue
return result
}
func mapValue(_ fn: (String) throws -> String) rethrows -> Self {
withValue(try fn(value))
}
var printedArgs: [String] {
switch spacing {
case .equals, .unspaced:
["\(flag)\(spacing)\(value.escaped)"]
case .spaced:
["\(flag)", value.escaped]
}
}
var printed: String {
printedArgs.joined(separator: " ")
}
}
// MARK: OptionSpacing
extension Command {
enum OptionSpacing: Comparable {
case equals, unspaced, spaced
}
}
extension Command.OptionSpacing {
var printed: String {
switch self {
case .equals: "="
case .unspaced: ""
case .spaced: " "
}
}
}
// MARK: CustomStringConvertible
extension Command.Argument: CustomStringConvertible {
var description: String { printed }
}
extension Command.OptionSpacing: CustomStringConvertible {
var description: String { printed }
}
extension Command.Flag: CustomStringConvertible {
var description: String { printed }
}
extension Command.Option: CustomStringConvertible {
var description: String { printed }
}
// MARK: Comparable
// We sort the resulting command-line arguments to ensure deterministic
// ordering.
extension Command.Flag: Comparable {
static func < (lhs: Self, rhs: Self) -> Bool {
guard lhs.dash == rhs.dash else {
return lhs.dash < rhs.dash
}
return lhs.name.rawValue < rhs.name.rawValue
}
}
extension Command.Option: Comparable {
static func < (lhs: Self, rhs: Self) -> Bool {
guard lhs.flag == rhs.flag else {
return lhs.flag < rhs.flag
}
guard lhs.spacing == rhs.spacing else {
return lhs.spacing < rhs.spacing
}
return lhs.value < rhs.value
}
}
extension Command.Argument: Comparable {
static func < (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
// Sort flags < options < values
case (.flag, .option): true
case (.flag, .value): true
case (.option, .value): true
case (.option, .flag): false
case (.value, .flag): false
case (.value, .option): false
case (.flag(let lhs), .flag(let rhs)): lhs < rhs
case (.option(let lhs), .option(let rhs)): lhs < rhs
case (.value(let lhs), .value(let rhs)): lhs < rhs
}
}
}