-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmake_items.py
More file actions
executable file
·465 lines (384 loc) · 16.8 KB
/
make_items.py
File metadata and controls
executable file
·465 lines (384 loc) · 16.8 KB
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
460
461
462
463
464
465
#! /usr/bin/env python3
# To test tklr
import random
import os
from datetime import datetime, date, timedelta
from rich import print
from tklr.item import Item
from tklr.controller import Controller
from tklr.model import DatabaseManager
from tklr.tklr_env import TklrEnvironment
# from tklr.shared import log_msg
import lorem
from typing import Union
from dateutil.tz import gettz
from dateutil import rrule
from dateutil.rrule import rruleset, rrulestr
from dateutil.parser import parse
ONEDAY = timedelta(days=1)
ONEWEEK = timedelta(days=7)
os.environ["TKLR_HOME"] = "/Users/dag/Projects/tklr-uv/items"
# in_one_hour = (
# datetime.now().replace(second=0, microsecond=0) + timedelta(hours=1)
# ).strftime("%Y-%m-%d %H:%M00")
def in_two_minutes():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 2 + (2 - now.minute % 10)
next = now + timedelta(minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_five_minutes():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 5 + (5 - now.minute % 10)
next = now + timedelta(minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_ten_minutes():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 10 + (10 - now.minute % 10)
next = now + timedelta(minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def one_hour_ago():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now - timedelta(minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_one_hour():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now + timedelta(minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_one_day():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now + timedelta(days=1, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_two_days():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now + timedelta(days=2, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_five_days():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now + timedelta(days=5, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def five_days_ago():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now - timedelta(days=5, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def ten_days_ago():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now - timedelta(days=10, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def in_two_weeks():
now = datetime.now().replace(second=0, microsecond=0)
delta_minutes = 60 + (15 - now.minute % 15)
next = now + timedelta(days=2 * 7, minutes=delta_minutes)
return next.strftime("%Y-%m-%d %H:%M")
def five_years_ago():
td = date.today()
return td.replace(year=td.year - 5).strftime("%Y-%m-%d")
def local_dtstr_to_utc_str(local_dt_str: str) -> str:
"""
Convert a local datetime string to a UTC datetime string.
Args:
local_dt_str (str): Local datetime string.
local_tz_str (str): Local timezone string.
Returns:
str: UTC datetime string.
"""
from dateutil import parser
try:
local_dt = parser.parse(local_dt_str).astimezone()
utc_dt = local_dt.astimezone(tz=gettz("UTC")).replace(tzinfo=None)
# return utc_dt.isoformat()
return utc_dt.strftime("%Y-%m-%d %H:%M")
except:
print(f"error parsing {local_dt_str = }")
return ""
def to_tdstr(seconds: int) -> str:
"""Convert a timedelta object to a compact string like '1h30m20s'."""
total = int(seconds)
if total == 0:
return "0s"
h, remainder = divmod(total, 3600)
m, s = divmod(remainder, 60)
parts = []
if h:
parts.append(f"{h}h")
if m:
parts.append(f"{m}m")
if s:
parts.append(f"{s}s")
return "".join(parts)
def week(dt: datetime) -> Union[datetime, datetime]:
y, w, d = dt.isocalendar()
wk_beg = dt - (d - 1) * ONEDAY if d > 1 else dt
wk_end = dt + (7 - d) * ONEDAY if d < 7 else dt
return wk_beg.date(), wk_end.date()
env = TklrEnvironment()
ctrl = Controller("./items/tklr.db", env, reset=True)
# Insert the UTC records into the database
num_items = 0
types = ["~", "~", "*", "~", "*", "*", "*", "*", "%"]
contexts = ["errands", "home", "office", "shop"]
tags = ["red", "green", "blue"]
dates = [0, 0, 0, 1, 0, 0, 0] # dates 1/7 of the time
repeat = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0] # repeat 1/10 of the time
# duration = [to_tdstr(x) for x in range(6, 2 * 60 * 60, 6)]
duration = [to_tdstr(x) for x in range(0, 2 * 60 * 60, 900)]
now = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
num_konnections = 0
num_items = int(num_items)
wkbeg, wkend = week(now)
months = num_items // 200
start = wkbeg - 2 * 7 * ONEDAY
until = wkend + (10 * 7) * ONEDAY
print(f"Generating {num_items} records from {start} to {until}...")
def parse_rruleset(rrule_text: str) -> rruleset:
lines = rrule_text.strip().splitlines()
rset = rruleset()
dtstart = None
for line in lines:
if line.startswith("DTSTART"):
_, dt_str = line.split(":", 1)
dtstart = parse(dt_str)
elif line.startswith("RRULE"):
rule = rrulestr(line, dtstart=dtstart)
rset.rrule(rule)
elif line.startswith("RDATE"):
_, dt_str = line.split(":", 1)
for rdate in dt_str.split(","):
rset.rdate(parse(rdate))
return rset
def handle_new_entry(self, entry_str: str):
try:
item = Item()
item.parse(entry_str)
except ValueError as e:
self.show_error(f"Invalid entry: {e}")
return
self.model.add_item(item)
datetimes = list(
rrule.rrule(
rrule.DAILY,
byweekday=range(7),
byhour=range(7, 20),
byminute=range(0, 60, 30),
dtstart=start,
until=until,
)
)
tmp = []
while len(tmp) < 8:
_ = lorem.sentence().split(" ")[0]
if _ not in tmp:
tmp.append(_)
names = []
for i in range(0, 8, 2):
names.append(f"{tmp[i]}, {tmp[i + 1]}")
def phrase():
# for the summary
# drop the ending period
s = lorem.sentence()[:-1]
num = random.choice([3, 4, 5])
words = s.split(" ")[:num]
return " ".join(words).rstrip()
def word():
return lorem.sentence()[:-1].split(" ")[0]
freq = [
"w",
"w &w MO,WE,FR",
"w &i 2",
"d",
"d &i 2",
"d &i 3",
]
counts = [2, 3, 4]
first_of_month = now.replace(day=1).strftime("%Y-%m-%d")
yesterday_date = (now - ONEDAY).strftime("%Y-%m-%d")
today_date = now.strftime("%Y-%m-%d")
tomorrow_date = (now + ONEDAY).strftime("%Y-%m-%d")
one_week_ago = (now - ONEWEEK).strftime("%Y-%m-%d")
in_one_week = (now + ONEWEEK).strftime("%Y-%m-%d")
# type, name, details, rrulestr, extent, alerts, location
links = [
f"* https link @s {today_date} 18h @g https://dagraham.github.io/tklr-dgraham/",
f"* mailto link @s {today_date} 19h @g mailto:[email protected]",
f"* markdown file link @s {today_date} 20h @g /Users/dag/Projects/tklr-uv/README.md",
f"* sqlite file link @s {today_date} 20h @g /Users/dag/Projects/tklr-uv/items/tklr.db",
f"* png file link @s {today_date} 20h @g /Users/dag/Projects/tklr-uv/mouse_with_pocket_watch.png",
]
busy = [
f"* all-day yesterday @d all day event @s {yesterday_date}",
f"* all-day today @d all day event @s {today_date}",
f"* all-day tomorrow @d all day event @s {tomorrow_date}",
f"* one hour yesterday @s {yesterday_date} 9a @e 1h",
f"* one hour today @s {today_date} 10a @e 1h",
f"* one hour tomorrow @s {tomorrow_date} 11a @e 1h",
"* all-day every Tuesday @s tue @r w &c 3",
"* all-day every fourth day @s wed @r d &i 3 &c 5",
f"* 2-hours every third day @s {in_one_week} 9a @e 2h @r d &i 3 &c 5",
"* spanning 3 days @s sat 7p @e 2d2h30m",
f"* 1-hour last, this and next week @s {one_week_ago} 4p @e 1h @+ {today_date} 4p, {in_one_week} 4p",
]
finish = [
"~ test: repeating daily 1 @s 2025-12-04 3:30p @r d &c 2 @f 2025-12-09 10:00a @d instance on 5th should now be first",
"~ test: repeating daily @s 2025-12-04 3:30p @r d &c 2 @f 2025-12-09 10:00a @d instance on 5th should now be first",
"~ test: repeating daily 2 @s 2025-12-04 3:30p @r d &c 1 @f 2025-12-09 10:00a @d should be marked finished",
"~ test: rdates @s 2025-12-08 1:30p @r d &c 3 @+ 2025-12-08 9:00a, 2025-12-08 5:00p @f 2025-12-09 10:00a @d instance at 9am is first and should be finished",
"~ test: not repeating @s 2025-12-08 1:30p @f 2025-12-08 10:00a @d should be marked finished",
"~ test: offset @s 2025-12-04 12:00p @o 4d @f 2025-12-08 9:00a ",
"~ test: offset learn @s 2025-12-04 12:00p @o ~4d @f 2025-12-08 9:00p ",
"""^ test: project 1 @s 2025-12-08 1:30p @~ job 1 &s 1w &r 1 &f 2025-12-04 @~ job 2 &s 3w &r 2: 1 """,
"""^ test: project 2 @s 2025-12-08 1:30p @~ job 1 &s 1w &r 1 &f 2025-12-04 9:00a @~ job 2 &s 3w &r 2: 1 &f 2025-12-10 4:00p """,
]
goals = [
"! goal 0 @s 2025/12/9 @t 3/1w",
"! goal 1 @s 2025/12/9 @t 3/1w @k 1",
"! goal 2 @s 2025/12/7 @t 3/1w",
"! goal 3 @s 2025/12/28 @t 3/1w @k 1",
"! goal 3 @s 2025/12/28 @t 3/1w @k 2",
"! goal 3 @s 2025/12/28 @t 3/1w @k 3",
"! goal 4 @s 2025/12/28 @t 3/1w",
"! active goal @s 2024/12/31 @t 1/1w",
"! future goal @s 2026/02/01 @t 1/1w",
]
sixpm = [
f"* should be 4-7pm @s {today_date} 4p @e 3h",
f"* should be 5-8pm @s {today_date} 5p @e 3h",
f"* should be 6-9pm @s {today_date} 6p @e 3h",
f"* should be 6:01-9:01pm @s {today_date} 6:01p @e 3h",
f"* should be 6:59-9:59pm @s {today_date} 6:59p @e 3h",
f"* should be 7-10pm @s {today_date} 7p @e 3h",
f"* should be 8-11pm @s {today_date} 8p @e 3h",
f"* should be 9-11:59pm @s {today_date} 9p @e 3h",
f"* should be 10-11:59 with split for 12-1am @s {today_date} 10p @e 3h",
]
notice = [
f"* #notice event tomorrow with 1d notice @s {tomorrow_date} @n 1d",
f"* #notice event day after tomorrow with 1d notice @s {in_two_days()} @n 1d",
f"~ #notice one off task in 2 days with 1d notice @s {in_two_days()} @n 1d",
f"~ #notice one off task in 2 days with 2d notice @s {in_two_days()} @n 2d",
f"~ #notice one off task in 2 days with 3d notice @s {in_two_days()} @n 3d",
f"~ #notice offset task in 2 days with 1d notice @s {in_two_days()} @o 4d @n 1d",
f"~ #notice offset task in 2 days with 2d notice @s {in_two_days()} @o 4d @n 2d",
f"~ #notice offset task in 2 days with 3d notice @s {in_two_days()} @o 4d @n 3d",
f"~ #notice repeating task in 2 days with 1d notice @s {in_two_days()} @r w @n 1d",
f"~ #notice repeating task in 2 days with 2d notice @s {in_two_days()} @r w @n 2d",
f"~ #notice repeating task in 2 days with 3d notice @s {in_two_days()} @r w @n 3d",
]
items = [
f"* {{XXX}} anniversary @s {five_years_ago()} @r y",
f"% masked entry @m This is a masked entry - it should be readable in the details view of the UI but otherwise obfuscated. @s {today_date}",
f"* first of the month @d all day event @s {first_of_month}",
f"* event in 2 days with 1d notice @s {in_two_days()} @n 1d",
f"~ task in 5 days with 1w notice @s {in_five_days()} @n 1w",
f"* event in 1 day with notice @s {tomorrow_date} @n 1w",
f"~ all day yesterday @d all day task @p 2 @s {yesterday_date}",
f"~ all day today @d all day task @p 2 @s {today_date}",
f"~ all day tomorrow @d all day event @p 2 @s {tomorrow_date}",
f"* zero extent #naive @s {tomorrow_date} 10h @z none",
f"* zero extent #naive from z @s {tomorrow_date} 10h z none",
"* daily with US/Pacific from z @s 3pm z US/Pacific @d whatever @c wherever @r d &i 3 &c 10 @b repeating/tags",
"* daily datetime US/Pacific @s 1pm @z US/Pacific @d whatever @c wherever @r d &i 3 &c 10 @b repeating/tags",
f"~ daily at 10am local @s {today_date} 10:00 @r d @b repeating/tags",
f"~ just today at 10am local @s {today_date} 10:00 ",
f"~ just today at 10am naive @s {today_date} 10:00 z none",
f"* starting in 5 days repeating for 3 days @s {in_five_days()} 8:30a @e 4h @r d &c 3 @n 1w @b repeating/tags",
f"~ repeating and rdates @s {today_date} 1:30p @r d @+ 2:30p, 3:30p @b repeating/tags",
f"* repeating until @s {today_date} 7:30p @e 1h @r d &u {in_two_weeks()} @b repeating/tags",
f"~ due, tags, description, priority one @p 1 @s {tomorrow_date} @d This item has a #description. Now is the time for all good men to come to the aid of their country #red #white #blue",
f"* three datetimes @s {in_ten_minutes()} @e 45m @+ {in_one_hour()}, {in_one_day()}",
f"""% long formatted description @s {yesterday_date}
@d Title
1. This
i. with part one
ii. and this
2. And finally this.
""",
"~ no due date or datetime and priority one @p 1",
f"~ one due date and priority one @s {today_date} @p 1",
f"~ one due datetime and priority one @s {in_one_hour()} @p 1",
"~ no due date and #priority two @p 2",
"~ no due date and #priority three @p 3",
"~ no due date and #priority four @p 4",
"~ no due date and no #priority",
"~ no due date and #priority five @p 5",
f"^ no prerequisites @s {today_date} @n 1w @~ this &r 1 &f {today_date} @~ that &r 2",
"? draft reminder - no checks",
f"~ one date with priority three @s {yesterday_date} @p 3",
"~ three datetimes @s 9am @+ 10am, 11am",
"* event spread over multiple days @s 3p fri @e 2d2h30m",
"* daily datetime @s 3p @e 30m @r d @b repeating/tags",
"* gour Tiki Roundtable Meeting @s 1/1 14:00 z UTC @e 1h30m @r m &w +3TH &c 10 @b repeating/tags",
"* timezone test for noon CET @s 12p z CET @e 1h",
"* timezone test for noon naive @s 12p z none @e 1h",
f"~ wind grandfather clock @s {five_days_ago()} @o 4d",
f"~ fill birdfeeders @s {ten_days_ago()} @o ~4d",
f"% very long note @d {lorem.paragraph()} #lorem",
]
bins = [
"% Journal entry for October @b 2025:10/2025/journal @s 2p @d Test bin entries",
"% Churchill - Give me a pig @b Churchill/quotations/library @d Dogs look up at you.\nCats look down at you.\nGive me a pig. They look you in the eye and treat you as an equal.",
"* Ellen's French adventure @s mon @r d &c 7 @b travel/activities @b Lille/France/places @b repeating/tags",
"% Charles and Bonnie Smith @b SmithCB/people:S/people @d details about Charles and Bonnie @b Athens/Greece/places",
"% itenerary @b Athens-Istanbul/travel/activities @s 2025-10-23 @b Istanbul/Turkey/places @b 2025:10/2025/journal @g whatever",
]
alerts = [
f"* alert test @s {in_five_minutes()} @a 3m, 1m: v",
f"* notify test @s {in_five_minutes()} @a 4m, 2m: n",
f"* combined test @s {in_five_minutes()} @a 0m, -1m: n, v",
]
records = []
count = 0
# items = []
# num_items = 0
while len(items) < num_items:
t = random.choice(types)
name = phrase()
description = lorem.paragraph() + " #lorem"
start = random.choice(datetimes)
date = random.choice(dates)
if date:
# all day if event else end of day
dtstart = start.strftime("%Y%m%d")
else:
dtstart = start.strftime("%Y-%m-%d %H:%M")
# dtstart = local_dtstr_to_utc_str(dts)
extent = f" @e {random.choice(duration)}" if (t == "*" and not date) else ""
if random.choice(repeat):
items.append(
f"{t} {name} @d {description} @s {dtstart}{extent} "
f"@r {random.choice(freq)} &c {random.choice(counts)} "
)
else:
items.append(f"{t} {name} @d {description} @s {dtstart}{extent}")
id = 0
# for entry in items: # + alerts:
# for entry in busy + items:
for entry in items + bins + finish + goals + links + notice + alerts + sixpm + busy:
id += 1
try:
item = Item(raw=entry, env=env, final=True, controller=ctrl) # .to_dict()
# new_entry = item.to_entry()
# print(f">>>\n{new_entry = }")
# continue
if item.parse_ok:
try:
record_id = ctrl.add_item(item) # .to_dict()
count += 1
except Exception as e:
print(f"exception: {e = }\n from {entry = }, {item.__dict__ = }\n")
else:
print(f"parse failed: {item.parse_message}")
if count % 20 == 0:
print(f"---\n{count} {entry = }")
except Exception as e:
print(f"error: {e = }\n from {entry = }\n")
try:
ctrl.db_manager.populate_dependent_tables()
except Exception as e:
print(f"Error: {e}")
print(f"Inserted {count} records into the database, last_id {id}.")