ics-query 0.4.32__tar.gz → 0.4.34__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.
- {ics_query-0.4.32 → ics_query-0.4.34}/PKG-INFO +39 -3
- {ics_query-0.4.32 → ics_query-0.4.34}/README.md +36 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/_version.py +2 -2
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/cli.py +97 -13
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/conftest.py +17 -9
- ics_query-0.4.34/ics_query/tests/runs/at 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics -.run +9 -0
- ics_query-0.4.34/ics_query/tests/runs/calendars/x-wr-timezone-rdate-hackerpublicradio.ics +26 -0
- ics_query-0.4.34/ics_query/tests/test_issue_40_valid_calendar.py +68 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/pyproject.toml +1 -1
- {ics_query-0.4.32 → ics_query-0.4.34}/.github/FUNDING.yml +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/.github/dependabot.yml +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/.github/workflows/tests.yml +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/.gitignore +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/.pre-commit-config.yaml +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/LICENSE +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/conftest.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics-query +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics-query.cmd +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics-query.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/__init__.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/__main__.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/parse.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/query.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/__init__.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/all --tz Singapore one-event.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/all three-events.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 multiple-calendars.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event-twice.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-07 multiple-calendars.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2024-08-20 Berlin-Los-Angeles.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/between 20240823 4d recurring-work-events.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/Berlin-Los-Angeles.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/empty-calendar.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/empty-file.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/multiple-calendars.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event-twice.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event-without-timezone.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/recurring-work-events.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/simple-journal.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/simple-todo.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/three-events.ics +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL -c VEVENT one-event.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL one-event.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL simple-journal.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VTODO -c VJOURNAL simple-todo.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VTODO simple-todo.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first empty-calendar.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first empty-file.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first recurring-work-events.ics -.run +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_command_line.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_parse_date.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/test_parse_timedelta.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/ics_query/version.py +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/renovate.json +0 -0
- {ics_query-0.4.32 → ics_query-0.4.34}/tox.ini +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ics-query
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.34
|
|
4
4
|
Summary: Find out what happens in ICS calendar files - query and filter RFC 5545 compatible .ics files for events, journals, TODOs and more.
|
|
5
5
|
Project-URL: Homepage, https://github.com/niccokunzmann/ics-query/
|
|
6
6
|
Project-URL: Repository, https://github.com/niccokunzmann/ics-query/
|
|
7
|
-
Project-URL: source_archive, https://github.com/niccokunzmann/ics-query/archive/
|
|
7
|
+
Project-URL: source_archive, https://github.com/niccokunzmann/ics-query/archive/c25d3a90e2b57c9bd22aa18e57104f174691dba3.zip
|
|
8
8
|
Project-URL: Issues, https://github.com/niccokunzmann/ics-query/issues
|
|
9
9
|
Project-URL: Documentation, https://github.com/niccokunzmann/ics-query/
|
|
10
10
|
Project-URL: Changelog, https://github.com/niccokunzmann/ics-query/#changelog
|
|
@@ -697,7 +697,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
697
697
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
698
698
|
Requires-Python: >=3.9
|
|
699
699
|
Requires-Dist: click==8.1.8
|
|
700
|
-
Requires-Dist: icalendar==6.
|
|
700
|
+
Requires-Dist: icalendar==6.2.0
|
|
701
701
|
Requires-Dist: python-dateutil==2.9.0.post0
|
|
702
702
|
Requires-Dist: recurring-ical-events==3.7.0
|
|
703
703
|
Requires-Dist: six==1.17.0
|
|
@@ -802,6 +802,8 @@ LAST-MODIFIED:20190303T111937
|
|
|
802
802
|
END:VEVENT
|
|
803
803
|
```
|
|
804
804
|
|
|
805
|
+
#### Concatenating Calendars
|
|
806
|
+
|
|
805
807
|
We can concatenate calendars and pipe them into `ics-query`.
|
|
806
808
|
In the example below, we get all events that happen right now in two calendars.
|
|
807
809
|
|
|
@@ -819,6 +821,30 @@ You can pipe one or more calendars into the input.
|
|
|
819
821
|
cat calendar.ics | ics-query first -
|
|
820
822
|
```
|
|
821
823
|
|
|
824
|
+
#### Valid ICS files
|
|
825
|
+
|
|
826
|
+
The resulting events are missing the timezone and the calendar information by default.
|
|
827
|
+
This information can be added using the `--as-calendar` parameter.
|
|
828
|
+
The result is a valid `.ics` file that can be processed further by other commands and programs.
|
|
829
|
+
|
|
830
|
+
In the example below, we use the calendar command to inspect the event for human readability.
|
|
831
|
+
|
|
832
|
+
```shell
|
|
833
|
+
$ ics-query at --as-calendar 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics event.ics
|
|
834
|
+
$ icalendar event.ics
|
|
835
|
+
Organizer:
|
|
836
|
+
Attendees:
|
|
837
|
+
|
|
838
|
+
Summary : HPR Community News
|
|
839
|
+
Starts : Sat May 3 20:00:00 2014
|
|
840
|
+
End : Sat May 3 22:00:00 2014
|
|
841
|
+
Duration : 2:00:00
|
|
842
|
+
Location : mumble.openspeak.cc port: 64747
|
|
843
|
+
Comment :
|
|
844
|
+
Description:
|
|
845
|
+
This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_Hacking_shownotes.html
|
|
846
|
+
```
|
|
847
|
+
|
|
822
848
|
### Events at Certain Times
|
|
823
849
|
|
|
824
850
|
You can query which events happen at certain times:
|
|
@@ -1110,6 +1136,16 @@ We automatically release the versions that only update dependencies.
|
|
|
1110
1136
|
If the version you installed does not show up here, only the dependencies
|
|
1111
1137
|
have been updated.
|
|
1112
1138
|
|
|
1139
|
+
- v0.4.33
|
|
1140
|
+
|
|
1141
|
+
- Add `--as-calendar` parameter.
|
|
1142
|
+
|
|
1143
|
+
- v0.4.32
|
|
1144
|
+
|
|
1145
|
+
- Update dependencies.
|
|
1146
|
+
- Include recurrence ID in events to identify the occurrence in a series.
|
|
1147
|
+
- Update help message in command line.
|
|
1148
|
+
|
|
1113
1149
|
- v0.4.1
|
|
1114
1150
|
|
|
1115
1151
|
- Automatic release with patch level version number increased
|
|
@@ -92,6 +92,8 @@ LAST-MODIFIED:20190303T111937
|
|
|
92
92
|
END:VEVENT
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
#### Concatenating Calendars
|
|
96
|
+
|
|
95
97
|
We can concatenate calendars and pipe them into `ics-query`.
|
|
96
98
|
In the example below, we get all events that happen right now in two calendars.
|
|
97
99
|
|
|
@@ -109,6 +111,30 @@ You can pipe one or more calendars into the input.
|
|
|
109
111
|
cat calendar.ics | ics-query first -
|
|
110
112
|
```
|
|
111
113
|
|
|
114
|
+
#### Valid ICS files
|
|
115
|
+
|
|
116
|
+
The resulting events are missing the timezone and the calendar information by default.
|
|
117
|
+
This information can be added using the `--as-calendar` parameter.
|
|
118
|
+
The result is a valid `.ics` file that can be processed further by other commands and programs.
|
|
119
|
+
|
|
120
|
+
In the example below, we use the calendar command to inspect the event for human readability.
|
|
121
|
+
|
|
122
|
+
```shell
|
|
123
|
+
$ ics-query at --as-calendar 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics event.ics
|
|
124
|
+
$ icalendar event.ics
|
|
125
|
+
Organizer:
|
|
126
|
+
Attendees:
|
|
127
|
+
|
|
128
|
+
Summary : HPR Community News
|
|
129
|
+
Starts : Sat May 3 20:00:00 2014
|
|
130
|
+
End : Sat May 3 22:00:00 2014
|
|
131
|
+
Duration : 2:00:00
|
|
132
|
+
Location : mumble.openspeak.cc port: 64747
|
|
133
|
+
Comment :
|
|
134
|
+
Description:
|
|
135
|
+
This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_Hacking_shownotes.html
|
|
136
|
+
```
|
|
137
|
+
|
|
112
138
|
### Events at Certain Times
|
|
113
139
|
|
|
114
140
|
You can query which events happen at certain times:
|
|
@@ -400,6 +426,16 @@ We automatically release the versions that only update dependencies.
|
|
|
400
426
|
If the version you installed does not show up here, only the dependencies
|
|
401
427
|
have been updated.
|
|
402
428
|
|
|
429
|
+
- v0.4.33
|
|
430
|
+
|
|
431
|
+
- Add `--as-calendar` parameter.
|
|
432
|
+
|
|
433
|
+
- v0.4.32
|
|
434
|
+
|
|
435
|
+
- Update dependencies.
|
|
436
|
+
- Include recurrence ID in events to identify the occurrence in a series.
|
|
437
|
+
- Update help message in command line.
|
|
438
|
+
|
|
403
439
|
- v0.4.1
|
|
404
440
|
|
|
405
441
|
- Automatic release with patch level version number increased
|
|
@@ -24,18 +24,18 @@ import typing as t
|
|
|
24
24
|
import zoneinfo
|
|
25
25
|
|
|
26
26
|
import click
|
|
27
|
-
from icalendar
|
|
27
|
+
from icalendar import Calendar, Component, Timezone
|
|
28
28
|
from tzlocal import get_localzone_name
|
|
29
29
|
|
|
30
30
|
from . import parse
|
|
31
31
|
from .query import Query
|
|
32
|
-
from .version import cli_version
|
|
32
|
+
from .version import __version__, cli_version
|
|
33
33
|
|
|
34
34
|
if t.TYPE_CHECKING:
|
|
35
35
|
from io import FileIO
|
|
36
36
|
|
|
37
37
|
import recurring_ical_events
|
|
38
|
-
from icalendar
|
|
38
|
+
from icalendar import Component
|
|
39
39
|
|
|
40
40
|
from .parse import Date
|
|
41
41
|
|
|
@@ -67,17 +67,58 @@ class ComponentsResult:
|
|
|
67
67
|
def __init__(self, output: FileIO):
|
|
68
68
|
"""Create a new result."""
|
|
69
69
|
self._file = output
|
|
70
|
+
self._entered = False
|
|
70
71
|
|
|
71
|
-
def
|
|
72
|
+
def write(self, data: bytes | str):
|
|
73
|
+
"""Write data to the output."""
|
|
74
|
+
if isinstance(data, str):
|
|
75
|
+
data = data.encode()
|
|
76
|
+
self._file.write(data)
|
|
77
|
+
|
|
78
|
+
def __enter__(self):
|
|
79
|
+
"""Start adding components."""
|
|
80
|
+
self._entered = True
|
|
81
|
+
|
|
82
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
83
|
+
"""Stop adding components."""
|
|
84
|
+
self._entered = False
|
|
85
|
+
|
|
86
|
+
def add_component(self, component: Component) -> None:
|
|
72
87
|
"""Return a component."""
|
|
73
|
-
self.
|
|
88
|
+
assert self._entered
|
|
89
|
+
self.write(component.to_ical())
|
|
74
90
|
|
|
75
|
-
def add_components(self, components: t.Iterable[Component]):
|
|
91
|
+
def add_components(self, components: t.Iterable[Component]) -> None:
|
|
76
92
|
"""Add all components."""
|
|
77
93
|
for component in components:
|
|
78
94
|
self.add_component(component)
|
|
79
95
|
|
|
80
96
|
|
|
97
|
+
class CalendarResult(ComponentsResult):
|
|
98
|
+
"""Wrap the resulting components in a calendar."""
|
|
99
|
+
|
|
100
|
+
CALENDAR_START = (
|
|
101
|
+
f"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-query {__version__}\r\n"
|
|
102
|
+
)
|
|
103
|
+
CALENDAR_END = "END:VCALENDAR\r\n"
|
|
104
|
+
|
|
105
|
+
def __init__(self, output: FileIO, timezones: list[Timezone]):
|
|
106
|
+
super().__init__(output)
|
|
107
|
+
self.timezones = timezones
|
|
108
|
+
|
|
109
|
+
def __enter__(self):
|
|
110
|
+
"""Start the calendar."""
|
|
111
|
+
super().__enter__()
|
|
112
|
+
self.write(self.CALENDAR_START)
|
|
113
|
+
for timezone in self.timezones:
|
|
114
|
+
self.write(timezone.to_ical())
|
|
115
|
+
|
|
116
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
117
|
+
"""Stop the calendar."""
|
|
118
|
+
self.write(self.CALENDAR_END)
|
|
119
|
+
super().__exit__(exc_type, exc_value, traceback)
|
|
120
|
+
|
|
121
|
+
|
|
81
122
|
class ComponentsResultArgument(click.File):
|
|
82
123
|
"""Argument for the result."""
|
|
83
124
|
|
|
@@ -89,6 +130,11 @@ class ComponentsResultArgument(click.File):
|
|
|
89
130
|
) -> ComponentsResult:
|
|
90
131
|
"""Return a ComponentsResult."""
|
|
91
132
|
file = super().convert(value, param, ctx)
|
|
133
|
+
# we claim the as_calendar argument
|
|
134
|
+
wrap_calendar = ctx.params.pop("as_calendar", False)
|
|
135
|
+
if wrap_calendar:
|
|
136
|
+
joined: JoinedCalendars = ctx.params["calendar"]
|
|
137
|
+
return CalendarResult(file, joined.timezones)
|
|
92
138
|
return ComponentsResult(file)
|
|
93
139
|
|
|
94
140
|
|
|
@@ -97,6 +143,7 @@ class JoinedCalendars:
|
|
|
97
143
|
self, calendars: list[Calendar], timezone: str, components: t.Sequence[str]
|
|
98
144
|
):
|
|
99
145
|
"""Join multiple calendars."""
|
|
146
|
+
self.calendars = calendars
|
|
100
147
|
self.queries = [
|
|
101
148
|
Query(calendar, timezone, components=components) for calendar in calendars
|
|
102
149
|
]
|
|
@@ -124,6 +171,27 @@ class JoinedCalendars:
|
|
|
124
171
|
for query in self.queries:
|
|
125
172
|
yield from query.between(start, end)
|
|
126
173
|
|
|
174
|
+
@property
|
|
175
|
+
def timezones(self) -> list[Timezone]:
|
|
176
|
+
"""Return all the timezones in use."""
|
|
177
|
+
result = []
|
|
178
|
+
tzids = set()
|
|
179
|
+
# add existing timezone components first
|
|
180
|
+
for calendar in self.calendars:
|
|
181
|
+
for timezone in calendar.timezones:
|
|
182
|
+
if timezone.tz_name not in tzids:
|
|
183
|
+
tzids.add(timezone.tz_name)
|
|
184
|
+
result.append(timezone)
|
|
185
|
+
# add X-WR-TIMEZONE later to prevent generating if existing
|
|
186
|
+
for calendar in self.calendars:
|
|
187
|
+
tzid = calendar.get("X-WR-TIMEZONE", None)
|
|
188
|
+
if tzid is not None and tzid not in tzids:
|
|
189
|
+
timezone = Timezone.from_tzid(tzid)
|
|
190
|
+
tzids.add(timezone.tz_name)
|
|
191
|
+
tzids.add(tzid)
|
|
192
|
+
result.append(timezone)
|
|
193
|
+
return result
|
|
194
|
+
|
|
127
195
|
|
|
128
196
|
class CalendarQueryInputArgument(click.File):
|
|
129
197
|
"""Argument for the result."""
|
|
@@ -160,6 +228,15 @@ opt_timezone = click.option(
|
|
|
160
228
|
help=("Set the timezone. See also --available-timezones"),
|
|
161
229
|
)
|
|
162
230
|
|
|
231
|
+
opt_calendar = click.option(
|
|
232
|
+
"--as-calendar",
|
|
233
|
+
envvar=ENV_PREFIX + "_AS_CALENDAR",
|
|
234
|
+
is_flag=True,
|
|
235
|
+
default=False,
|
|
236
|
+
is_eager=True,
|
|
237
|
+
help="Return a valid calendar, not just the components.",
|
|
238
|
+
)
|
|
239
|
+
|
|
163
240
|
|
|
164
241
|
def arg_calendar(func):
|
|
165
242
|
"""Decorator for a calendar argument with all used options."""
|
|
@@ -173,9 +250,12 @@ def arg_calendar(func):
|
|
|
173
250
|
return opt_timezone(opt_components(arg(wrapper)))
|
|
174
251
|
|
|
175
252
|
|
|
176
|
-
arg_output
|
|
177
|
-
|
|
178
|
-
#
|
|
253
|
+
def arg_output(func):
|
|
254
|
+
"""Add the output argument and its parameters."""
|
|
255
|
+
# Option with many values and list as result
|
|
256
|
+
# see https://click.palletsprojects.com/en/latest/options/#multiple-options
|
|
257
|
+
arg = click.argument("output", type=ComponentsResultArgument("wb"), default="-")
|
|
258
|
+
return opt_calendar(arg(func))
|
|
179
259
|
|
|
180
260
|
|
|
181
261
|
def opt_available_timezones(*param_decls: str, **kwargs: t.Any) -> t.Callable:
|
|
@@ -458,7 +538,8 @@ def at(calendar: JoinedCalendars, output: ComponentsResult, date: Date):
|
|
|
458
538
|
ics-query at 19900101235959 # 1st January 1990, 23:59:59
|
|
459
539
|
ics-query at `date +%Y%m%d%H%M%S` # now
|
|
460
540
|
""" # noqa: D301
|
|
461
|
-
output
|
|
541
|
+
with output:
|
|
542
|
+
output.add_components(calendar.at(date))
|
|
462
543
|
|
|
463
544
|
|
|
464
545
|
@cli.command()
|
|
@@ -475,7 +556,8 @@ def first(calendar: JoinedCalendars, output: ComponentsResult):
|
|
|
475
556
|
ics-query first --component VEVENT calendar.ics -
|
|
476
557
|
|
|
477
558
|
""" # noqa: D301
|
|
478
|
-
output
|
|
559
|
+
with output:
|
|
560
|
+
output.add_components(calendar.first())
|
|
479
561
|
|
|
480
562
|
|
|
481
563
|
@cli.command()
|
|
@@ -499,7 +581,8 @@ def all(calendar: JoinedCalendars, output: ComponentsResult): # noqa: A001
|
|
|
499
581
|
potentially enourmous. You can mitigate this by closing the OUTPUT
|
|
500
582
|
when you have enough e.g. with a head command.
|
|
501
583
|
""" # noqa: D301
|
|
502
|
-
output
|
|
584
|
+
with output:
|
|
585
|
+
output.add_components(calendar.all())
|
|
503
586
|
|
|
504
587
|
|
|
505
588
|
@cli.command()
|
|
@@ -683,7 +766,8 @@ def between(
|
|
|
683
766
|
Add 3 hours and 15 minutes to START: +3h15m
|
|
684
767
|
|
|
685
768
|
""" # noqa: D301
|
|
686
|
-
output
|
|
769
|
+
with output:
|
|
770
|
+
output.add_components(calendar.between(start, end))
|
|
687
771
|
|
|
688
772
|
|
|
689
773
|
def main():
|
|
@@ -22,13 +22,14 @@ from pathlib import Path
|
|
|
22
22
|
from typing import Callable, NamedTuple
|
|
23
23
|
|
|
24
24
|
import pytest
|
|
25
|
+
from icalendar import Calendar
|
|
25
26
|
|
|
26
27
|
HERE = Path(__file__).parent
|
|
27
28
|
IO_DIRECTORY = HERE / "runs"
|
|
28
29
|
CALENDARS_DIRECTORY = IO_DIRECTORY / "calendars"
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
class
|
|
32
|
+
class ExampleRun(NamedTuple):
|
|
32
33
|
"""The result from a test run."""
|
|
33
34
|
|
|
34
35
|
exit_code: int
|
|
@@ -38,7 +39,7 @@ class TestRun(NamedTuple):
|
|
|
38
39
|
@classmethod
|
|
39
40
|
def from_completed_process(
|
|
40
41
|
cls, completed_process: subprocess.CompletedProcess
|
|
41
|
-
) ->
|
|
42
|
+
) -> ExampleRun:
|
|
42
43
|
"""Create a new run result."""
|
|
43
44
|
stdout = completed_process.stdout.decode("UTF-8").replace("\r\n", "\n")
|
|
44
45
|
print(stdout)
|
|
@@ -48,6 +49,11 @@ class TestRun(NamedTuple):
|
|
|
48
49
|
completed_process.stderr.decode("UTF-8"),
|
|
49
50
|
)
|
|
50
51
|
|
|
52
|
+
@property
|
|
53
|
+
def calendar(self) -> Calendar:
|
|
54
|
+
"""Return the output as a calendar."""
|
|
55
|
+
return Calendar.from_ical(self.output)
|
|
56
|
+
|
|
51
57
|
|
|
52
58
|
def get_binary_path(request: pytest.FixtureRequest) -> str:
|
|
53
59
|
"""Return the path to the ics-query command."""
|
|
@@ -59,7 +65,7 @@ def get_binary_path(request: pytest.FixtureRequest) -> str:
|
|
|
59
65
|
return Path(command).absolute()
|
|
60
66
|
|
|
61
67
|
|
|
62
|
-
def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) ->
|
|
68
|
+
def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) -> ExampleRun:
|
|
63
69
|
"""Run ics-qeury with a command.
|
|
64
70
|
|
|
65
71
|
- cwd is the working directory
|
|
@@ -74,7 +80,9 @@ def run_ics_query(*command, cwd=CALENDARS_DIRECTORY, binary: str) -> TestRun:
|
|
|
74
80
|
check=False,
|
|
75
81
|
cwd=cwd,
|
|
76
82
|
)
|
|
77
|
-
|
|
83
|
+
if completed_process.stderr:
|
|
84
|
+
print(completed_process.stderr.decode())
|
|
85
|
+
return ExampleRun.from_completed_process(completed_process)
|
|
78
86
|
|
|
79
87
|
|
|
80
88
|
class IOTestCase(NamedTuple):
|
|
@@ -92,7 +100,7 @@ class IOTestCase(NamedTuple):
|
|
|
92
100
|
expected_output = path.read_text(encoding="UTF-8").replace("\r\n", "\n")
|
|
93
101
|
return cls(path.name, path.stem.split(), path.parent, expected_output, binary)
|
|
94
102
|
|
|
95
|
-
def run(self) ->
|
|
103
|
+
def run(self) -> ExampleRun:
|
|
96
104
|
"""Run this test case and return the result."""
|
|
97
105
|
return run_ics_query(*self.command, binary=self.binary)
|
|
98
106
|
|
|
@@ -100,7 +108,7 @@ class IOTestCase(NamedTuple):
|
|
|
100
108
|
io_test_case_paths = [
|
|
101
109
|
test_case_path
|
|
102
110
|
for test_case_path in IO_DIRECTORY.iterdir()
|
|
103
|
-
if test_case_path.is_file()
|
|
111
|
+
if test_case_path.is_file() and test_case_path.suffix == ".run"
|
|
104
112
|
]
|
|
105
113
|
|
|
106
114
|
|
|
@@ -112,8 +120,8 @@ def io_testcase(request: pytest.FixtureRequest) -> IOTestCase:
|
|
|
112
120
|
return IOTestCase.from_path(path, binary)
|
|
113
121
|
|
|
114
122
|
|
|
115
|
-
@pytest.fixture
|
|
116
|
-
def run(request: pytest.FixtureRequest) -> Callable[...,
|
|
123
|
+
@pytest.fixture()
|
|
124
|
+
def run(request: pytest.FixtureRequest) -> Callable[..., ExampleRun]:
|
|
117
125
|
"""Return a runner function."""
|
|
118
126
|
|
|
119
127
|
def run(*args, **kw):
|
|
@@ -123,4 +131,4 @@ def run(request: pytest.FixtureRequest) -> Callable[..., TestRun]:
|
|
|
123
131
|
return run
|
|
124
132
|
|
|
125
133
|
|
|
126
|
-
__all__ = ["IOTestCase", "
|
|
134
|
+
__all__ = ["IOTestCase", "ExampleRun"]
|
ics_query-0.4.34/ics_query/tests/runs/at 2014-05-03 x-wr-timezone-rdate-hackerpublicradio.ics -.run
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
BEGIN:VEVENT
|
|
2
|
+
SUMMARY:HPR Community News
|
|
3
|
+
DTSTART;TZID=Europe/London:20140503T200000
|
|
4
|
+
DTEND;TZID=Europe/London:20140503T220000
|
|
5
|
+
RECURRENCE-ID;TZID=Europe/London:20140503T200000
|
|
6
|
+
DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCal
|
|
7
|
+
endar_Hacking_shownotes.html
|
|
8
|
+
LOCATION:mumble.openspeak.cc port: 64747
|
|
9
|
+
END:VEVENT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
BEGIN:VCALENDAR
|
|
2
|
+
VERSION:2.0
|
|
3
|
+
PRODID:Data::ICal 0.20
|
|
4
|
+
X-WR-CALNAME:Hacker Public Radio
|
|
5
|
+
X-WR-TIMEZONE:Europe/London
|
|
6
|
+
BEGIN:VEVENT
|
|
7
|
+
DESCRIPTION:This is from http://www.hackerpublicradio.org/eps/hpr1286/iCalendar_
|
|
8
|
+
Hacking_shownotes.html
|
|
9
|
+
DTEND:20130803T210000Z
|
|
10
|
+
DTSTART:20130803T190000Z
|
|
11
|
+
LOCATION:mumble.openspeak.cc port: 64747
|
|
12
|
+
RDATE;VALUE=DATE-TIME:20130803T190000Z
|
|
13
|
+
RDATE;VALUE=DATE-TIME:20130831T190000Z
|
|
14
|
+
RDATE;VALUE=DATE-TIME:20131005T190000Z
|
|
15
|
+
RDATE;VALUE=DATE-TIME:20131102T190000Z
|
|
16
|
+
RDATE;VALUE=DATE-TIME:20131130T190000Z
|
|
17
|
+
RDATE;VALUE=DATE-TIME:20140104T190000Z
|
|
18
|
+
RDATE;VALUE=DATE-TIME:20140201T190000Z
|
|
19
|
+
RDATE;VALUE=DATE-TIME:20140301T190000Z
|
|
20
|
+
RDATE;VALUE=DATE-TIME:20140405T190000Z
|
|
21
|
+
RDATE;VALUE=DATE-TIME:20140503T190000Z
|
|
22
|
+
RDATE;VALUE=DATE-TIME:20140531T190000Z
|
|
23
|
+
RDATE;VALUE=DATE-TIME:20140705T190000Z
|
|
24
|
+
SUMMARY:HPR Community News
|
|
25
|
+
END:VEVENT
|
|
26
|
+
END:VCALENDAR
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""We make sure that the result can be processed by another icalendar application.
|
|
2
|
+
|
|
3
|
+
See https://github.com/niccokunzmann/ics-query/issues/40
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from ics_query import __version__
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from icalendar import Calendar, Timezone
|
|
16
|
+
|
|
17
|
+
from ics_query.tests.conftest import ExampleRun
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture()
|
|
21
|
+
def calendar(run) -> ExampleRun:
|
|
22
|
+
"""Return a calendar that is wrapped around the event."""
|
|
23
|
+
return run("first", "--as-calendar", "one-event-without-timezone.ics").calendar
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_result_is_wrapped_in_a_calendar(calendar: Calendar):
|
|
27
|
+
"""Add the calendar component around the event."""
|
|
28
|
+
assert calendar.name == "VCALENDAR"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_the_product_id_is_that_of_ics_query(calendar):
|
|
32
|
+
"""The product id is set with version."""
|
|
33
|
+
assert calendar["PRODID"] == f"ics-query {__version__}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_the_version_is_set(calendar):
|
|
37
|
+
"""Version is required."""
|
|
38
|
+
assert calendar["VERSION"] == "2.0"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_no_timezone_is_included(calendar):
|
|
42
|
+
"""We do not have timezones in this file, so there should be none."""
|
|
43
|
+
assert calendar.timezones == []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
"file",
|
|
48
|
+
[
|
|
49
|
+
"one-event.ics", # one timezone
|
|
50
|
+
"multiple-calendars.ics", # same timezone twice
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
def test_calendar_adds_timezones_automatically(run, file):
|
|
54
|
+
"""Return a calendar that is wrapped around the event."""
|
|
55
|
+
calendar = run("first", "--as-calendar", file).calendar
|
|
56
|
+
assert len(calendar.timezones) == 1
|
|
57
|
+
tz: Timezone = calendar.timezones[0]
|
|
58
|
+
assert tz.tz_name == "Europe/Berlin"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_x_wr_timezone_is_added(run):
|
|
62
|
+
"""X-WR-TIMEZONE requires adding the timezone component manually."""
|
|
63
|
+
calendar = run(
|
|
64
|
+
"first", "--as-calendar", "x-wr-timezone-rdate-hackerpublicradio.ics"
|
|
65
|
+
).calendar
|
|
66
|
+
assert len(calendar.timezones) == 1
|
|
67
|
+
tz: Timezone = calendar.timezones[0]
|
|
68
|
+
assert tz.tz_name == "Europe/London"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/all --tz Singapore one-event.ics -.run
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event-twice.ics -.run
RENAMED
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/at 2019-03-04 one-event.ics -.run
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/one-event-without-timezone.ics
RENAMED
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/calendars/recurring-work-events.ics
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VJOURNAL one-event.ics -.run
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first -c VTODO simple-todo.ics -.run
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ics_query-0.4.32 → ics_query-0.4.34}/ics_query/tests/runs/first recurring-work-events.ics -.run
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|