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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kcal-cli
3
- Version: 0.1.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
- print(f" {start}{end} {summary} [uid: {uid[:8]}...]")
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
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kcal-cli"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Non-interactive CLI for managing local iCalendar (.ics) files"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
File without changes
File without changes