]>
Commit | Line | Data |
---|---|---|
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 | } |