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