-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
-
445
-
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-- Copyright 2026 Shota FUJI
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
--
-- SPDX-License-Identifier: MPL-2.0
module Parameters.Parser exposing (Error(..), Errors, parse)
import Dict.Any exposing (AnyDict)
import Length exposing (Length, mm, toMM)
import Parameters exposing (ColorSchema(..), LoopStyle(..), Parameters, ParametersDict, getKey, hasKey)
import Parameters.Constraints as Constraints exposing (constraints)
import Parameters.Key as Key exposing (Key(..))
type Error
= MissingValue
| BelowMin Float
| AboveMax Float
| NotALength
| NotAnInt
| NonexistentVariant String
| UnsupportedParametersVersion String
type alias Errors =
AnyDict String Key Error
mkErrors : List ( Key, Maybe Error ) -> Errors
mkErrors list =
list
|> List.filterMap (\( key, error ) -> Maybe.map (Tuple.pair key) error)
|> Dict.Any.fromList Key.toString
unwrapErrors : Result Errors a -> Errors
unwrapErrors r =
case r of
Ok _ ->
Dict.Any.empty Key.toString
Err errs ->
errs
getError : Result a b -> Maybe a
getError r =
case r of
Err e ->
Just e
_ ->
Nothing
parseInt : Constraints.NumberConstraints Int -> String -> Result Error Int
parseInt constraints text =
case text of
"" ->
Err MissingValue
_ ->
let
min =
constraints.min
|> Maybe.map
(\m ->
Result.andThen
(\x ->
if x >= m then
Ok x
else
Err (BelowMin (toFloat m))
)
)
|> Maybe.withDefault identity
max =
constraints.max
|> Maybe.map
(\m ->
Result.andThen
(\x ->
if x <= m then
Ok x
else
Err (AboveMax (toFloat m))
)
)
|> Maybe.withDefault identity
in
String.toInt text
|> Result.fromMaybe NotAnInt
|> min
|> max
parseLength : Constraints.NumberConstraints Length -> String -> Result Error Length
parseLength constraints text =
case text of
"" ->
Err MissingValue
_ ->
let
min =
constraints.min
|> Maybe.map toMM
|> Maybe.map
(\m ->
Result.andThen
(\x ->
if toMM x >= m then
Ok x
else
Err (BelowMin m)
)
)
|> Maybe.withDefault identity
max =
constraints.max
|> Maybe.map toMM
|> Maybe.map
(\m ->
Result.andThen
(\x ->
if toMM x <= m then
Ok x
else
Err (AboveMax m)
)
)
|> Maybe.withDefault identity
in
String.toFloat text
|> Maybe.map mm
|> Result.fromMaybe NotALength
|> min
|> max
parseColorSchema : String -> Result Error ColorSchema
parseColorSchema text =
case text of
"white-on-black" ->
Ok WhiteOnBlack
"black-on-white" ->
Ok BlackOnWhite
_ ->
Err (NonexistentVariant text)
parseLoopStyle : String -> Result Error LoopStyle
parseLoopStyle text =
case text of
"simple" ->
Ok Simple
"folded" ->
Ok Folded
_ ->
Err (NonexistentVariant text)
parseField : Key -> (String -> Result Error a) -> ParametersDict -> Result Error a
parseField key f fields =
getKey key fields
|> Maybe.withDefault ""
|> f
parseOptionalField : Key -> (String -> Result Error a) -> ParametersDict -> Result Error (Maybe a)
parseOptionalField key f fields =
case getKey key fields of
Just value ->
f value |> Result.map Just
Nothing ->
Ok Nothing
parseFixedLoop : ParametersDict -> Result Errors (Maybe Parameters.FixedLoop)
parseFixedLoop fields =
if hasKey HasFixedLoop fields then
case
( parseField FixedLoopWidth (parseLength constraints.shortPiece.loops.fixed.width) fields
, parseField FixedLoopLength (parseLength constraints.shortPiece.loops.fixed.length) fields
)
of
( Ok width, Ok length ) ->
let
base =
Parameters.defaultFixedLoop
in
Ok (Just { base | width = width, length = length })
( width, length ) ->
Err
(mkErrors
[ ( FixedLoopWidth, getError width )
, ( FixedLoopLength, getError length )
]
)
else
Ok Nothing
parseFreeLoop : ParametersDict -> Result Errors (Maybe Parameters.FreeLoop)
parseFreeLoop fields =
if hasKey HasFreeLoop fields then
case
( parseField FreeLoopWidth (parseLength constraints.shortPiece.loops.free.width) fields
, parseField FreeLoopLength (parseLength constraints.shortPiece.loops.free.length) fields
, parseField FreeLoopOverlap (parseLength constraints.shortPiece.loops.free.overlap) fields
)
of
( Ok width, Ok length, Ok overlap ) ->
Ok (Just { width = width, length = length, overlap = overlap })
( width, length, overlap ) ->
Err
(mkErrors
[ ( FreeLoopWidth, getError width )
, ( FreeLoopLength, getError length )
, ( FreeLoopOverlap, getError overlap )
]
)
else
Ok Nothing
parseLoops : ParametersDict -> Result Errors Parameters.Loops
parseLoops fields =
case
( ( parseFixedLoop fields, parseFreeLoop fields )
, parseField LoopStyle parseLoopStyle fields
)
of
( ( Ok fixed, Ok free ), Ok style ) ->
Ok { fixed = fixed, free = free, style = style }
( ( fixed, free ), style ) ->
[ unwrapErrors fixed
, unwrapErrors free
, mkErrors [ ( LoopStyle, getError style ) ]
]
|> List.map Dict.Any.toList
|> List.concat
|> Dict.Any.fromList Key.toString
|> Err
parseShortPiece : ParametersDict -> Result Errors Parameters.ShortPiece
parseShortPiece fields =
case
( parseLoops fields
, parseField ShortPieceLength (parseLength constraints.shortPiece.length) fields
)
of
( Ok loops, Ok length ) ->
let
base =
Parameters.default.shortPiece
in
Ok { base | loops = loops, length = length }
( loops, length ) ->
[ unwrapErrors loops
, mkErrors [ ( ShortPieceLength, getError length ) ]
]
|> List.map Dict.Any.toList
|> List.concat
|> Dict.Any.fromList Key.toString
|> Err
parseBuckleHole : ParametersDict -> Result Errors Parameters.BuckleHole
parseBuckleHole fields =
case
( ( parseField BuckleHoleAdjustments (parseInt constraints.longPiece.buckleHole.adjustments) fields
, parseField BuckleHoleDiameter (parseLength constraints.longPiece.buckleHole.diameter) fields
)
, ( parseField BuckleHoleDistance (parseLength constraints.longPiece.buckleHole.distance) fields
, parseField BuckleHoleInterval (parseLength constraints.longPiece.buckleHole.interval) fields
)
)
of
( ( Ok adjustments, Ok diameter ), ( Ok distance, Ok interval ) ) ->
Ok { adjustments = adjustments, diameter = diameter, distance = distance, interval = interval }
( ( adjustments, diameter ), ( distance, interval ) ) ->
Err
(mkErrors
[ ( BuckleHoleAdjustments, getError adjustments )
, ( BuckleHoleDiameter, getError diameter )
, ( BuckleHoleDistance, getError distance )
, ( BuckleHoleInterval, getError interval )
]
)
parseTipStyle : ParametersDict -> Result Errors Parameters.TipStyle
parseTipStyle fields =
case getKey TipStyle fields of
Just "round" ->
Ok Parameters.Round
Just "pointed" ->
case parseField TipSharpness (parseInt constraints.longPiece.tipSharpness) fields of
Ok sharpness ->
Ok (Parameters.Pointed sharpness)
Err err ->
mkErrors [ ( TipSharpness, Just err ) ]
|> Err
Just text ->
mkErrors [ ( TipStyle, Just (NonexistentVariant text) ) ]
|> Err
Nothing ->
mkErrors [ ( TipStyle, Just MissingValue ) ]
|> Err
parseLongPiece : ParametersDict -> Result Errors Parameters.LongPiece
parseLongPiece fields =
case
( ( parseBuckleHole fields
, parseField LongPieceLength (parseLength constraints.longPiece.length) fields
)
, parseTipStyle fields
)
of
( ( Ok buckleHole, Ok length ), Ok tip ) ->
Ok { buckleHole = buckleHole, length = length, tip = tip }
( ( buckleHole, length ), tip ) ->
[ mkErrors [ ( LongPieceLength, getError length ) ]
, unwrapErrors buckleHole
, unwrapErrors tip
]
|> List.map Dict.Any.toList
|> List.concat
|> Dict.Any.fromList Key.toString
|> Err
parseRendering : ParametersDict -> Result Errors Parameters.Rendering
parseRendering fields =
case
( ( parseField CanvasMargin (parseLength constraints.rendering.margin) fields
, parseField LineWidth (parseLength constraints.rendering.lineWidth) fields
, parseField ColorSchema parseColorSchema fields
)
, hasKey QRCode fields
)
of
( ( Ok margin, Ok lineWidth, Ok colorSchema ), qrCode ) ->
let
base =
Parameters.default.rendering
in
Ok
{ base
| margin = margin
, lineWidth = lineWidth
, colorSchema = colorSchema
, qrCode = qrCode
}
( ( margin, lineWidth, colorSchema ), _ ) ->
Err
(mkErrors
[ ( CanvasMargin, getError margin )
, ( LineWidth, getError lineWidth )
, ( ColorSchema, getError colorSchema )
]
)
parseProfile : ParametersDict -> Result Errors Parameters.Profile
parseProfile fields =
case getKey Profile fields of
Just "straight" ->
Ok Parameters.Straight
Just "tapered" ->
case parseField TaperTo (parseLength constraints.taperTo) fields of
Ok taperTo ->
Ok (Parameters.Tapered taperTo)
Err err ->
Err (mkErrors [ ( TaperTo, Just err ) ])
profile ->
Err (mkErrors [ ( Profile, Just (NonexistentVariant (Maybe.withDefault "" profile)) ) ])
parse : ParametersDict -> Result Errors Parameters
parse fields =
case getKey Version fields of
Just "1.0" ->
case
( ( parseField ShoulderWidth (parseLength constraints.shoulderWidth) fields
, parseOptionalField PaddingOffset (parseLength constraints.paddingOffset) fields
|> Result.map (Maybe.withDefault (mm 0))
)
, ( parseLongPiece fields
, parseShortPiece fields
, parseProfile fields
)
, parseRendering fields
)
of
( ( Ok shoulderWidth, Ok paddingOffset ), ( Ok longPiece, Ok shortPiece, Ok profile ), Ok rendering ) ->
Ok
{ shoulderWidth = shoulderWidth
, longPiece = longPiece
, shortPiece = shortPiece
, paddingOffset = paddingOffset
, rendering = rendering
, profile = profile
}
( ( shoulderWidth, paddingOffset ), ( longPiece, shortPiece, profile ), rendering ) ->
[ mkErrors
[ ( ShoulderWidth, getError shoulderWidth )
, ( PaddingOffset, getError paddingOffset )
]
, unwrapErrors longPiece
, unwrapErrors shortPiece
, unwrapErrors rendering
, unwrapErrors profile
]
|> List.map Dict.Any.toList
|> List.concat
|> Dict.Any.fromList Key.toString
|> Err
Just str ->
Err (mkErrors [ ( Version, Just (UnsupportedParametersVersion str) ) ])
Nothing ->
Err (mkErrors [ ( Version, Just MissingValue ) ])