]> git.r.bdr.sh - rbdr/map/blame - Map/Logic/MapParser/MapParser.swift
Add license notices
[rbdr/map] / Map / Logic / MapParser / MapParser.swift
CommitLineData
98f09799
RBR
1/*
2 Copyright (C) 2024 Rubén Beltrán del Río
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see https://map.tranquil.systems.
16 */
77d0155b
RBR
17import CoreGraphics
18import Foundation
19
e2c37ac1
RBR
20struct MapParser {
21 static func parse(content: String) -> ParsedMap {
22
23 let parsers = [
24 AnyMapParserStrategy(NoteParserStrategy()),
25 AnyMapParserStrategy(VertexParserStrategy()),
26 AnyMapParserStrategy(EdgeParserStrategy()),
27 AnyMapParserStrategy(BlockerParserStrategy()),
28 AnyMapParserStrategy(OpportunityParserStrategy()),
29 AnyMapParserStrategy(StageParserStrategy()),
30 AnyMapParserStrategy(GroupParserStrategy()),
31 ]
32 let builder = MapBuilder()
33
34 let lines = content.split(whereSeparator: \.isNewline)
35
36 for (index, line) in lines.enumerated() {
37 for parser in parsers {
38 if parser.canHandle(line: String(line)) {
39 let (type, object) = parser.handle(
40 index: index, line: String(line), vertices: builder.vertices)
41 builder.addObjectToMap(type: type, object: object)
42 break
43 }
44 }
45 }
46
47 return builder.build()
48 }
49}
50
77d0155b
RBR
51// MARK: - Types
52
53struct MapParsingPatterns {
54 static let vertex = try! NSRegularExpression(
55 pattern:
e2c37ac1
RBR
56 "^([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?[\\s]*$",
57 options: [.caseInsensitive, .anchorsMatchLines])
77d0155b 58 static let edge = try! NSRegularExpression(
e2c37ac1 59 pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
77d0155b 60 static let blocker = try! NSRegularExpression(
e2c37ac1 61 pattern: "^\\[(Blocker)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
77d0155b 62 static let opportunity = try! NSRegularExpression(
e2c37ac1
RBR
63 pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)",
64 options: [.caseInsensitive, .anchorsMatchLines])
fdb4633d 65 static let note = try! NSRegularExpression(
e2c37ac1
RBR
66 pattern:
67 "^\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)",
68 options: [.caseInsensitive, .anchorsMatchLines])
77d0155b 69 static let stage = try! NSRegularExpression(
e2c37ac1
RBR
70 pattern: "^\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)",
71 options: [.caseInsensitive, .anchorsMatchLines])
72 static let group = try! NSRegularExpression(
73 pattern: "^\\[(Group)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
77d0155b
RBR
74}
75
76struct ParsedMap {
77 let vertices: [Vertex]
78 let edges: [MapEdge]
79 let blockers: [Blocker]
80 let opportunities: [Opportunity]
fdb4633d 81 let notes: [Note]
77d0155b 82 let stages: [CGFloat]
e2c37ac1 83 let groups: [[Vertex]]
75a0e450
RBR
84
85 static let empty: ParsedMap = ParsedMap(
e2c37ac1
RBR
86 vertices: [], edges: [], blockers: [], opportunities: [], notes: [], stages: defaultDimensions,
87 groups: [])
77d0155b
RBR
88}
89
e2c37ac1 90struct Vertex: Identifiable, Hashable {
77d0155b
RBR
91 let id: Int
92 let label: String
93 let position: CGPoint
94 var shape: VertexShape = .circle
95}
96
fdb4633d
RBR
97struct Note {
98 let id: Int
99 let position: CGPoint
100 let text: String
101}
102
77d0155b
RBR
103enum VertexShape: String {
104 case circle
105 case square
106 case triangle
107 case x
108}
109
110struct MapEdge {
111 let id: Int
112 let origin: CGPoint
113 let destination: CGPoint
114 let arrowhead: Bool
115}
116
117struct Blocker {
118 let id: Int
119 let position: CGPoint
120}
121
122struct Opportunity {
123 let id: Int
124 let origin: CGPoint
125 let destination: CGPoint
126}
127
128struct StageDimensions {
129 let index: Int
130 let dimensions: CGFloat
131}
132
133private let defaultDimensions: [CGFloat] = [
134 25.0,
135 50.0,
136 75.0,
137]
138
139// MARK: - MapParserStrategy protocol
140
141protocol MapParserStrategy {
142 func canHandle(line: String) -> Bool
143 func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any)
144}
145
146struct AnyMapParserStrategy: MapParserStrategy {
147
148 private let base: MapParserStrategy
149
150 init<T: MapParserStrategy>(_ base: T) {
151 self.base = base
152 }
153
154 func canHandle(line: String) -> Bool {
155 return base.canHandle(line: line)
156 }
157 func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) {
158 return base.handle(index: index, line: line, vertices: vertices)
159 }
160}
161
162// MARK: - Map Builder
163
164class MapBuilder {
165 var vertices: [String: Vertex] = [:]
166 private var edges: [MapEdge] = []
167 private var blockers: [Blocker] = []
168 private var opportunities: [Opportunity] = []
fdb4633d 169 private var notes: [Note] = []
77d0155b 170 private var stages: [CGFloat] = defaultDimensions
e2c37ac1 171 private var groups: [[Vertex]] = []
77d0155b
RBR
172
173 func addObjectToMap(type: Any.Type, object: Any) {
174 if type == Vertex.self {
175 let vertex = object as! Vertex
176 vertices[vertex.label] = vertex
177 }
178
179 if type == MapEdge.self {
180 let edge = object as! MapEdge
181 edges.append(edge)
182 }
183
184 if type == Blocker.self {
185 let blocker = object as! Blocker
186 blockers.append(blocker)
187 }
188
189 if type == Opportunity.self {
190 let opportunity = object as! Opportunity
191 opportunities.append(opportunity)
192 }
e2c37ac1 193
fdb4633d
RBR
194 if type == Note.self {
195 let note = object as! Note
196 notes.append(note)
197 }
77d0155b
RBR
198
199 if type == StageDimensions.self {
200 let stageDimensions = object as! StageDimensions
201 stages[stageDimensions.index] = stageDimensions.dimensions
77d0155b 202 }
e2c37ac1
RBR
203
204 if type == [Vertex].self {
205 let group = object as! [Vertex]
206 groups.append(group)
207 }
77d0155b
RBR
208 }
209
210 func build() -> ParsedMap {
211 let mappedVertices = vertices.map { label, vertex in return vertex }
212 return ParsedMap(
e2c37ac1
RBR
213 vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities,
214 notes: notes,
215 stages: stages, groups: groups)
77d0155b
RBR
216 }
217}