aboutsummaryrefslogtreecommitdiff
path: root/bin/events2md.pl
blob: b0772d3f3ff3f8f3b20509474140ebf488990ef9 (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 IO::Interactive::Tiny;
  9. use Log::Any qw($log);
  10. use Log::Any::Adapter;
  11. use URI;
  12. use DateTime;
  13. use Path::Tiny;
  14. use Text::Xslate;
  15. use POSIX qw(locale_h); # resolve LC_TIME
  16. use locale;
  17. use DateTime::TimeZone;
  18. use Object::Groupware::DAV;
  19. use Object::Groupware::Calendar;
  20. if ( IO::Interactive::Tiny::is_interactive() ) {
  21. Log::Any::Adapter->set( 'Screen', default_level => 'info' );
  22. }
  23. else {
  24. use Log::Any::Adapter ( 'Stderr', default_level => 'info' );
  25. }
  26. # set defaults and parse command-line options
  27. my ($BASE_URI, $CALENDAR_URI, $SKELDIR, $OUTPUT_FILE, $CALENDAR_LANG,
  28. $CALENDAR_TIME_ZONE, %GROUPWARE_OPTIONS
  29. );
  30. $BASE_URI = $ENV{CAL_DAV_URL_BASE};
  31. $CALENDAR_URI = $ENV{CAL_DAV_URL_CALENDAR};
  32. $SKELDIR = $ENV{SKELDIR} || "$Bin/../templates";
  33. $BASE_URI ||= shift @ARGV
  34. if @ARGV;
  35. $CALENDAR_URI ||= shift @ARGV
  36. if @ARGV;
  37. $OUTPUT_FILE = shift @ARGV
  38. if @ARGV;
  39. $CALENDAR_LANG = $ENV{CAL_LANG} || setlocale(LC_TIME);
  40. $CALENDAR_TIME_ZONE
  41. = DateTime::TimeZone->new( name => ( $ENV{CAL_TIME_ZONE} || 'local' ), );
  42. # extend DateTime locale with form LONGER
  43. # * omit year and second
  44. # * unabbreviate weekday and month
  45. # * interpose time preposition in combined date and time, where known
  46. my %at = (
  47. C => " 'at' ",
  48. ar => " 'في' ",
  49. da => " 'kl.' ",
  50. de => " 'um' ",
  51. en => " 'at' ",
  52. es => " 'a las' ",
  53. fr => " 'à' ",
  54. he => " 'בשעה' ",
  55. it => " 'alle' ",
  56. ja => "'に'",
  57. no => " 'kl.' ",
  58. ru => " 'в' ",
  59. zh => "'在'",
  60. );
  61. my $dt_locale = DateTime::Locale->load($CALENDAR_LANG);
  62. my ( $locale, $lang ) = $dt_locale->code =~ /^((\w+)(?:-\w+)?)/;
  63. my $dt = DateTime->now( locale => $dt_locale );
  64. my %dt_locale_data = $dt_locale->locale_data;
  65. $dt_locale_data{code} = "${locale}-LONGER";
  66. $dt_locale_data{name} .= ' nouns unabbreviated';
  67. $dt_locale_data{date_format_medium} = $dt->locale->format_for('MMMMEd');
  68. $dt_locale_data{date_format_medium} ||= $dt->locale->format_for('MMMEd');
  69. $dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
  70. $dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
  71. $dt_locale_data{date_format_medium} =~ s/\bE\b/EEEE/;
  72. $dt_locale_data{time_format_medium} = $dt->locale->format_for('Hm');
  73. $dt_locale_data{datetime_format_medium}
  74. =~ s/^\{1\}\K,? (?=\{0\}$)/$at{$lang}/
  75. if $at{$lang};
  76. %GROUPWARE_OPTIONS = (
  77. dt_locale => DateTime::Locale::FromData->new( \%dt_locale_data ),
  78. dt_time_zone => $CALENDAR_TIME_ZONE,
  79. );
  80. $log->infof(
  81. 'Will use locale %s and time zone %s',
  82. $GROUPWARE_OPTIONS{dt_locale}->code,
  83. $GROUPWARE_OPTIONS{dt_time_zone}->name,
  84. );
  85. # resolve calendar URIs
  86. my ( $base_uri, $calendar_uri, $calendar );
  87. $base_uri = URI->new($BASE_URI)
  88. if ($BASE_URI);
  89. $base_uri
  90. or $log->fatal('required base URI not provided') && exit 2;
  91. $base_uri->scheme
  92. or $base_uri->scheme('file');
  93. if ( $base_uri->scheme eq 'http' or $base_uri->scheme eq 'https' ) {
  94. $log->infof( 'will use base URI %s', $base_uri );
  95. $calendar_uri = URI->new( $CALENDAR_URI || $base_uri );
  96. $calendar_uri and $calendar_uri->authority
  97. or $log->fatal('bad calendar URI: must be an internet URI') && exit 2;
  98. $base_uri->eq($calendar_uri) and $calendar_uri = undef
  99. or $log->infof( 'will use calendar URI %s', $calendar_uri );
  100. my $session = Object::Groupware::DAV->new(
  101. user => $ENV{CAL_DAV_USER},
  102. pass => $ENV{CAL_DAV_PASS},
  103. uri => $base_uri,
  104. %GROUPWARE_OPTIONS,
  105. );
  106. $calendar = $session->get($calendar_uri);
  107. }
  108. elsif ( $base_uri->scheme eq 'file' ) {
  109. defined $base_uri->file
  110. or $log->fatal('bad base URI: cannot open file') && exit 2;
  111. $log->infof( 'will use base URI %s', $base_uri );
  112. # parse local calendar data
  113. $log->debug('parse local calendar data...');
  114. my $path = path( $base_uri->file );
  115. if ( $path->is_file ) {
  116. $calendar = Object::Groupware::Calendar->new(
  117. filename => "$path",
  118. %GROUPWARE_OPTIONS,
  119. );
  120. }
  121. else {
  122. my $data;
  123. $path->visit( sub { $data .= $_->slurp_raw if $_->is_file } );
  124. $calendar = Object::Groupware::Calendar->new(
  125. data => $data,
  126. %GROUPWARE_OPTIONS,
  127. );
  128. }
  129. }
  130. # select subset of calendar events
  131. $log->debug('serialize calendar events...');
  132. my $start;
  133. if ( $ENV{CAL_DAV_NOW} ) {
  134. try { require DateTimeX::Easy }
  135. catch ($e) {
  136. $log->fatalf( 'failed parsing CAL_DAV_NOW: %s', $e ) && exit 2
  137. }
  138. $start = DateTimeX::Easy->new( $ENV{CAL_DAV_NOW} );
  139. $log->fatalf(
  140. 'failed parsing CAL_DAV_NOW: unknown start time "%s"',
  141. $ENV{CAL_DAV_NOW}
  142. )
  143. && exit 2
  144. unless defined $start;
  145. }
  146. $start ||= DateTime->now;
  147. my $end = $start->clone->add( months => 6 );
  148. my $span = DateTime::Span->from_datetimes( start => $start, end => $end );
  149. my @events = $calendar->events($span);
  150. # serialize calendar view
  151. my $output_path;
  152. if ($OUTPUT_FILE) {
  153. $output_path = path($OUTPUT_FILE);
  154. $output_path->parent->mkpath;
  155. $output_path->remove;
  156. }
  157. my %vars;
  158. for (@events) {
  159. next unless $_->summary;
  160. push @{ $vars{events} }, $_;
  161. }
  162. my %tmpl;
  163. $tmpl{list} = path($SKELDIR)->child('list.md')->slurp_utf8;
  164. my $template = Text::Xslate->new(
  165. path => \%tmpl,
  166. syntax => 'TTerse',
  167. type => 'text',
  168. );
  169. my $content = $template->render( 'list', \%vars );
  170. if ($output_path) {
  171. $output_path->append_utf8($content);
  172. }
  173. else {
  174. print $content;
  175. }
  176. 1;