aboutsummaryrefslogtreecommitdiff
path: root/bin/events2semesterplan.pl
blob: 2df85adb57606c73bfea91f3528710629ea035b6 (plain)
  1. #!/usr/bin/perl
  2. use v5.36;
  3. use utf8;
  4. use open qw(:std :encoding(UTF-8));
  5. use Feature::Compat::Try;
  6. use FindBin qw($Bin);
  7. use lib "$Bin/../lib";
  8. use Getopt::Complete (
  9. 'quiet!' => undef,
  10. 'verbose!' => undef,
  11. 'debug!' => undef,
  12. 'trace!' => undef,
  13. 'output' => 'files',
  14. 'skeldir' => 'directories',
  15. 'username' => undef,
  16. 'password' => undef,
  17. 'locale' => undef,
  18. 'timezone' => undef,
  19. 'title' => undef,
  20. '<>' => undef,
  21. );
  22. use IO::Interactive::Tiny;
  23. use Log::Any qw($log);
  24. use Log::Any::Adapter;
  25. use URI;
  26. use DateTime;
  27. use Path::Tiny 0.119 qw(path tempdir);
  28. use Text::Xslate;
  29. use POSIX qw(locale_h); # resolve LC_TIME
  30. use locale;
  31. use DateTime::TimeZone;
  32. use LaTeX::Encode qw(latex_encode);
  33. use LaTeX::Driver;
  34. use Object::Groupware::DAV;
  35. use Object::Groupware::Calendar;
  36. # collect settings from command-line options and defaults
  37. my $SKELDIR = $ARGS{skeldir} || $ENV{SKELDIR} || "$Bin/../templates";
  38. my $BASE_URI = $ARGS{'<>'}[0] || $ENV{CAL_DAV_URL_BASE};
  39. my $CALENDAR_URI = $ARGS{'<>'}[1] || $ENV{CAL_DAV_URL_CALENDAR};
  40. my $USERNAME = $ARGS{username} || $ENV{CAL_DAV_USER};
  41. my $PASSWORD = $ARGS{password} || $ENV{CAL_DAV_PASS};
  42. my $LOCALE = $ARGS{locale} || $ENV{CAL_LANG};
  43. my $TIME_ZONE = $ARGS{timezone};
  44. my $OUTPUT_FILE = $ARGS{output};
  45. # init logging
  46. my $LOGLEVEL = 'warning';
  47. $LOGLEVEL = 'critical' if $ARGS{quiet};
  48. $LOGLEVEL = 'warning' if defined $ARGS{verbose} and !$ARGS{verbose};
  49. $LOGLEVEL = 'info' if $ARGS{verbose};
  50. $LOGLEVEL = 'debug' if $ARGS{debug};
  51. $LOGLEVEL = 'trace' if $ARGS{trace};
  52. if ( IO::Interactive::Tiny::is_interactive() ) {
  53. Log::Any::Adapter->set( 'Screen', default_level => $LOGLEVEL );
  54. }
  55. else {
  56. use Log::Any::Adapter ( 'Stderr', default_level => $LOGLEVEL );
  57. }
  58. # init groupware settings
  59. my $dt_locale = DateTime::Locale->load( $LOCALE || setlocale(LC_TIME) );
  60. my %GROUPWARE_OPTIONS = (
  61. dt_locale => $dt_locale,
  62. dt_time_zone => DateTime::TimeZone->new(
  63. name => ( $ARGS{timezone} || 'local' ),
  64. ),
  65. );
  66. $log->infof(
  67. 'Will use locale %s and time zone %s',
  68. $GROUPWARE_OPTIONS{dt_locale}->code,
  69. $GROUPWARE_OPTIONS{dt_time_zone}->name,
  70. );
  71. # init calendar URIs
  72. $BASE_URI = URI->new($BASE_URI)
  73. or $log->fatal('failed to parse required base URI') && exit 2;
  74. $BASE_URI->scheme
  75. or $BASE_URI->scheme('file');
  76. # get calendar
  77. my $calendar;
  78. if ( $BASE_URI->scheme eq 'http' or $BASE_URI->scheme eq 'https' ) {
  79. $log->infof( 'will use base URI %s', $BASE_URI );
  80. $CALENDAR_URI = URI->new( $CALENDAR_URI || $BASE_URI );
  81. $CALENDAR_URI and $CALENDAR_URI->authority
  82. or $log->fatal('bad calendar URI: must be an internet URI') && exit 2;
  83. $BASE_URI->eq($CALENDAR_URI) and $CALENDAR_URI = undef
  84. or $log->infof( 'will use calendar URI %s', $CALENDAR_URI );
  85. my $session = Object::Groupware::DAV->new(
  86. user => $USERNAME,
  87. pass => $PASSWORD,
  88. uri => $BASE_URI,
  89. %GROUPWARE_OPTIONS,
  90. );
  91. $calendar = $session->get($CALENDAR_URI);
  92. }
  93. elsif ( $BASE_URI->scheme eq 'file' ) {
  94. defined $BASE_URI->file
  95. or $log->fatal('bad base URI: cannot open file') && exit 2;
  96. $log->infof( 'will use base URI %s', $BASE_URI );
  97. # parse local calendar data
  98. $log->debug('parse local calendar data...');
  99. my $path = path( $BASE_URI->file );
  100. if ( $path->is_file ) {
  101. $calendar = Object::Groupware::Calendar->new(
  102. filename => "$path",
  103. %GROUPWARE_OPTIONS,
  104. );
  105. }
  106. else {
  107. my $data;
  108. $path->visit( sub { $data .= $_->slurp_raw if $_->is_file } );
  109. $calendar = Object::Groupware::Calendar->new(
  110. data => $data,
  111. %GROUPWARE_OPTIONS,
  112. );
  113. }
  114. }
  115. # select subset of calendar events
  116. $log->debug('serialize calendar events...');
  117. my $start;
  118. if ( $ENV{CAL_DAV_NOW} ) {
  119. try { require DateTimeX::Easy }
  120. catch ($e) {
  121. $log->fatalf( 'failed parsing CAL_DAV_NOW: %s', $e ) && exit 2
  122. }
  123. $start = DateTimeX::Easy->new( $ENV{CAL_DAV_NOW} );
  124. $log->fatalf(
  125. 'failed parsing CAL_DAV_NOW: unknown start time "%s"',
  126. $ENV{CAL_DAV_NOW}
  127. )
  128. && exit 2
  129. unless defined $start;
  130. }
  131. $start ||= DateTime->now;
  132. my $end = $start->clone->add( months => 6 );
  133. my $span = DateTime::Span->from_datetimes( start => $start, end => $end );
  134. my @events = $calendar->events($span);
  135. # serialize calendar view
  136. my %vars;
  137. #$vars{metadata} = $calendar->metadata();
  138. $vars{name} = latex_encode( $ARGS{title} ) || '';
  139. for (@events) {
  140. next unless $_->summary;
  141. push @{ $vars{events} }, {
  142. start_date => $_->start_date,
  143. summary => latex_encode( $_->summary ),
  144. };
  145. }
  146. my %tmpl;
  147. $tmpl{plan} = path($SKELDIR)->child('semesterplan.tex')->slurp_utf8;
  148. $tmpl{list} = path($SKELDIR)->child('semesterplan-list.events')->slurp_utf8;
  149. my $template = Text::Xslate->new(
  150. path => \%tmpl,
  151. syntax => 'TTerse',
  152. type => 'text',
  153. );
  154. my $content_plan = $template->render( 'plan', \%vars );
  155. my $content_list = $template->render( 'list', \%vars );
  156. my $tempdir = tempdir( CLEANUP => !$log->is_debug );
  157. my $srcfile = $tempdir->child('plan.tex');
  158. my $pdffile = $tempdir->child('plan.pdf');
  159. if ( $log->is_debug ) {
  160. $log->warnf(
  161. '[debug] temporary directory %s will not be cleaned',
  162. $tempdir
  163. );
  164. }
  165. $srcfile->append_utf8($content_plan);
  166. $tempdir->child('list.events')->append_utf8($content_list);
  167. my $drv = LaTeX::Driver->new(
  168. source => "$srcfile",
  169. output => "$pdffile",
  170. format => 'pdf(lualatex)',
  171. DEBUG => $log->is_debug,
  172. -capture_stderr,
  173. );
  174. $drv->run
  175. or $log->fatalf( 'failed to generate PDF file: %s', $drv->stderr )
  176. && exit 2;
  177. if ($OUTPUT_FILE) {
  178. $OUTPUT_FILE = path($OUTPUT_FILE);
  179. $OUTPUT_FILE->parent->mkpath;
  180. $pdffile->copy($OUTPUT_FILE);
  181. }
  182. else {
  183. print $pdffile->slurp_raw;
  184. }
  185. 1;