Task.swift 5.73 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

15 16
import atfoundation

17 18 19 20 21 22 23 24 25 26
final public class Task {
    ///The unqualified name of the task, not including its package name
    public let unqualifiedName: String

    ///The qualified name of the task, including its package name
    public var qualifiedName: String {
        return package.name + "." + unqualifiedName
    }

    ///The package for the task
27
    public let package: Package
28 29 30

    public var dependencies: [String] = []
    public var tool: String
31
    public var importedPath: Path /// the directory at which the task was imported
32 33

    var overlay: [String] = [] ///The overlays we should apply to this task
Drew's avatar
Drew committed
34
    internal(set) public var appliedOverlays: [String] = [] ///The overlays we did apply to this task
35 36

    var declaredOverlays: [String: [String: ParseValue]] = [:] ///The overlays this task declares
37

38
    public var allKeys: [String]
39

40 41
    private var kvp: [String:ParseValue]

Drew's avatar
Drew committed
42 43
    public var onlyPlatforms: [String] = []

Drew's avatar
Drew committed
44 45 46 47 48
    public enum Option: String {
        case Tool = "tool"
        case UseOverlays = "use-overlays"
        case Overlays = "overlays"
        case Dependencies = "dependencies"
Drew's avatar
Drew committed
49
        case OnlyPlatforms = "only-platforms"
Drew's avatar
Drew committed
50 51 52 53 54 55

        public static var allOptions: [Option] {
            return [
                    Tool,
                    UseOverlays,
                    Overlays,
Drew's avatar
Drew committed
56 57
                    Dependencies,
                    OnlyPlatforms
Drew's avatar
Drew committed
58 59 60 61
            ]
        }
    }

62
    init?(value: ParseValue, unqualifiedName: String, package: Package, importedPath: Path) {
63 64
        precondition(!unqualifiedName.characters.contains("."), "Task \(unqualifiedName) may not contain a period.")
        guard let kvp = value.map else { return nil }
65
        self.importedPath = importedPath
66 67 68 69 70
        self.kvp = kvp
        self.unqualifiedName = unqualifiedName
        self.package = package
        self.allKeys = [String](kvp.keys)

Drew's avatar
Drew committed
71
        guard let tool = kvp[Option.Tool.rawValue]?.string else {
72 73 74 75 76
            self.tool = "invalid"
            fatalError("No tool for task \(qualifiedName); did you forget to specify it?")
        }
        self.tool = tool

Drew's avatar
Drew committed
77
        if let ol = kvp[Option.UseOverlays.rawValue] {
78
            guard let overlays = ol.vector else {
Drew's avatar
Drew committed
79
                fatalError("Non-vector \(Option.UseOverlays.rawValue) \(ol); did you mean to use `\(Option.Overlays.rawValue)` instead?")
80 81 82 83 84 85 86 87
            }
            for overlay in overlays {
                guard let str = overlay.string else {
                    fatalError("Non-string overlay \(overlay)")
                }
                self.overlay.append(str)
            }
        }
Drew's avatar
Drew committed
88
        if let ol = kvp[Option.Overlays.rawValue] {
89
            guard let overlays = ol.map else {
Drew's avatar
Drew committed
90
                fatalError("Non-map \(Option.Overlays.rawValue) \(ol); did you mean to use `\(Option.UseOverlays.rawValue)` instead?")
91 92 93 94 95 96 97 98 99 100
            }
            for (name, overlay) in overlays {

                guard let innerOverlay = overlay.map else {
                    fatalError("non-map overlay \(overlay)")
                }
                self.declaredOverlays[name] = innerOverlay
            }
        }

Drew's avatar
Drew committed
101
        if let values = kvp[Option.Dependencies.rawValue]?.vector {
102 103 104 105
            for value in values {
                if let dep = value.string { self.dependencies.append(dep) }
            }
        }
Drew's avatar
Drew committed
106 107 108 109 110 111 112 113

        if let onlyPlatforms = kvp[Option.OnlyPlatforms.rawValue] {
            guard let o = onlyPlatforms.vector else { fatalError("Non-vector \(Option.OnlyPlatforms.rawValue) \(onlyPlatforms)")}
            for o in o {
                guard case .StringLiteral(let platform) = o else { fatalError("Non-string \(Option.OnlyPlatforms.rawValue) \(o)")}
                self.onlyPlatforms.append(platform)
            }
        }
114
    }
115

116 117 118 119 120 121 122 123
    public subscript(key: String) -> ParseValue? {
        get {
            return kvp[key]
        }
        set {
            kvp[key] = newValue
        }
    }
Drew's avatar
Drew committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

    ///Checks that the required overlays in the task are present. We
    ///allow the parsing of a file that is missing some overlays since
    ///those overlays may not be required for the present task.
    public func checkRequiredOverlays() throws {
            if let requiredOverlays_v = self["required-overlays"] {
                guard let requiredOverlays = requiredOverlays_v.vector else {
                    fatalError("Non-vector \(requiredOverlays_v)")
                }
                nextSet: for overlaySet_v in requiredOverlays {
                    guard let overlaySet = overlaySet_v.vector else {
                        fatalError("Non-vector \(overlaySet_v)")
                    }
                    for overlay_s in overlaySet {
                        guard let overlay = overlay_s.string else {
                            fatalError("Non-string \(overlay_s)")
                        }
                        if self.appliedOverlays.contains(overlay) { continue nextSet }
                    }
                    print("Task \(self.qualifiedName) requires at least one of \(overlaySet.map() {$0.string}) but it was not applied.  Applied overlays: \(self.appliedOverlays)")
                    throw PackageError.RequiredOverlayNotPresent(overlaySet.map() {$0.string!})
                }
            }
    }
148
}