]> git.r.bdr.sh - rbdr/map/blob - Map/Logic/MapParser/MapParser.swift
3.0.0
[rbdr/map] / Map / Logic / MapParser / MapParser.swift
1 import CoreGraphics
2 import Foundation
3
4 struct MapParser {
5 static func parse(content: String) -> ParsedMap {
6
7 let parsers = [
8 AnyMapParserStrategy(NoteParserStrategy()),
9 AnyMapParserStrategy(VertexParserStrategy()),
10 AnyMapParserStrategy(EdgeParserStrategy()),
11 AnyMapParserStrategy(BlockerParserStrategy()),
12 AnyMapParserStrategy(OpportunityParserStrategy()),
13 AnyMapParserStrategy(StageParserStrategy()),
14 AnyMapParserStrategy(GroupParserStrategy()),
15 ]
16 let builder = MapBuilder()
17
18 let lines = content.split(whereSeparator: \.isNewline)
19
20 for (index, line) in lines.enumerated() {
21 for parser in parsers {
22 if parser.canHandle(line: String(line)) {
23 let (type, object) = parser.handle(
24 index: index, line: String(line), vertices: builder.vertices)
25 builder.addObjectToMap(type: type, object: object)
26 break
27 }
28 }
29 }
30
31 return builder.build()
32 }
33 }
34
35 // MARK: - Types
36
37 struct MapParsingPatterns {
38 static let vertex = try! NSRegularExpression(
39 pattern:
40 "^([^\\(\\[\\]]*?)[\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(?:\\[(.*?)\\])?[\\s]*$",
41 options: [.caseInsensitive, .anchorsMatchLines])
42 static let edge = try! NSRegularExpression(
43 pattern: "^(.+?)[\\s]*-([->])[\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
44 static let blocker = try! NSRegularExpression(
45 pattern: "^\\[(Blocker)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
46 static let opportunity = try! NSRegularExpression(
47 pattern: "^\\[(Evolution)\\][\\s]*(.+)[\\s]+([-+])[\\s]*([0-9]+.?[0-9]*)",
48 options: [.caseInsensitive, .anchorsMatchLines])
49 static let note = try! NSRegularExpression(
50 pattern:
51 "^\\[(Note)\\][\\s]*\\([\\s]*([0-9]+.?[0-9]*)[\\s]*,[\\s]*([0-9]+.?[0-9]*)[\\s]*\\)[\\s]*(.*)",
52 options: [.caseInsensitive, .anchorsMatchLines])
53 static let stage = try! NSRegularExpression(
54 pattern: "^\\[(I{1,3})\\][\\s]*([0-9]+.?[0-9]*)",
55 options: [.caseInsensitive, .anchorsMatchLines])
56 static let group = try! NSRegularExpression(
57 pattern: "^\\[(Group)\\][\\s]*(.+)", options: [.caseInsensitive, .anchorsMatchLines])
58 }
59
60 struct ParsedMap {
61 let vertices: [Vertex]
62 let edges: [MapEdge]
63 let blockers: [Blocker]
64 let opportunities: [Opportunity]
65 let notes: [Note]
66 let stages: [CGFloat]
67 let groups: [[Vertex]]
68
69 static let empty: ParsedMap = ParsedMap(
70 vertices: [], edges: [], blockers: [], opportunities: [], notes: [], stages: defaultDimensions,
71 groups: [])
72 }
73
74 struct Vertex: Identifiable, Hashable {
75 let id: Int
76 let label: String
77 let position: CGPoint
78 var shape: VertexShape = .circle
79 }
80
81 struct Note {
82 let id: Int
83 let position: CGPoint
84 let text: String
85 }
86
87 enum VertexShape: String {
88 case circle
89 case square
90 case triangle
91 case x
92 }
93
94 struct MapEdge {
95 let id: Int
96 let origin: CGPoint
97 let destination: CGPoint
98 let arrowhead: Bool
99 }
100
101 struct Blocker {
102 let id: Int
103 let position: CGPoint
104 }
105
106 struct Opportunity {
107 let id: Int
108 let origin: CGPoint
109 let destination: CGPoint
110 }
111
112 struct StageDimensions {
113 let index: Int
114 let dimensions: CGFloat
115 }
116
117 private let defaultDimensions: [CGFloat] = [
118 25.0,
119 50.0,
120 75.0,
121 ]
122
123 // MARK: - MapParserStrategy protocol
124
125 protocol MapParserStrategy {
126 func canHandle(line: String) -> Bool
127 func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any)
128 }
129
130 struct AnyMapParserStrategy: MapParserStrategy {
131
132 private let base: MapParserStrategy
133
134 init<T: MapParserStrategy>(_ base: T) {
135 self.base = base
136 }
137
138 func canHandle(line: String) -> Bool {
139 return base.canHandle(line: line)
140 }
141 func handle(index: Int, line: String, vertices: [String: Vertex]) -> (Any.Type, Any) {
142 return base.handle(index: index, line: line, vertices: vertices)
143 }
144 }
145
146 // MARK: - Map Builder
147
148 class MapBuilder {
149 var vertices: [String: Vertex] = [:]
150 private var edges: [MapEdge] = []
151 private var blockers: [Blocker] = []
152 private var opportunities: [Opportunity] = []
153 private var notes: [Note] = []
154 private var stages: [CGFloat] = defaultDimensions
155 private var groups: [[Vertex]] = []
156
157 func addObjectToMap(type: Any.Type, object: Any) {
158 if type == Vertex.self {
159 let vertex = object as! Vertex
160 vertices[vertex.label] = vertex
161 }
162
163 if type == MapEdge.self {
164 let edge = object as! MapEdge
165 edges.append(edge)
166 }
167
168 if type == Blocker.self {
169 let blocker = object as! Blocker
170 blockers.append(blocker)
171 }
172
173 if type == Opportunity.self {
174 let opportunity = object as! Opportunity
175 opportunities.append(opportunity)
176 }
177
178 if type == Note.self {
179 let note = object as! Note
180 notes.append(note)
181 }
182
183 if type == StageDimensions.self {
184 let stageDimensions = object as! StageDimensions
185 stages[stageDimensions.index] = stageDimensions.dimensions
186 }
187
188 if type == [Vertex].self {
189 let group = object as! [Vertex]
190 groups.append(group)
191 }
192 }
193
194 func build() -> ParsedMap {
195 let mappedVertices = vertices.map { label, vertex in return vertex }
196 return ParsedMap(
197 vertices: mappedVertices, edges: edges, blockers: blockers, opportunities: opportunities,
198 notes: notes,
199 stages: stages, groups: groups)
200 }
201 }