alpha
Login
or
Join now
kwando.tngl.sh
/
gcal
Star
1
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
I calendar parsing
Star
1
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
rename splitter -> parser
author
Hannes Nevalainen
date
6 hours ago
(Jul 27, 2026, 11:18 AM +0200)
commit
0f404569
0f4045696d26ac776c0a9658b96efecb28ecb366
parent
f3fdee76
f3fdee76fb3e7d5aa63dcd0dd25d51af03d818ed
+48
-55
1 changed file
Expand all
Collapse all
Unified
Split
src
gcal.gleam
+48
-55
src/gcal.gleam
View file
Reviewed
···
77
77
input: String,
78
78
tz: String,
79
79
) -> Result(Calendar, Error) {
80
80
-
let splitters = new_parser()
80
80
+
let parser = new_parser()
81
81
82
82
-
let lines = unfold_lines(input, splitters)
82
82
+
let lines = unfold_lines(input, parser)
83
83
let non_empty = list.filter(lines, fn(line) { line != "" })
84
84
85
85
-
case parse_all_components(non_empty, [], splitters) {
86
86
-
Ok(components) -> build_calendar(components, tz, splitters)
85
85
+
case parse_all_components(non_empty, [], parser) {
86
86
+
Ok(components) -> build_calendar(components, tz, parser)
87
87
Error(err) -> Error(err)
88
88
}
89
89
}
90
90
91
91
-
fn unfold_lines(input: String, splitters: Parser) -> List(String) {
92
92
-
do_unfold_lines(input, splitters, [])
91
91
+
fn unfold_lines(input: String, parser: Parser) -> List(String) {
92
92
+
do_unfold_lines(input, parser, [])
93
93
|> list.reverse
94
94
}
95
95
96
96
fn do_unfold_lines(
97
97
input: String,
98
98
-
splitters: Parser,
98
98
+
parser: Parser,
99
99
acc: List(String),
100
100
) -> List(String) {
101
101
-
case splitter.split(splitters.lines, input) {
101
101
+
case splitter.split(parser.lines, input) {
102
102
#(line, "", "") ->
103
103
case acc {
104
104
[] -> [line]
105
105
[prev, ..rest] ->
106
106
case string.starts_with(line, " ") || string.starts_with(line, "\t") {
107
107
True -> {
108
108
-
let #(_, remaining) = splitter.split_after(splitters.ws, line)
108
108
+
let #(_, remaining) = splitter.split_after(parser.ws, line)
109
109
[prev <> remaining, ..rest]
110
110
}
111
111
False -> [line, prev, ..rest]
···
113
113
}
114
114
#(line, _, remaining) ->
115
115
case acc {
116
116
-
[] -> do_unfold_lines(remaining, splitters, [line])
116
116
+
[] -> do_unfold_lines(remaining, parser, [line])
117
117
[prev, ..rest] ->
118
118
case string.starts_with(line, " ") || string.starts_with(line, "\t") {
119
119
True -> {
120
120
-
let #(_, remaining_line) =
121
121
-
splitter.split_after(splitters.ws, line)
122
122
-
do_unfold_lines(remaining, splitters, [
120
120
+
let #(_, remaining_line) = splitter.split_after(parser.ws, line)
121
121
+
do_unfold_lines(remaining, parser, [
123
122
prev <> remaining_line,
124
123
..rest
125
124
])
126
125
}
127
127
-
False -> do_unfold_lines(remaining, splitters, [line, prev, ..rest])
126
126
+
False -> do_unfold_lines(remaining, parser, [line, prev, ..rest])
128
127
}
129
128
}
130
129
}
···
133
132
fn parse_all_components(
134
133
lines: List(String),
135
134
acc: List(Component),
136
136
-
splitters: Parser,
135
135
+
parser: Parser,
137
136
) -> Result(List(Component), Error) {
138
137
case lines {
139
138
[] -> Ok(list.reverse(acc))
140
139
[line, ..rest] ->
141
141
-
case splitter.split(splitters.begin, line) {
140
140
+
case splitter.split(parser.begin, line) {
142
141
#("", "BEGIN:", kind) ->
143
143
-
case parse_component(kind, rest, [], [], splitters) {
142
142
+
case parse_component(kind, rest, [], [], parser) {
144
143
Ok(#(component, remaining)) ->
145
145
-
parse_all_components(remaining, [component, ..acc], splitters)
144
144
+
parse_all_components(remaining, [component, ..acc], parser)
146
145
Error(err) -> Error(err)
147
146
}
148
147
_ -> Error(ParseError("Unexpected line: " <> line))
···
155
154
lines: List(String),
156
155
props: List(Property),
157
156
children: List(Component),
158
158
-
splitters: Parser,
157
157
+
parser: Parser,
159
158
) -> Result(#(Component, List(String)), Error) {
160
159
let end_marker = "END:" <> kind
161
160
case lines {
···
168
167
rest,
169
168
))
170
169
False ->
171
171
-
case splitter.split(splitters.begin, line) {
170
170
+
case splitter.split(parser.begin, line) {
172
171
#("", "BEGIN:", subkind) ->
173
173
-
case parse_component(subkind, rest, [], [], splitters) {
172
172
+
case parse_component(subkind, rest, [], [], parser) {
174
173
Ok(#(child, remaining)) ->
175
174
parse_component(
176
175
kind,
177
176
remaining,
178
177
props,
179
178
[child, ..children],
180
180
-
splitters,
179
179
+
parser,
181
180
)
182
181
Error(err) -> Error(err)
183
182
}
184
183
_ ->
185
185
-
case parse_property(line, splitters) {
184
184
+
case parse_property(line, parser) {
186
185
Ok(prop) ->
187
187
-
parse_component(
188
188
-
kind,
189
189
-
rest,
190
190
-
[prop, ..props],
191
191
-
children,
192
192
-
splitters,
193
193
-
)
186
186
+
parse_component(kind, rest, [prop, ..props], children, parser)
194
187
Error(err) -> Error(err)
195
188
}
196
189
}
···
198
191
}
199
192
}
200
193
201
201
-
fn parse_property(line: String, splitters: Parser) -> Result(Property, Error) {
202
202
-
case splitter.split(splitters.colon, line) {
194
194
+
fn parse_property(line: String, parser: Parser) -> Result(Property, Error) {
195
195
+
case splitter.split(parser.colon, line) {
203
196
#(name_part, ":", value) -> {
204
204
-
let #(name, params) = parse_name_and_params(name_part, splitters)
197
197
+
let #(name, params) = parse_name_and_params(name_part, parser)
205
198
Ok(Property(name, params, unescape_text(value)))
206
199
}
207
200
_ -> Error(ParseError("Invalid property line: " <> line))
···
210
203
211
204
fn parse_name_and_params(
212
205
part: String,
213
213
-
splitters: Parser,
206
206
+
parser: Parser,
214
207
) -> #(String, List(Parameter)) {
215
215
-
case splitter.split(splitters.semi, part) {
216
216
-
#(name, ";", params_str) -> #(name, parse_params(params_str, splitters))
208
208
+
case splitter.split(parser.semi, part) {
209
209
+
#(name, ";", params_str) -> #(name, parse_params(params_str, parser))
217
210
_ -> #(part, [])
218
211
}
219
212
}
220
213
221
221
-
fn parse_params(params_str: String, splitters: Parser) -> List(Parameter) {
222
222
-
do_parse_params(params_str, [], splitters)
214
214
+
fn parse_params(params_str: String, parser: Parser) -> List(Parameter) {
215
215
+
do_parse_params(params_str, [], parser)
223
216
}
224
217
225
218
fn do_parse_params(
226
219
input: String,
227
220
acc: List(Parameter),
228
228
-
splitters: Parser,
221
221
+
parser: Parser,
229
222
) -> List(Parameter) {
230
230
-
case splitter.split(splitters.semi, input) {
223
223
+
case splitter.split(parser.semi, input) {
231
224
#(param, ";", remaining) ->
232
232
-
case parse_single_param(param, splitters) {
233
233
-
Ok(p) -> do_parse_params(remaining, [p, ..acc], splitters)
234
234
-
Error(_) -> do_parse_params(remaining, acc, splitters)
225
225
+
case parse_single_param(param, parser) {
226
226
+
Ok(p) -> do_parse_params(remaining, [p, ..acc], parser)
227
227
+
Error(_) -> do_parse_params(remaining, acc, parser)
235
228
}
236
229
#(param, "", "") ->
237
237
-
case parse_single_param(param, splitters) {
230
230
+
case parse_single_param(param, parser) {
238
231
Ok(p) -> list.reverse([p, ..acc])
239
232
Error(_) -> list.reverse(acc)
240
233
}
···
244
237
245
238
fn parse_single_param(
246
239
param: String,
247
247
-
splitters: Parser,
240
240
+
parser: Parser,
248
241
) -> Result(Parameter, Error) {
249
249
-
case splitter.split(splitters.eq, param) {
242
242
+
case splitter.split(parser.eq, param) {
250
243
#(name, "=", value) -> Ok(Parameter(name, value))
251
244
_ -> Error(ParseError("Invalid parameter: " <> param))
252
245
}
···
332
325
333
326
fn parse_datetime_value(
334
327
value: String,
335
335
-
splitters: Parser,
328
328
+
parser: Parser,
336
329
) -> Result(#(calendar.Date, calendar.TimeOfDay, Bool), Error) {
337
337
-
case splitter.split(splitters.t_sep, value) {
330
330
+
case splitter.split(parser.t_sep, value) {
338
331
#(date_str, sep, time_str) ->
339
332
case sep == "T" || sep == "t" {
340
333
True -> {
···
378
371
@internal
379
372
pub fn parse_datetime(
380
373
prop: Property,
381
381
-
splitters: Parser,
374
374
+
parser: Parser,
382
375
fallback_tz: String,
383
376
) -> timestamp.Timestamp {
384
377
let value = prop.value
···
403
396
False -> value
404
397
}
405
398
406
406
-
case parse_datetime_value(clean_value, splitters) {
399
399
+
case parse_datetime_value(clean_value, parser) {
407
400
Ok(#(date, time, _)) -> {
408
401
let tzid = case is_utc {
409
402
True -> Error(ParseError("UTC"))
···
456
449
457
450
fn build_event(
458
451
props: List(Property),
459
459
-
splitters: Parser,
452
452
+
parser: Parser,
460
453
fallback_tz: String,
461
454
) -> Event {
462
455
let uid = extract_prop(props, "UID")
···
471
464
472
465
let dtstart =
473
466
dtstart_prop
474
474
-
|> result.map(fn(p) { parse_datetime(p, splitters, fallback_tz) })
467
467
+
|> result.map(fn(p) { parse_datetime(p, parser, fallback_tz) })
475
468
|> result.unwrap(timestamp.unix_epoch)
476
469
let dtend =
477
470
dtend_prop
478
478
-
|> result.map(fn(p) { parse_datetime(p, splitters, fallback_tz) })
471
471
+
|> result.map(fn(p) { parse_datetime(p, parser, fallback_tz) })
479
472
|> result.unwrap(timestamp.unix_epoch)
480
473
481
474
Event(uid, summary, dtstart, dtend, is_all_day, props)
···
484
477
fn build_calendar(
485
478
components: List(Component),
486
479
tz_override: String,
487
487
-
splitters: Parser,
480
480
+
parser: Parser,
488
481
) -> Result(Calendar, Error) {
489
482
let flat =
490
483
components
···
511
504
cal_children
512
505
|> list.filter_map(fn(c) {
513
506
case c.kind {
514
514
-
"VEVENT" -> Ok(build_event(c.properties, splitters, detected_tz))
507
507
+
"VEVENT" -> Ok(build_event(c.properties, parser, detected_tz))
515
508
_ -> Error(Nil)
516
509
}
517
510
})