feat: Add .gitignore, LICENSE, README, pyproject.toml, and kalente source files
- Added .gitignore file to specify files and directories to ignore in version control. - Added LICENSE file with MIT license. - Added README.md file with project information and license details. - Added pyproject.toml file with project metadata and dependencies. - Added kalente source files for generating PDF calendars. - Implemented functions for generating weekly calendars, converting HTML to PDF, and CLI parsing. - Created a template for rendering weekly calendars in HTML format.
This commit is contained in:
commit
60cfcc70dd
7 changed files with 242 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
venv/
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
*.pdf
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2023 Kumi Mitterer <kalente@kumi.email>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Kalente
|
||||||
|
|
||||||
|
Kalente is a simple Python script for generating PDF calendars. It can be used
|
||||||
|
to generate weekly calendars at the moment, but it will be extended to support
|
||||||
|
monthly and yearly calendars as well.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Kalente is licensed under the MIT license. See the `LICENSE` file for more
|
||||||
|
information.
|
32
pyproject.toml
Normal file
32
pyproject.toml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "kalente"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
{ name="Kumi Mitterer", email="kalente@kumi.email" },
|
||||||
|
]
|
||||||
|
description = "Simple Python script to generate a printable calendar"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { file="LICENSE" }
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"holidays",
|
||||||
|
"pdfkit",
|
||||||
|
"jinja2",
|
||||||
|
"python-dateutil",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
"Homepage" = "https://kumig.it/kumitterer/kalente"
|
||||||
|
"Bug Tracker" = "https://kumig.it/kumitterer/kalente/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
kalente = "kalente.__main__:main"
|
0
src/kalente/__init__.py
Normal file
0
src/kalente/__init__.py
Normal file
121
src/kalente/__main__.py
Normal file
121
src/kalente/__main__.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import holidays
|
||||||
|
import pdfkit
|
||||||
|
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
from dateutil.parser import parse
|
||||||
|
|
||||||
|
|
||||||
|
def get_week(
|
||||||
|
for_date: date = None,
|
||||||
|
country_code: Optional[str] = None,
|
||||||
|
date_format: str = "%b %d, %Y",
|
||||||
|
):
|
||||||
|
week_days = []
|
||||||
|
|
||||||
|
for_date = for_date or date.today()
|
||||||
|
week_start = for_date - timedelta(days=for_date.weekday())
|
||||||
|
week_end = week_start + timedelta(days=6)
|
||||||
|
|
||||||
|
if country_code:
|
||||||
|
holiday_list = holidays.CountryHoliday(
|
||||||
|
country_code, years=[for_date.year, week_end.year, week_start.year]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
holiday_list = {}
|
||||||
|
|
||||||
|
for i in range(7):
|
||||||
|
day = week_start + timedelta(days=i)
|
||||||
|
day_info = {
|
||||||
|
"day": day.strftime("%A"),
|
||||||
|
"date": day.strftime(date_format),
|
||||||
|
"holiday": holiday_list.get(day),
|
||||||
|
"is_weekend": (day.weekday() in [5, 6]),
|
||||||
|
}
|
||||||
|
week_days.append(day_info)
|
||||||
|
return week_days
|
||||||
|
|
||||||
|
|
||||||
|
def generate_weekly_html(week):
|
||||||
|
file_loader = FileSystemLoader(Path(__file__).parent.absolute() / "templates")
|
||||||
|
env = Environment(loader=file_loader)
|
||||||
|
template = env.get_template("weekly.html")
|
||||||
|
return template.render(week=week)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_html_to_pdf(content, output_filename):
|
||||||
|
options = {
|
||||||
|
"page-size": "A4",
|
||||||
|
"orientation": "Landscape",
|
||||||
|
}
|
||||||
|
pdfkit.from_string(content, output_filename, options=options)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--country",
|
||||||
|
"-c",
|
||||||
|
help="Country code for the holidays",
|
||||||
|
required=False,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output", "-o", help="Output filename", required=False, default="calendar.pdf"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--date",
|
||||||
|
"-d",
|
||||||
|
help="Date to generate the calendar for",
|
||||||
|
required=False,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--date-format",
|
||||||
|
"-f",
|
||||||
|
help="Date format to use",
|
||||||
|
required=False,
|
||||||
|
default="%b %d, %Y",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--type",
|
||||||
|
"-t",
|
||||||
|
help="Type of calendar to generate",
|
||||||
|
required=False,
|
||||||
|
default="weekly",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.country:
|
||||||
|
country_code = args.country.upper()
|
||||||
|
assert (
|
||||||
|
country_code in holidays.list_supported_countries()
|
||||||
|
), f"Country code {country_code} is not supported"
|
||||||
|
|
||||||
|
else:
|
||||||
|
country_code = None
|
||||||
|
|
||||||
|
if args.date:
|
||||||
|
try:
|
||||||
|
for_date = parse(args.date).date()
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"Unrecognized date format {args.date}")
|
||||||
|
else:
|
||||||
|
for_date = None
|
||||||
|
|
||||||
|
if args.type != "weekly":
|
||||||
|
raise NotImplementedError(f"Calendar type {args.type} is not supported")
|
||||||
|
|
||||||
|
week = get_week(for_date, country_code, args.date_format)
|
||||||
|
html_content = generate_weekly_html(week)
|
||||||
|
convert_html_to_pdf(html_content, args.output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
56
src/kalente/templates/weekly.html
Normal file
56
src/kalente/templates/weekly.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: large;
|
||||||
|
background-color: #dddddd;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
height: 675px;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.holiday {
|
||||||
|
background-color: #ff9999; /* For holidays - Red */
|
||||||
|
}
|
||||||
|
.saturday {
|
||||||
|
background-color: #99ccff; /* For Saturday - Blue */
|
||||||
|
}
|
||||||
|
.sunday {
|
||||||
|
background-color: #99ff99; /* For Sunday - Green */
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Weekly Calendar</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
{% for day in week %}
|
||||||
|
<th>{{ day.day }}<br>{{ day.date }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for day in week %}
|
||||||
|
<td class="{% if day.holiday %}holiday{% elif day.is_weekend and day.day == 'Saturday' %}saturday{% elif day.is_weekend and day.day == 'Sunday' %}sunday{% endif %}">
|
||||||
|
{% if day.holiday %}
|
||||||
|
<b>{{ day.holiday }}</b>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue