#!/usr/bin/perl use v5.36; use utf8; use open qw(:std :encoding(UTF-8)); use Feature::Compat::Try; use FindBin qw($Bin); use lib "$Bin/../lib"; use Getopt::Complete ( 'quiet!' => undef, 'verbose!' => undef, 'debug!' => undef, 'trace!' => undef, 'output' => 'files', 'skeldir' => 'directories', 'username' => undef, 'password' => undef, 'locale' => undef, 'timezone' => undef, 'title' => undef, '<>' => undef, ); use IO::Interactive::Tiny; use Log::Any qw($log); use Log::Any::Adapter; use URI; use DateTime; use Path::Tiny 0.119 qw(path tempdir); use Text::Xslate; use POSIX qw(locale_h); # resolve LC_TIME use locale; use DateTime::TimeZone; use LaTeX::Driver; use Object::Groupware::DAV; use Object::Groupware::Calendar; # collect settings from command-line options and defaults my $SKELDIR = $ARGS{skeldir} || $ENV{SKELDIR} || "$Bin/../templates"; my $BASE_URI = $ARGS{'<>'}[0] || $ENV{CAL_DAV_URL_BASE}; my $CALENDAR_URI = $ARGS{'<>'}[1] || $ENV{CAL_DAV_URL_CALENDAR}; my $USERNAME = $ARGS{username} || $ENV{CAL_DAV_USER}; my $PASSWORD = $ARGS{password} || $ENV{CAL_DAV_PASS}; my $LOCALE = $ARGS{locale} || $ENV{CAL_LANG}; my $TIME_ZONE = $ARGS{timezone}; my $OUTPUT_FILE = $ARGS{output}; # init logging my $LOGLEVEL = 'warning'; $LOGLEVEL = 'critical' if $ARGS{quiet}; $LOGLEVEL = 'warning' if defined $ARGS{verbose} and !$ARGS{verbose}; $LOGLEVEL = 'info' if $ARGS{verbose}; $LOGLEVEL = 'debug' if $ARGS{debug}; $LOGLEVEL = 'trace' if $ARGS{trace}; if ( IO::Interactive::Tiny::is_interactive() ) { Log::Any::Adapter->set( 'Screen', default_level => $LOGLEVEL ); } else { use Log::Any::Adapter ( 'Stderr', default_level => $LOGLEVEL ); } # init groupware settings my $dt_locale = DateTime::Locale->load( $LOCALE || setlocale(LC_TIME) ); my %GROUPWARE_OPTIONS = ( dt_locale => $dt_locale, dt_time_zone => DateTime::TimeZone->new( name => ( $ARGS{timezone} || 'local' ), ), ); $log->infof( 'Will use locale %s and time zone %s', $GROUPWARE_OPTIONS{dt_locale}->code, $GROUPWARE_OPTIONS{dt_time_zone}->name, ); # init calendar URIs $BASE_URI = URI->new($BASE_URI) or $log->fatal('failed to parse required base URI') && exit 2; $BASE_URI->scheme or $BASE_URI->scheme('file'); # get calendar my $calendar; if ( $BASE_URI->scheme eq 'http' or $BASE_URI->scheme eq 'https' ) { $log->infof( 'will use base URI %s', $BASE_URI ); $CALENDAR_URI = URI->new( $CALENDAR_URI || $BASE_URI ); $CALENDAR_URI and $CALENDAR_URI->authority or $log->fatal('bad calendar URI: must be an internet URI') && exit 2; $BASE_URI->eq($CALENDAR_URI) and $CALENDAR_URI = undef or $log->infof( 'will use calendar URI %s', $CALENDAR_URI ); my $session = Object::Groupware::DAV->new( user => $USERNAME, pass => $PASSWORD, uri => $BASE_URI, %GROUPWARE_OPTIONS, ); $calendar = $session->get($CALENDAR_URI); } elsif ( $BASE_URI->scheme eq 'file' ) { defined $BASE_URI->file or $log->fatal('bad base URI: cannot open file') && exit 2; $log->infof( 'will use base URI %s', $BASE_URI ); # parse local calendar data $log->debug('parse local calendar data...'); my $path = path( $BASE_URI->file ); if ( $path->is_file ) { $calendar = Object::Groupware::Calendar->new( filename => "$path", %GROUPWARE_OPTIONS, ); } else { my $data; $path->visit( sub { $data .= $_->slurp_raw if $_->is_file } ); $calendar = Object::Groupware::Calendar->new( data => $data, %GROUPWARE_OPTIONS, ); } } # select subset of calendar events $log->debug('serialize calendar events...'); my $start; if ( $ENV{CAL_DAV_NOW} ) { try { require DateTimeX::Easy } catch ($e) { $log->fatalf( 'failed parsing CAL_DAV_NOW: %s', $e ) && exit 2 } $start = DateTimeX::Easy->new( $ENV{CAL_DAV_NOW} ); $log->fatalf( 'failed parsing CAL_DAV_NOW: unknown start time "%s"', $ENV{CAL_DAV_NOW} ) && exit 2 unless defined $start; } $start ||= DateTime->now; my $end = $start->clone->add( months => 6 ); my $span = DateTime::Span->from_datetimes( start => $start, end => $end ); my @events = $calendar->events($span); # serialize calendar view my %vars; #$vars{metadata} = $calendar->metadata(); $vars{name} = $ARGS{title} || ''; for (@events) { next unless $_->summary; push @{ $vars{events} }, $_; } my %tmpl; $tmpl{plan} = path($SKELDIR)->child('semesterplan.tex')->slurp_utf8; $tmpl{list} = path($SKELDIR)->child('semesterplan-list.events')->slurp_utf8; my $template = Text::Xslate->new( path => \%tmpl, syntax => 'TTerse', type => 'text', ); my $content_plan = $template->render( 'plan', \%vars ); my $content_list = $template->render( 'list', \%vars ); my $tempdir = tempdir( CLEANUP => !$log->is_debug ); my $srcfile = $tempdir->child('plan.tex'); my $pdffile = $tempdir->child('plan.pdf'); if ( $log->is_debug ) { $log->warnf( '[debug] temporary directory %s will not be cleaned', $tempdir ); } $srcfile->append_utf8($content_plan); $tempdir->child('list.events')->append_utf8($content_list); my $drv = LaTeX::Driver->new( source => "$srcfile", output => "$pdffile", format => 'pdf(lualatex)', DEBUG => $log->is_debug, -capture_stderr, ); $drv->run or $log->fatalf( 'failed to generate PDF file: %s', $drv->stderr ) && exit 2; if ($OUTPUT_FILE) { $OUTPUT_FILE = path($OUTPUT_FILE); $OUTPUT_FILE->parent->mkpath; $pdffile->copy($OUTPUT_FILE); } else { print $pdffile->slurp_raw; } 1;