#!/usr/bin/env python3 import argparse import calendar import datetime import locale import logging import os import shutil import subprocess import sys from typing import Optional try: from lesana.command import _get_first_docstring_line # type: ignore except ImportError: def _get_first_docstring_line(obj): return "" try: import argcomplete # type: ignore except ImportError: argcomplete = False import jinja2 locale.setlocale(locale.LC_ALL, '') class Generator: """ """ default_template = "planner-A6" def __init__( self, year: Optional[int] = None, template: Optional[str] = None, out_file: Optional[str] = None, build_dir: Optional[str] = "build", ): self.year = year or ( datetime.date.today() + datetime.timedelta(days=334) ).year self.out_file = out_file or self.default_template + ".pdf" env = jinja2.Environment() self.templates_dir = "templates" loader = jinja2.FileSystemLoader(self.templates_dir) if not template: template = self.default_template self.template_recto = loader.load(env, template + "-r.svg") self.template_verso = loader.load(env, template + "-v.svg") self.template_cover = loader.load(env, "cover-A6-r.svg") self.build_dir = build_dir self.page_fname = os.path.join( self.build_dir, template + "-{year}-{page:03}.svg" ) def render_page(self, page: int, **kw): # page counts starts with 0 if page == 0: template = self.template_cover elif page % 2 == 0: template = self.template_recto else: template = self.template_verso with open(self.page_fname.format( year=self.year, page=page ), "w") as fp: fp.write(template.render(**kw)) def run(self): self.clean_build_dir() self.generate_cover_page() self.generate_pages() self.convert_pages_to_svg() self.join_pages() def clean_build_dir(self): try: shutil.rmtree(self.build_dir) except FileNotFoundError: pass os.makedirs(self.build_dir) def generate_cover_page(self): self.render_page(page=0, year=self.year) def generate_pages(self): pass def convert_pages_to_svg(self): inkscape_commands = ";\n".join([ ( "file-open:{build_dir}/{svg};" + " export-type: pdf;" + " export-filename:build/{pdf};" + " export-text-to-path;" + " export-do" ).format( build_dir=self.build_dir, svg=s, pdf=os.path.splitext(s)[0] + ".pdf", ) for s in os.listdir(self.build_dir) ]) try: subprocess.run( ["inkscape", "--shell"], input=inkscape_commands, text=True, ) except FileNotFoundError: logging.warning("Inkscape is not installed, can't convert to pdf") logging.warning("Stopping here, you can use the svgs as you like") sys.exit(1) def get_pdf_pages(self): pdf_pages = sorted([ os.path.join(self.build_dir, p) for p in os.listdir(self.build_dir) if p.endswith(".pdf") ]) return pdf_pages def join_pages(self): pdf_pages = self.get_pdf_pages() try: subprocess.run([ "pdfjam", "--outfile", self.out_file, *pdf_pages ]) except FileNotFoundError: logging.warning("pdfjam is not installed") logging.warning("you will have to join the pdf pages yourself") sys.exit(1) class WeeklyGenerator(Generator): default_template = "week_on_two_pages-A6" def generate_pages(self): cal = calendar.Calendar() weeks = sum( [r[0] for r in cal.yeardatescalendar(self.year, width=1)], [] ) last_monday = None page = 1 for week in weeks: # yeardatescalendar will have the same week twice at the # margin of a month, but we want to skip one of those if week[0] == last_monday: continue last_monday = week[0] self.render_page(page=page, week=week) page += 1 self.render_page(page=page, week=week) page += 1 class DailyGenerator(Generator): default_template = "daily-A6" def generate_pages(self): day = datetime.date(self.year, 1, 1) # we want to start with a left side page (starting from 0) page = 2 while day.year == self.year: self.render_page(page=page, day=day) page += 1 day += datetime.timedelta(days=1) if day.year > self.year: break self.render_page(page=page, day=day) page += 1 day += datetime.timedelta(days=1) def get_pdf_pages(self): pdf_pages = super().get_pdf_pages() # insert an empty page on the second page, to start the year on # a left page pdf_pages.insert(1, "1, {}") return pdf_pages class MonthGenerator(Generator): """ """ default_template = "month-A6" def generate_pages(self): cal = calendar.Calendar() full_year = cal.yeardatescalendar(self.year, width=1) months = [] for i in range(12): months.append([ day for week in full_year[i][0] for day in week if day.month == i + 1 ]) page = 1 for month in months: self.render_page(page=page, month=month, text=[]) page += 1 class Command: """ Generate a planner """ def get_parser(self): desc = _get_first_docstring_line(self) parser = argparse.ArgumentParser(description=desc) parser.add_argument( "--year", '-y', default=None, help="Default is next year, or this year in January." ) parser.add_argument( "--template", '-t', default=None, help="Base name of the template (without -[rv].svg)", ) parser.add_argument( "command", ) return parser def main(self): self.parser = self.get_parser() if argcomplete: argcomplete.autocomplete(self.parser) self.args = self.parser.parse_args() generator = getattr( sys.modules[__name__], self.args.command.capitalize() + "Generator", None ) if generator: generator( year=self.args.year, template=self.args.template, ).run() else: print("command not supported: {}".format(self.args.command)) if __name__ == "__main__": Command().main()