diff --git a/.github/workflows/github-action.yml b/.github/workflows/github-action.yml index e56332d..078690c 100644 --- a/.github/workflows/github-action.yml +++ b/.github/workflows/github-action.yml @@ -22,20 +22,18 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install flake8 pytest freezegun pytest-cov - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -r requirements-dev.txt + pip install . -r requirements.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest - working-directory: ./i3_agenda run: | - pytest --junitxml=../junit/test-results.xml - pytest --doctest-modules --cov=. --cov-report=xml:../coverage/cov.xml --cov-report=html:../coverage/ - + pytest tests --junitxml=junit/test-results.xml + pytest tests --doctest-modules --cov=. --cov-report=xml:coverage/cov.xml --cov-report=html:coverage/ - name: Code Coverage Report uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 with: @@ -59,4 +57,3 @@ jobs: with: name: coverage path: pr/ - diff --git a/.gitignore b/.gitignore index c852e6d..f7333ed 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ i3_agenda/__pycache__ .mypy_cache/ __pycache__ +.autoenv +.coverage diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d1ffce4 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +help: + @cat ./Makefile | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +install: ## Install dependencies + pip install . -r requirements.txt + +dev: ## Install dependencies for development + pip install -e . + pip install -r requirements-dev.txt + +test: ## Run tests + pytest tests --doctest-modules --cov=src + +lint: ## Check code style + flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + +fix: ## Format code + black -l79 src + +release: ## Create a release: make release v=0.1.0 + @if [ -z "$(v)" ]; then echo "Missing version number:\nUse: make release v=0.1"; exit 1; fi + @sed -i -e "s/version = \".*\"/version = \"$(v)\"/" pyproject.toml + @sed -i -e "s/__version__ = \".*\"/__version__ = \"$(v)\"/" src/i3_agenda/__init__.py + @sed -i -e "s/archive\/[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\.tar\.gz/archive\/$(v).tar.gz/" pyproject.toml + @git diff + @# Ideally, we would use the following + @# git add pyproject.toml src/i3_agenda/__init__.py + @# git commit -m "Release v$(v)" + @# git tag -a v$(v) -m "Release v$(v)" + @# git push + @# git push --tags diff --git a/README.md b/README.md index 07f3d4b..44033cc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![AUR version](https://img.shields.io/aur/version/i3-agenda?style=flat-square&logo=arch-linux)](https://aur.archlinux.org/packages/i3-agenda/) -[![PyPI](https://img.shields.io/pypi/v/i3-agenda?style=flat-square&logo=python)](https://pypi.org/project/i3-agenda/) Version Badge +[![PyPI](https://img.shields.io/pypi/v/i3-agenda?style=flat-square&logo=python)](https://pypi.org/project/i3-agenda/) Version Badge # What is this? @@ -16,21 +16,38 @@ It will print the time and title of the closest event. # Setup ## Google API + https://developers.google.com/calendar/quickstart/python -1. You need to create a Google API project and download your OAuth 2.0 credentials json file.\ -You first need to create a project [here](https://console.developers.google.com/apis/credentials), then add Google Calendar support, then download the credentials.json file.\ -**Alternatively, you can just use [this link](https://developers.google.com/calendar/quickstart/python) and click "Enable the Google Calendar API". This will create a project, add Google Calendar support, and let you download the file in 1 click**.\ -If you're having trouble, you can use this tutorial for more information [https://developers.google.com/calendar/auth](https://developers.google.com/calendar/auth).\ -Another great guide can be found here if you're still having trouble: [https://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile](https://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile). +1. You need to create a Google API project and download your OAuth 2.0 + credentials json file.You first need to create a project [here][cred], then + add Google Calendar support, then download the credentials.json file. + **Alternatively, you can just use [this link][python] and click "Enable the + Google Calendar API". This will create a project, add Google Calendar + support, and let you download the file in 1 click**. If you're having + trouble, you can use this tutorial for more information + [https://developers.google.com/calendar/auth][auth]. Another great guide can + be found here if you're still having trouble: + [https://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile][secret]. 2. Download the credentials file to somewhere on your computer. 3. Proceed to installation phase. ## Installation After downloading the credentials file, install the package. +### Pipx + +Using [`pipx`][pipx] will save you time and it's cross-platform. It is a +package manager for Python that allows you to easily install and run Python +packages in isolated environments. + +```bash +pipx install https://github.com/rosenpin/i3-agenda +``` + ### Pip -1. `sudo pip install i3-agenda` + +1. `sudo pip install .` 2. Try running `i3-agenda -c $CREDENTIALS_FILE_PATH` with "$CREDENTIALS_FILE_PATH" replaced with the path to the credentials.json file you downloaded in the previous step. 3. Add configuration to your bar (examples in the Examples section below). @@ -90,7 +107,7 @@ Leaving the list empty will fetch all calendars (default behavior). It might not work properly if you have more than 10 all day events, this can be fixed by increasing the maxResults variable. ### RTL support -If you use RTL or some of your events contain RTL languages, you will need to pipe [pybidi](https://pypi.org/project/python-bidi/) with the script. Example: +If you use RTL or some of your events contain RTL languages, you will need to pipe [pybidi](https://pypi.org/project/python-bidi/) with the script. Example: `i3-agenda -c ~/.google_credentials.json -ttl 60 | pybidi` ### Caching @@ -120,7 +137,7 @@ interval = 60 ### Example [SwiftBar](https://github.com/swiftbar/SwiftBar) configuration ![example](https://raw.githubusercontent.com/rosenpin/i3-agenda/master/art/mac_screenshot.png) -This will show your next event as the menu bar title, when you press it you will see a dropdown with all your today events +This will show your next event as the menu bar title, when you press it you will see a dropdown with all your today events You can call the file `agenda.2m.sh` to make it refresh every 2 minutes ``` bash #!/bin/bash @@ -132,7 +149,7 @@ echo "---" href="href='https://calendar.google.com/calendar/u/0/r/'" i=1 -while :; do +while :; do event=$(i3-agenda -c ~/.google_credentials.json -ttl 60 --limchar 30 --skip $i --today) ((i++)) if [[ "$event" == "No events" ]];then @@ -205,3 +222,10 @@ if [ -n "${1}" ]; then echo $skip > $file fi ``` + + +[pipx]: https://pypa.github.io/pipx/installation/ +[cred]: https://console.developers.google.com/apis/credentials +[python]: https://developers.google.com/calendar/quickstart/python +[auth]: https://developers.google.com/calendar/auth +[secret]: https://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile diff --git a/i3_agenda/const.py b/i3_agenda/const.py deleted file mode 100644 index 6c067f5..0000000 --- a/i3_agenda/const.py +++ /dev/null @@ -1,17 +0,0 @@ - -from typing_extensions import Final - -DAYS_PER_WEEK : Final = 7 -HOURS_PER_DAY : Final = 24 -SECONDS_PER_DAY : Final = 86400 -SECONDS_PER_HOUR : Final = 3600 -SECONDS_PER_MINUTE : Final = 60 - -URGENT_DELAY_MN : Final = 5 - -MIN_CHARS : Final = -1 -MIN_DELAY : Final = -1 - -LEFT_MOUSE_BUTTON : Final = "1" -RIGHT_MOUSE_BUTTON : Final = "3" - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0f901a3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools", "wheel", "build"] +build-backend = "setuptools.build_meta" + +[project] +name = "i3-agenda" +version = "1.8" +description = "Show your next google calendar event in polybar or i3-bar" +readme = "README.md" +authors = [{ name = "Tomer Rosenfeld", email = "mail@tomerrosenfeld.com" }] +license = {text = "Unlicense"} +classifiers = ["Programming Language :: Python :: 3"] +requires-python = ">=3.3" +dependencies = [ + "aiohttp", + "google-api-python-client", + "google-auth-httplib2", + "google-auth-oauthlib", + "python-bidi", + "typing_extensions" +] + +[project.urls] +Download = "https://github.com/rosenpin/i3-agenda/archive/1.8.tar.gz" + +[project.scripts] +i3-agenda = "i3_agenda.main:main" + +[metadata] +url = "https://github.com/rosenpin/i3-agenda" +author = "Tomer Rosenfeld" +author_email = "mail@tomerrosenfeld.com" + diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..d7d3694 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +flake8 +pytest +freezegun +pytest-cov +black +isort diff --git a/requirements.txt b/requirements.txt index 362a55d..666f6ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,5 +3,4 @@ google-api-python-client>=2.66 google-auth-httplib2>=0.1. google-auth-oauthlib>=0.7 aiohttp>=3.8 -typing_extensions - +typing_extensions>=4.7.1 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100644 index 4f1f6f1..0000000 --- a/setup.py +++ /dev/null @@ -1,41 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="i3-agenda", - version="1.7", - author="Tomer Rosenfeld", - author_email="mail@tomerrosenfeld.com", - description="Show your next google calendar event in polybar or i3-bar", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/rosenpin/i3-agenda", - download_url="https://github.com/rosenpin/i3-agenda/archive/1.7.tar.gz", - packages=setuptools.find_packages(), - license="Unlicense", - classifiers=["Programming Language :: Python :: 3"], - install_requires=[ - "python-bidi", - "google-api-python-client", - "google-auth-httplib2", - "google-auth-oauthlib", - "aiohttp", - ], - scripts=[ - "i3_agenda/API.py", - "i3_agenda/cache_utils.py", - "i3_agenda/config.py", - "i3_agenda/const.py", - "i3_agenda/event.py", - "i3_agenda/helpers.py", - "i3_agenda/i3_agenda.py", - ], - entry_points={ - "console_scripts": [ - "i3-agenda = i3_agenda:main", - ], - }, - python_requires=">=3.3", -) diff --git a/src/i3_agenda/__init__.py b/src/i3_agenda/__init__.py new file mode 100644 index 0000000..a6462e6 --- /dev/null +++ b/src/i3_agenda/__init__.py @@ -0,0 +1,4 @@ +__version__ = "1.8" +__all__ = [ + "__version__", +] diff --git a/i3_agenda/API.py b/src/i3_agenda/api.py similarity index 86% rename from i3_agenda/API.py rename to src/i3_agenda/api.py index 88f43c3..227d61e 100644 --- a/i3_agenda/API.py +++ b/src/i3_agenda/api.py @@ -8,8 +8,9 @@ from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build, Resource -from event import Event, from_json -from config import CONF_DIR +from textwrap import dedent +from i3_agenda.event import Event, from_json +from i3_agenda.config import CONF_DIR SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] TMP_TOKEN = f"{CONF_DIR}/i3agenda_google_token.pickle" @@ -36,7 +37,12 @@ def get_credentials(credspath): if not creds or not creds.valid: if not Path(credspath).is_file(): print( - """You need to download your credentials json file from the Google API Console and pass its path to this script""" + dedent( + """ + You need to download your credentials json file from the Google + API Console and pass its path to this script + """ + ).replace("\n", " ") ) exit(1) if creds and creds.expired and creds.refresh_token: @@ -50,7 +56,9 @@ def get_credentials(credspath): return creds -def get_callendar_ids(allowed_calendars_ids: List[str], service: Resource) -> List: +def get_callendar_ids( + allowed_calendars_ids: List[str], service: Resource +) -> List: calendar_ids = [] while True: calendar_list = service.calendarList().list().execute() @@ -85,7 +93,9 @@ def get_result(service, calendar_id, max_results, time_max_rfc3339=None): def get_today_events(service, calendar_id, max_results): now = datetime.datetime.utcnow() - midnight_rfc3339 = now.replace(hour=23, minute=59, second=59).isoformat() + "Z" + midnight_rfc3339 = ( + now.replace(hour=23, minute=59, second=59).isoformat() + "Z" + ) return get_result(service, calendar_id, max_results, midnight_rfc3339).get( "items", [] ) diff --git a/i3_agenda/cache_utils.py b/src/i3_agenda/cache_utils.py similarity index 82% rename from i3_agenda/cache_utils.py rename to src/i3_agenda/cache_utils.py index 8263cb8..c611708 100644 --- a/i3_agenda/cache_utils.py +++ b/src/i3_agenda/cache_utils.py @@ -1,12 +1,12 @@ -from config import CONF_DIR +from i3_agenda.config import CONF_DIR from typing import Optional, List, TextIO import os.path import time import json -from event import Event, EventEncoder -from const import SECONDS_PER_MINUTE +from i3_agenda.event import Event, EventEncoder +from i3_agenda.const import SECONDS_PER_MINUTE CACHE_PATH = f"{CONF_DIR}/i3agenda_cache.txt" @@ -15,7 +15,10 @@ def load_cache(cachettl: int) -> Optional[List[Event]]: if not os.path.exists(CACHE_PATH): return None - if time.time() - os.path.getmtime(CACHE_PATH) > cachettl * SECONDS_PER_MINUTE: + if ( + time.time() - os.path.getmtime(CACHE_PATH) + > cachettl * SECONDS_PER_MINUTE + ): return None try: diff --git a/i3_agenda/config.py b/src/i3_agenda/config.py similarity index 95% rename from i3_agenda/config.py rename to src/i3_agenda/config.py index aa7b1a9..0aec251 100644 --- a/i3_agenda/config.py +++ b/src/i3_agenda/config.py @@ -1,8 +1,7 @@ import os from os.path import expanduser import argparse -from const import * - +from i3_agenda.const import MIN_DELAY, MIN_CHARS CONF_DIR = expanduser("~") + os.path.sep + ".i3agenda" @@ -40,7 +39,7 @@ "-u", action="store_true", default=False, - help="""when using this flag it will not load previous results from cache, it will however save + help="""when using this flag it will not load previous results from cache, it will however save new results to cache. You can use this flag to refresh all the cache forcefully""", ) parser.add_argument( @@ -56,7 +55,7 @@ "-r", type=int, default=10, - help="""max number of events to query Google's API for each of your calendars. Increase this number if you + help="""max number of events to query Google's API for each of your calendars. Increase this number if you have lot of events in your google calendar""", ) parser.add_argument( diff --git a/i3_agenda/conftest.py b/src/i3_agenda/conftest.py similarity index 100% rename from i3_agenda/conftest.py rename to src/i3_agenda/conftest.py diff --git a/src/i3_agenda/const.py b/src/i3_agenda/const.py new file mode 100644 index 0000000..fb53d74 --- /dev/null +++ b/src/i3_agenda/const.py @@ -0,0 +1,15 @@ +from typing_extensions import Final + +DAYS_PER_WEEK: Final = 7 +HOURS_PER_DAY: Final = 24 +SECONDS_PER_DAY: Final = 86400 +SECONDS_PER_HOUR: Final = 3600 +SECONDS_PER_MINUTE: Final = 60 + +URGENT_DELAY_MN: Final = 5 + +MIN_CHARS: Final = -1 +MIN_DELAY: Final = -1 + +LEFT_MOUSE_BUTTON: Final = "1" +RIGHT_MOUSE_BUTTON: Final = "3" diff --git a/i3_agenda/event.py b/src/i3_agenda/event.py similarity index 76% rename from i3_agenda/event.py rename to src/i3_agenda/event.py index d4d5f7f..b512826 100644 --- a/i3_agenda/event.py +++ b/src/i3_agenda/event.py @@ -7,13 +7,22 @@ from bidi.algorithm import get_display -from config import MIN_CHARS, MIN_DELAY, URL_REGEX -from const import * -from helpers import get_unix_time, human_delta +from i3_agenda.config import ( + MIN_CHARS, + MIN_DELAY, + URL_REGEX, +) + +from i3_agenda.const import ( + SECONDS_PER_MINUTE, + DAYS_PER_WEEK, + URGENT_DELAY_MN, + SECONDS_PER_DAY, +) +from i3_agenda.helpers import get_unix_time, human_delta from dataclasses import dataclass - @dataclass class Event: summary: str @@ -21,7 +30,6 @@ class Event: end_time: int location: Union[str, None] - def get_datetime(self) -> dt.datetime: return dt.datetime.fromtimestamp(self.start_time) @@ -68,7 +76,9 @@ def get_string( def is_ongoing(self) -> bool: now = dt.datetime.now() - ongoing = now > self.get_datetime() and not now > self.get_end_datetime() + ongoing = ( + now > self.get_datetime() and not now > self.get_end_datetime() + ) return ongoing def is_today(self) -> bool: @@ -84,11 +94,12 @@ def is_this_week(self) -> bool: next_week = today + dt.timedelta(days=DAYS_PER_WEEK) return today.date() <= self.get_datetime().date() < next_week.date() - def is_urgent(self) -> bool: now = dt.datetime.now() urgent = now + dt.timedelta(minutes=URGENT_DELAY_MN) - five_minutes_started = self.get_datetime() + dt.timedelta(minutes=URGENT_DELAY_MN) + five_minutes_started = self.get_datetime() + dt.timedelta( + minutes=URGENT_DELAY_MN + ) # is urgent if it begins in URGENT_DELAY_MN minutes and if it hasn't # passed URGENT_DELAY_MN minutes it started return self.get_datetime() < urgent and not now > five_minutes_started @@ -97,10 +108,11 @@ def is_allday(self) -> bool: time_delta = self.end_time - self.start_time # event is considered all day if its start time and end time are both 00:00:00 # and the time difference between start and finish is divisible by 24 - return self.get_datetime().time() == dt.time(0) \ - and self.get_end_datetime().time() == dt.time(0) \ - and time_delta % SECONDS_PER_DAY == 0 - + return ( + self.get_datetime().time() == dt.time(0) + and self.get_end_datetime().time() == dt.time(0) + and time_delta % SECONDS_PER_DAY == 0 + ) class EventEncoder(json.JSONEncoder): @@ -111,13 +123,13 @@ def default(self, o): # pylint: disable=E0202 return json.JSONEncoder.default(self, o) - - def sort_events(events: List[Event]) -> List[Event]: return sorted(events, key=lambda e: e.start_time, reverse=False) -def get_future_events(events: List[Event], hide_event_after: int, show_event_before: int) -> List[Event]: +def get_future_events( + events: List[Event], hide_event_after: int, show_event_before: int +) -> List[Event]: future_events = [] now = time.time() @@ -132,11 +144,17 @@ def get_future_events(events: List[Event], hide_event_after: int, show_event_bef continue # Event won't start for more than show_event_before - if show_event_before > MIN_DELAY and now + SECONDS_PER_MINUTE * show_event_before < event.start_time: + if ( + show_event_before > MIN_DELAY + and now + SECONDS_PER_MINUTE * show_event_before < event.start_time + ): continue # If the event started more than hide_event_after ago - if hide_event_after > MIN_DELAY and event.start_time + SECONDS_PER_MINUTE * hide_event_after < now: + if ( + hide_event_after > MIN_DELAY + and event.start_time + SECONDS_PER_MINUTE * hide_event_after < now + ): continue future_events.append(event) @@ -153,13 +171,15 @@ def get_closest(events: List[Event]) -> Optional[Event]: return closest - - -def from_json(event_json : Dict[str,Any]) -> Event: - end_time = int(get_unix_time( - event_json["end"].get("dateTime", event_json["end"].get("date"))) +def from_json(event_json: Dict[str, Any]) -> Event: + end_time = int( + get_unix_time( + event_json["end"].get("dateTime", event_json["end"].get("date")) + ) + ) + start_time = event_json["start"].get( + "dateTime", event_json["start"].get("date") ) - start_time = event_json["start"].get("dateTime", event_json["start"].get("date")) start_time = int(get_unix_time(start_time)) location = None @@ -169,4 +189,6 @@ def from_json(event_json : Dict[str,Any]) -> Event: elif "description" in event_json: matches = re.findall(URL_REGEX, event_json["description"]) location = matches[0][0] if matches else None - return Event(event_json.get("summary", "(No title)"), start_time, end_time, location) + return Event( + event_json.get("summary", "(No title)"), start_time, end_time, location + ) diff --git a/i3_agenda/helpers.py b/src/i3_agenda/helpers.py similarity index 65% rename from i3_agenda/helpers.py rename to src/i3_agenda/helpers.py index 07032eb..d3d4f8a 100644 --- a/i3_agenda/helpers.py +++ b/src/i3_agenda/helpers.py @@ -1,13 +1,16 @@ import datetime as dt import time -from typing_extensions import LiteralString -from const import * +from i3_agenda.const import ( + SECONDS_PER_DAY, + SECONDS_PER_HOUR, + SECONDS_PER_MINUTE, +) -def human_delta(tdelta : dt.timedelta) -> str: - duration = [ 0 ] * 4 # will hold decomposition of tdelta in d, h, m, s - fmts = ['{d[0]} day(s)', '{d[1]}h', '{d[2]}m', '{d[3]}s'] +def human_delta(tdelta: dt.timedelta) -> str: + duration = [0] * 4 # will hold decomposition of tdelta in d, h, m, s + fmts = ["{d[0]} day(s)", "{d[1]}h", "{d[2]}m", "{d[3]}s"] total_seconds = int(tdelta.total_seconds()) @@ -17,15 +20,14 @@ def human_delta(tdelta : dt.timedelta) -> str: duration[2], duration[3] = divmod(rem, SECONDS_PER_MINUTE) # Keep only format for non null value - fmt = ' '.join([ fmts[i] for i in range(len(duration)) if duration[i] > 0]) + fmt = " ".join([fmts[i] for i in range(len(duration)) if duration[i] > 0]) if not fmt: return "0m" - return fmt.format(d = duration) + return fmt.format(d=duration) - -def make_tz_backward_compatible(full_time : str) -> str: +def make_tz_backward_compatible(full_time: str) -> str: # Python introduced the ability to parse ":" in the timezone format (in strptime()) only from version 3.7 and up. # We need to remove the : before the timezone to support older versions # See https://stackoverflow.com/questions/30999230/how-to-parse-timezone-with-colon for more information @@ -33,6 +35,7 @@ def make_tz_backward_compatible(full_time : str) -> str: full_time = full_time[:-3] + full_time[-2:] return full_time + def get_unix_time(full_time: str) -> float: if "T" in full_time: event_time_format = "%Y-%m-%dT%H:%M:%S%z" @@ -42,5 +45,7 @@ def get_unix_time(full_time: str) -> float: full_time = make_tz_backward_compatible(full_time) return time.mktime( - dt.datetime.strptime(full_time, event_time_format).astimezone().timetuple() + dt.datetime.strptime(full_time, event_time_format) + .astimezone() + .timetuple() ) diff --git a/i3_agenda/i3_agenda.py b/src/i3_agenda/main.py similarity index 76% rename from i3_agenda/i3_agenda.py rename to src/i3_agenda/main.py index 8eb080d..f8a6344 100644 --- a/i3_agenda/i3_agenda.py +++ b/src/i3_agenda/main.py @@ -1,17 +1,18 @@ -#!/usr/bin/env python3 - from __future__ import print_function import subprocess -import config +from i3_agenda import config from typing import List, Optional import datetime -from event import Event, get_closest, sort_events, get_future_events +from i3_agenda.event import Event, get_closest, sort_events, get_future_events from typing import Union -from const import * +from i3_agenda.const import ( + LEFT_MOUSE_BUTTON, + RIGHT_MOUSE_BUTTON, +) DEFAULT_CAL_WEBPAGE = "https://calendar.google.com/calendar/r/day" @@ -22,7 +23,7 @@ def button_action(button_code: str, closest: Event): print("Opening calendar page...") subprocess.Popen(["xdg-open", DEFAULT_CAL_WEBPAGE]) elif button_code == RIGHT_MOUSE_BUTTON: - if closest.location: + if closest.location: print("Opening location link...") subprocess.Popen(["xdg-open", closest.location]) @@ -40,10 +41,10 @@ def filter_only_todays_events(events: List[Event]) -> Optional[List[Event]]: def load_events(args) -> List[Event]: - from API import get_events - from cache_utils import load_cache, save_cache + from i3_agenda.api import get_events + from i3_agenda.cache_utils import load_cache, save_cache - events : Union[None,list[Event]] = None + events: Union[None, list[Event]] = None if not args.update: events = load_cache(args.cachettl) @@ -52,7 +53,9 @@ def load_events(args) -> List[Event]: events = filter_only_todays_events(events) if events is None or args.update: - events = get_events(args.credentials, args.ids, args.maxres, args.today) + events = get_events( + args.credentials, args.ids, args.maxres, args.today + ) save_cache(events) return events @@ -63,11 +66,13 @@ def main(): events = load_events(args) - events = get_future_events(events, args.hide_event_after, args.show_event_before) + events = get_future_events( + events, args.hide_event_after, args.show_event_before + ) if args.skip > 0: events = sort_events(events) - events = events[args.skip :] + events = events[args.skip:] closest = get_closest(events) if closest is None: diff --git a/i3_agenda/tests/test_event.py b/tests/test_event.py similarity index 99% rename from i3_agenda/tests/test_event.py rename to tests/test_event.py index 006f0c2..1ad10ce 100644 --- a/i3_agenda/tests/test_event.py +++ b/tests/test_event.py @@ -2,11 +2,16 @@ import os import time -from freezegun import freeze_time import pytest +from freezegun import freeze_time -from event import * - +from i3_agenda.event import ( + MIN_DELAY, + Event, + from_json, + get_closest, + get_future_events, +) os.environ['TZ'] = 'UTC' time.tzset() diff --git a/i3_agenda/tests/test_helpers.py b/tests/test_helpers.py similarity index 93% rename from i3_agenda/tests/test_helpers.py rename to tests/test_helpers.py index 21882d0..c021d2a 100644 --- a/i3_agenda/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -5,7 +5,7 @@ from typing import Dict -from helpers import * +from i3_agenda.helpers import * @pytest.mark.parametrize("test_input,expected",