kcal-cli 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/PKG-INFO +19 -1
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/README.md +18 -0
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/kcal.py +46 -1
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/pyproject.toml +1 -1
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/.gitignore +0 -0
- {kcal_cli-0.1.0 → kcal_cli-0.2.0}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kcal-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Non-interactive CLI for managing local iCalendar (.ics) files
|
|
5
5
|
Author: hebbian
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,6 +42,8 @@ kcal -f ~/calendar.ics list
|
|
|
42
42
|
kcal -f ~/calendar.ics list --date 2026-06-25
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
Recurring events show their frequency in brackets: `[weekly]`, `[monthly]`, etc.
|
|
46
|
+
|
|
45
47
|
### Add a timed event
|
|
46
48
|
|
|
47
49
|
```
|
|
@@ -60,12 +62,22 @@ For multi-day events, set `--end` to the day after the last day (iCal convention
|
|
|
60
62
|
kcal -f ~/calendar.ics add "Conference" -s "2026-08-01" -e "2026-08-04" --allday
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
### Add a recurring event
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
kcal -f ~/calendar.ics add "Standup" -s "2026-06-26 09:00" -e "2026-06-26 09:15" --repeat weekly --byday MO,WE,FR
|
|
69
|
+
kcal -f ~/calendar.ics add "Review" -s "2026-07-01 14:00" -e "2026-07-01 15:00" --repeat monthly --count 6
|
|
70
|
+
kcal -f ~/calendar.ics add "Sync" -s "2026-07-01 10:00" -e "2026-07-01 11:00" --repeat weekly --interval 2 --until "2026-12-31"
|
|
71
|
+
```
|
|
72
|
+
|
|
63
73
|
### Edit an event
|
|
64
74
|
|
|
65
75
|
```
|
|
66
76
|
kcal -f ~/calendar.ics edit 1cc4704e --summary "New title"
|
|
67
77
|
kcal -f ~/calendar.ics edit 1cc4704e --start "2026-06-26 10:00" --end "2026-06-26 11:00"
|
|
68
78
|
kcal -f ~/calendar.ics edit 1cc4704e --start "2026-06-26" --allday
|
|
79
|
+
kcal -f ~/calendar.ics edit 1cc4704e --repeat weekly --byday MO,FR
|
|
80
|
+
kcal -f ~/calendar.ics edit 1cc4704e --no-repeat
|
|
69
81
|
```
|
|
70
82
|
|
|
71
83
|
### Delete an event
|
|
@@ -84,6 +96,12 @@ UID prefix matching — only the first few characters of the event UID are neede
|
|
|
84
96
|
| `-s`, `--start` | Start date/time (`YYYY-MM-DD HH:MM` or `YYYY-MM-DD`) |
|
|
85
97
|
| `-e`, `--end` | End date/time (optional for all-day events) |
|
|
86
98
|
| `--allday` | Create/convert to all-day event (uses `VALUE=DATE`) |
|
|
99
|
+
| `--repeat` | Recurrence: `daily`, `weekly`, `monthly`, `yearly` |
|
|
100
|
+
| `--interval` | Repeat every N periods (default: 1) |
|
|
101
|
+
| `--count` | Total number of occurrences |
|
|
102
|
+
| `--until` | Recurrence end date (`YYYY-MM-DD`) |
|
|
103
|
+
| `--byday` | Days for weekly recurrence (e.g. `MO,WE,FR`) |
|
|
104
|
+
| `--no-repeat` | Remove recurrence rule (edit only) |
|
|
87
105
|
| `-d`, `--description` | Event description |
|
|
88
106
|
| `-l`, `--location` | Event location |
|
|
89
107
|
|
|
@@ -25,6 +25,8 @@ kcal -f ~/calendar.ics list
|
|
|
25
25
|
kcal -f ~/calendar.ics list --date 2026-06-25
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
Recurring events show their frequency in brackets: `[weekly]`, `[monthly]`, etc.
|
|
29
|
+
|
|
28
30
|
### Add a timed event
|
|
29
31
|
|
|
30
32
|
```
|
|
@@ -43,12 +45,22 @@ For multi-day events, set `--end` to the day after the last day (iCal convention
|
|
|
43
45
|
kcal -f ~/calendar.ics add "Conference" -s "2026-08-01" -e "2026-08-04" --allday
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
### Add a recurring event
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
kcal -f ~/calendar.ics add "Standup" -s "2026-06-26 09:00" -e "2026-06-26 09:15" --repeat weekly --byday MO,WE,FR
|
|
52
|
+
kcal -f ~/calendar.ics add "Review" -s "2026-07-01 14:00" -e "2026-07-01 15:00" --repeat monthly --count 6
|
|
53
|
+
kcal -f ~/calendar.ics add "Sync" -s "2026-07-01 10:00" -e "2026-07-01 11:00" --repeat weekly --interval 2 --until "2026-12-31"
|
|
54
|
+
```
|
|
55
|
+
|
|
46
56
|
### Edit an event
|
|
47
57
|
|
|
48
58
|
```
|
|
49
59
|
kcal -f ~/calendar.ics edit 1cc4704e --summary "New title"
|
|
50
60
|
kcal -f ~/calendar.ics edit 1cc4704e --start "2026-06-26 10:00" --end "2026-06-26 11:00"
|
|
51
61
|
kcal -f ~/calendar.ics edit 1cc4704e --start "2026-06-26" --allday
|
|
62
|
+
kcal -f ~/calendar.ics edit 1cc4704e --repeat weekly --byday MO,FR
|
|
63
|
+
kcal -f ~/calendar.ics edit 1cc4704e --no-repeat
|
|
52
64
|
```
|
|
53
65
|
|
|
54
66
|
### Delete an event
|
|
@@ -67,6 +79,12 @@ UID prefix matching — only the first few characters of the event UID are neede
|
|
|
67
79
|
| `-s`, `--start` | Start date/time (`YYYY-MM-DD HH:MM` or `YYYY-MM-DD`) |
|
|
68
80
|
| `-e`, `--end` | End date/time (optional for all-day events) |
|
|
69
81
|
| `--allday` | Create/convert to all-day event (uses `VALUE=DATE`) |
|
|
82
|
+
| `--repeat` | Recurrence: `daily`, `weekly`, `monthly`, `yearly` |
|
|
83
|
+
| `--interval` | Repeat every N periods (default: 1) |
|
|
84
|
+
| `--count` | Total number of occurrences |
|
|
85
|
+
| `--until` | Recurrence end date (`YYYY-MM-DD`) |
|
|
86
|
+
| `--byday` | Days for weekly recurrence (e.g. `MO,WE,FR`) |
|
|
87
|
+
| `--no-repeat` | Remove recurrence rule (edit only) |
|
|
70
88
|
| `-d`, `--description` | Event description |
|
|
71
89
|
| `-l`, `--location` | Event location |
|
|
72
90
|
|
|
@@ -41,6 +41,29 @@ def parse_date(s: str) -> date:
|
|
|
41
41
|
raise ValueError(f"Cannot parse date: {s!r}")
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def build_rrule(args):
|
|
45
|
+
if not args.repeat:
|
|
46
|
+
return None
|
|
47
|
+
rule = {"freq": args.repeat.upper()}
|
|
48
|
+
if hasattr(args, "interval") and args.interval:
|
|
49
|
+
rule["interval"] = int(args.interval)
|
|
50
|
+
if hasattr(args, "count") and args.count:
|
|
51
|
+
rule["count"] = int(args.count)
|
|
52
|
+
if hasattr(args, "until") and args.until:
|
|
53
|
+
rule["until"] = parse_dt(args.until) if " " in args.until else parse_date(args.until)
|
|
54
|
+
if hasattr(args, "byday") and args.byday:
|
|
55
|
+
rule["byday"] = args.byday.upper().split(",")
|
|
56
|
+
return rule
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def apply_rrule(event, args):
|
|
60
|
+
rrule = build_rrule(args)
|
|
61
|
+
if rrule:
|
|
62
|
+
if "rrule" in event:
|
|
63
|
+
del event["rrule"]
|
|
64
|
+
event.add("rrule", rrule)
|
|
65
|
+
|
|
66
|
+
|
|
44
67
|
def cmd_add(args):
|
|
45
68
|
cal = load_calendar(args.file)
|
|
46
69
|
event = Event()
|
|
@@ -62,6 +85,7 @@ def cmd_add(args):
|
|
|
62
85
|
event.add("description", args.description)
|
|
63
86
|
if args.location:
|
|
64
87
|
event.add("location", args.location)
|
|
88
|
+
apply_rrule(event, args)
|
|
65
89
|
cal.add_component(event)
|
|
66
90
|
save_calendar(cal, args.file)
|
|
67
91
|
print(f"Added: {args.summary} (uid: {event['uid']})")
|
|
@@ -119,7 +143,12 @@ def cmd_list(args):
|
|
|
119
143
|
start = dt.strftime("%Y-%m-%d %H:%M")
|
|
120
144
|
dtend = e.get("dtend")
|
|
121
145
|
end = " - " + dtend.dt.strftime("%H:%M") if dtend else ""
|
|
122
|
-
|
|
146
|
+
rrule = e.get("rrule")
|
|
147
|
+
repeat = ""
|
|
148
|
+
if rrule:
|
|
149
|
+
freq = rrule.get("freq", [""])[0].lower() if isinstance(rrule.get("freq"), list) else str(rrule.get("freq", "")).lower()
|
|
150
|
+
repeat = f" [{freq}]"
|
|
151
|
+
print(f" {start}{end} {summary}{repeat} [uid: {uid[:8]}...]")
|
|
123
152
|
|
|
124
153
|
|
|
125
154
|
def cmd_delete(args):
|
|
@@ -177,6 +206,11 @@ def cmd_edit(args):
|
|
|
177
206
|
if args.end:
|
|
178
207
|
del component["dtend"]
|
|
179
208
|
component.add("dtend", parse_dt(args.end))
|
|
209
|
+
if args.repeat:
|
|
210
|
+
apply_rrule(component, args)
|
|
211
|
+
if args.no_repeat:
|
|
212
|
+
if "rrule" in component:
|
|
213
|
+
del component["rrule"]
|
|
180
214
|
if args.description:
|
|
181
215
|
if "description" in component:
|
|
182
216
|
del component["description"]
|
|
@@ -205,6 +239,11 @@ def main():
|
|
|
205
239
|
p_add.add_argument("--start", "-s", required=True, help="Start date/time (YYYY-MM-DD HH:MM, or YYYY-MM-DD for all-day)")
|
|
206
240
|
p_add.add_argument("--end", "-e", help="End date/time (optional for all-day events)")
|
|
207
241
|
p_add.add_argument("--allday", action="store_true", help="Create an all-day event (uses DATE values)")
|
|
242
|
+
p_add.add_argument("--repeat", choices=["daily", "weekly", "monthly", "yearly"], help="Recurrence frequency")
|
|
243
|
+
p_add.add_argument("--interval", type=int, help="Repeat every N periods (default: 1)")
|
|
244
|
+
p_add.add_argument("--count", type=int, help="Number of occurrences")
|
|
245
|
+
p_add.add_argument("--until", help="Recurrence end date (YYYY-MM-DD)")
|
|
246
|
+
p_add.add_argument("--byday", help="Days for weekly recurrence (e.g. MO,WE,FR)")
|
|
208
247
|
p_add.add_argument("--description", "-d", help="Description")
|
|
209
248
|
p_add.add_argument("--location", "-l", help="Location")
|
|
210
249
|
|
|
@@ -220,6 +259,12 @@ def main():
|
|
|
220
259
|
p_edit.add_argument("--start", "-s", help="New start date/time")
|
|
221
260
|
p_edit.add_argument("--end", "-e", help="New end date/time")
|
|
222
261
|
p_edit.add_argument("--allday", action="store_true", help="Convert to an all-day event")
|
|
262
|
+
p_edit.add_argument("--repeat", choices=["daily", "weekly", "monthly", "yearly"], help="Set/change recurrence")
|
|
263
|
+
p_edit.add_argument("--interval", type=int, help="Repeat every N periods")
|
|
264
|
+
p_edit.add_argument("--count", type=int, help="Number of occurrences")
|
|
265
|
+
p_edit.add_argument("--until", help="Recurrence end date (YYYY-MM-DD)")
|
|
266
|
+
p_edit.add_argument("--byday", help="Days for weekly recurrence (e.g. MO,WE,FR)")
|
|
267
|
+
p_edit.add_argument("--no-repeat", action="store_true", help="Remove recurrence rule")
|
|
223
268
|
p_edit.add_argument("--description", "-d", help="New description")
|
|
224
269
|
p_edit.add_argument("--location", "-l", help="New location")
|
|
225
270
|
|
|
File without changes
|
|
File without changes
|