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