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