ics-query 0.0.1a0__py3-none-any.whl
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/__init__.py +11 -0
- ics_query/__main__.py +5 -0
- ics_query/_version.py +16 -0
- ics_query/cli.py +92 -0
- ics_query/parse.py +13 -0
- ics_query/tests/__init__.py +0 -0
- ics_query/tests/conftest.py +78 -0
- ics_query/tests/runs/at 2019-03-04 one-event.ics -.run +9 -0
- ics_query/tests/runs/calendars/one-event.ics +34 -0
- ics_query/tests/test_command_line.py +10 -0
- ics_query/version.py +16 -0
- ics_query-0.0.1a0.dist-info/METADATA +794 -0
- ics_query-0.0.1a0.dist-info/RECORD +16 -0
- ics_query-0.0.1a0.dist-info/WHEEL +4 -0
- ics_query-0.0.1a0.dist-info/entry_points.txt +2 -0
- ics_query-0.0.1a0.dist-info/licenses/LICENSE +674 -0
ics_query/__init__.py
ADDED
ics_query/__main__.py
ADDED
ics_query/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.0.1a0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
ics_query/cli.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""The command line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import os # noqa: TCH003
|
|
7
|
+
import sys
|
|
8
|
+
import typing as t
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import recurring_ical_events
|
|
12
|
+
from icalendar import Calendar
|
|
13
|
+
from recurring_ical_events import CalendarQuery
|
|
14
|
+
|
|
15
|
+
from . import parse
|
|
16
|
+
|
|
17
|
+
if t.TYPE_CHECKING:
|
|
18
|
+
from io import FileIO
|
|
19
|
+
|
|
20
|
+
from icalendar.cal import Component
|
|
21
|
+
|
|
22
|
+
from .parse import DateArgument
|
|
23
|
+
|
|
24
|
+
print = functools.partial(print, file=sys.stderr) # noqa: A001
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ComponentsResult:
|
|
28
|
+
"""Output interface for components."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, output: FileIO):
|
|
31
|
+
"""Create a new result."""
|
|
32
|
+
self._file = output
|
|
33
|
+
|
|
34
|
+
def add_component(self, component: Component):
|
|
35
|
+
"""Return a component."""
|
|
36
|
+
self._file.write(component.to_ical())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ComponentsResultArgument(click.File):
|
|
40
|
+
"""Argument for the result."""
|
|
41
|
+
|
|
42
|
+
def convert(
|
|
43
|
+
self,
|
|
44
|
+
value: str | os.PathLike[str] | t.IO[t.Any],
|
|
45
|
+
param: click.Parameter | None,
|
|
46
|
+
ctx: click.Context | None,
|
|
47
|
+
) -> ComponentsResult:
|
|
48
|
+
"""Return a ComponentsResult."""
|
|
49
|
+
file = super().convert(value, param, ctx)
|
|
50
|
+
return ComponentsResult(file)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CalendarQueryInputArgument(click.File):
|
|
54
|
+
"""Argument for the result."""
|
|
55
|
+
|
|
56
|
+
def convert(
|
|
57
|
+
self,
|
|
58
|
+
value: str | os.PathLike[str] | t.IO[t.Any],
|
|
59
|
+
param: click.Parameter | None,
|
|
60
|
+
ctx: click.Context | None,
|
|
61
|
+
) -> recurring_ical_events.CalendarQuery:
|
|
62
|
+
"""Return a CalendarQuery."""
|
|
63
|
+
file = super().convert(value, param, ctx)
|
|
64
|
+
calendar = Calendar.from_ical(file.read())
|
|
65
|
+
return recurring_ical_events.of(calendar)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
arg_calendar = click.argument("calendar", type=CalendarQueryInputArgument("rb"))
|
|
69
|
+
arg_output = click.argument("output", type=ComponentsResultArgument("wb"))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@click.group()
|
|
73
|
+
def main():
|
|
74
|
+
"""Simple program that greets NAME for a total of COUNT times."""
|
|
75
|
+
# sys.stdout = sys.stderr # remove accidential print impact
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
pass_datetime = click.make_pass_decorator(parse.to_time)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@main.command()
|
|
82
|
+
@click.argument("date", type=parse.to_time)
|
|
83
|
+
@arg_calendar
|
|
84
|
+
@arg_output
|
|
85
|
+
def at(calendar: CalendarQuery, output: ComponentsResult, date: DateArgument):
|
|
86
|
+
"""Get the components at a certain time."""
|
|
87
|
+
for event in calendar.at(date):
|
|
88
|
+
print("debug")
|
|
89
|
+
output.add_component(event)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = ["main"]
|
ics_query/parse.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Functions for parsing the content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
DateArgument = tuple[int]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def to_time(dt: str) -> DateArgument:
|
|
9
|
+
"""Parse the time and date."""
|
|
10
|
+
return tuple(map(int, dt.split("-")))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["to_time", "DateArgument"]
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Configure the tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import NamedTuple
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
HERE = Path(__file__).parent
|
|
13
|
+
IO_DIRECTORY = HERE / "runs"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestRun(NamedTuple):
|
|
17
|
+
"""The result from a test run."""
|
|
18
|
+
|
|
19
|
+
exit_code: int
|
|
20
|
+
output: str
|
|
21
|
+
error: str
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_completed_process(
|
|
25
|
+
cls, completed_process: subprocess.CompletedProcess
|
|
26
|
+
) -> TestRun:
|
|
27
|
+
"""Create a new run result."""
|
|
28
|
+
stdout = completed_process.stdout.decode("UTF-8").replace("\r\n", "\n")
|
|
29
|
+
print(stdout)
|
|
30
|
+
return cls(
|
|
31
|
+
completed_process.returncode,
|
|
32
|
+
stdout,
|
|
33
|
+
completed_process.stderr.decode("UTF-8"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class IOTestCase(NamedTuple):
|
|
38
|
+
"""An example test case."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
command: list[str]
|
|
42
|
+
cwd: Path
|
|
43
|
+
expected_output: str
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_path(cls, path: Path) -> IOTestCase:
|
|
47
|
+
"""Create a new testcase from the files."""
|
|
48
|
+
expected_output = path.read_text(encoding="UTF-8").replace("\r\n", "\n")
|
|
49
|
+
return cls(path.name, path.stem.split(), path.parent, expected_output)
|
|
50
|
+
|
|
51
|
+
def run(self) -> TestRun:
|
|
52
|
+
"""Run this test case and return the result."""
|
|
53
|
+
command = ["ics-query", *self.command]
|
|
54
|
+
print(" ".join(command))
|
|
55
|
+
completed_process = subprocess.run( # noqa: S603, RUF100
|
|
56
|
+
command, # noqa: S603, RUF100
|
|
57
|
+
capture_output=True,
|
|
58
|
+
timeout=3,
|
|
59
|
+
check=False,
|
|
60
|
+
cwd=self.cwd / "calendars",
|
|
61
|
+
)
|
|
62
|
+
return TestRun.from_completed_process(completed_process)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
io_test_cases = [
|
|
66
|
+
IOTestCase.from_path(test_case_path)
|
|
67
|
+
for test_case_path in IO_DIRECTORY.iterdir()
|
|
68
|
+
if test_case_path.is_file()
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.fixture(params=io_test_cases)
|
|
73
|
+
def io_testcase(request) -> IOTestCase:
|
|
74
|
+
"""Go though all the IO test cases."""
|
|
75
|
+
return deepcopy(request.param)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = ["IOTestCase", "TestRun"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
BEGIN:VCALENDAR
|
|
2
|
+
VERSION:2.0
|
|
3
|
+
PRODID:-//SabreDAV//SabreDAV//EN
|
|
4
|
+
CALSCALE:GREGORIAN
|
|
5
|
+
X-WR-CALNAME:test
|
|
6
|
+
X-APPLE-CALENDAR-COLOR:#e78074
|
|
7
|
+
BEGIN:VTIMEZONE
|
|
8
|
+
TZID:Europe/Berlin
|
|
9
|
+
X-LIC-LOCATION:Europe/Berlin
|
|
10
|
+
BEGIN:DAYLIGHT
|
|
11
|
+
TZOFFSETFROM:+0100
|
|
12
|
+
TZOFFSETTO:+0200
|
|
13
|
+
TZNAME:CEST
|
|
14
|
+
DTSTART:19700329T020000
|
|
15
|
+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
|
16
|
+
END:DAYLIGHT
|
|
17
|
+
BEGIN:STANDARD
|
|
18
|
+
TZOFFSETFROM:+0200
|
|
19
|
+
TZOFFSETTO:+0100
|
|
20
|
+
TZNAME:CET
|
|
21
|
+
DTSTART:19701025T030000
|
|
22
|
+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
|
23
|
+
END:STANDARD
|
|
24
|
+
END:VTIMEZONE
|
|
25
|
+
BEGIN:VEVENT
|
|
26
|
+
CREATED:20190303T111937
|
|
27
|
+
DTSTAMP:20190303T111937
|
|
28
|
+
LAST-MODIFIED:20190303T111937
|
|
29
|
+
UID:UYDQSG9TH4DE0WM3QFL2J
|
|
30
|
+
SUMMARY:test1
|
|
31
|
+
DTSTART;TZID=Europe/Berlin:20190304T080000
|
|
32
|
+
DTEND;TZID=Europe/Berlin:20190304T083000
|
|
33
|
+
END:VEVENT
|
|
34
|
+
END:VCALENDAR
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Test the commmand line."""
|
|
2
|
+
|
|
3
|
+
from .conftest import IOTestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_check_program_output(io_testcase: IOTestCase):
|
|
7
|
+
"""Run the test case and check the output."""
|
|
8
|
+
result = io_testcase.run()
|
|
9
|
+
print(result.error)
|
|
10
|
+
assert result.output == io_testcase.expected_output
|
ics_query/version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Nicco Kunzmann and Open Web Calendar Contributors <https://open-web-calendar.quelltext.eu/>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-2.0-only
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from ._version import __version__, __version_tuple__, version, version_tuple
|
|
7
|
+
except ModuleNotFoundError:
|
|
8
|
+
__version__ = version = "0.0dev0"
|
|
9
|
+
__version_tuple__ = version_tuple = (0, 0, "dev0")
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"__version__",
|
|
13
|
+
"version",
|
|
14
|
+
"__version_tuple__",
|
|
15
|
+
"version_tuple",
|
|
16
|
+
]
|