- #!/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 IO::Interactive::Tiny;
- use Log::Any qw($log);
- use Log::Any::Adapter;
- use URI;
- use DateTime;
- use Path::Tiny;
- use Text::Xslate;
- use POSIX qw(locale_h); # resolve LC_TIME
- use locale;
- use DateTime::TimeZone;
- use Object::Groupware::DAV;
- use Object::Groupware::Calendar;
- if ( IO::Interactive::Tiny::is_interactive() ) {
- Log::Any::Adapter->set( 'Screen', default_level => 'info' );
- }
- else {
- use Log::Any::Adapter ( 'Stderr', default_level => 'info' );
- }
- # set defaults and parse command-line options
- my ($BASE_URI, $CALENDAR_URI, $SKELDIR, $OUTPUT_FILE, $CALENDAR_LANG,
- $CALENDAR_TIME_ZONE, %GROUPWARE_OPTIONS
- );
- $BASE_URI = $ENV{CAL_DAV_URL_BASE};
- $CALENDAR_URI = $ENV{CAL_DAV_URL_CALENDAR};
- $SKELDIR = $ENV{SKELDIR} || "$Bin/../templates";
- $BASE_URI ||= shift @ARGV
- if @ARGV;
- $CALENDAR_URI ||= shift @ARGV
- if @ARGV;
- $OUTPUT_FILE = shift @ARGV
- if @ARGV;
- $CALENDAR_LANG = $ENV{CAL_LANG} || setlocale(LC_TIME);
- $CALENDAR_TIME_ZONE
- = DateTime::TimeZone->new( name => ( $ENV{CAL_TIME_ZONE} || 'local' ), );
- # extend DateTime locale with form LONGER
- # * omit year and second
- # * unabbreviate weekday and month
- # * interpose time preposition in combined date and time, where known
- my %at = (
- C => " 'at' ",
- ar => " 'في' ",
- da => " 'kl.' ",
- de => " 'um' ",
- en => " 'at' ",
- es => " 'a las' ",
- fr => " 'à' ",
- he => " 'בשעה' ",
- it => " 'alle' ",
- ja => "'に'",
- no => " 'kl.' ",
- ru => " 'в' ",
- zh => "'在'",
- );
- my $dt_locale = DateTime::Locale->load($CALENDAR_LANG);
- my ( $locale, $lang ) = $dt_locale->code =~ /^((\w+)(?:-\w+)?)/;
- my $dt = DateTime->now( locale => $dt_locale );
- my %dt_locale_data = $dt_locale->locale_data;
- $dt_locale_data{code} = "${locale}-LONGER";
- $dt_locale_data{name} .= ' nouns unabbreviated';
- $dt_locale_data{date_format_medium} = $dt->locale->format_for('MMMMEd');
- $dt_locale_data{date_format_medium} ||= $dt->locale->format_for('MMMEd');
- $dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
- $dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
- $dt_locale_data{date_format_medium} =~ s/\bE\b/EEEE/;
- $dt_locale_data{time_format_medium} = $dt->locale->format_for('Hm');
- $dt_locale_data{datetime_format_medium}
- =~ s/^\{1\}\K,? (?=\{0\}$)/$at{$lang}/
- if $at{$lang};
- %GROUPWARE_OPTIONS = (
- dt_locale => DateTime::Locale::FromData->new( \%dt_locale_data ),
- dt_time_zone => $CALENDAR_TIME_ZONE,
- );
- $log->infof(
- 'Will use locale %s and time zone %s',
- $GROUPWARE_OPTIONS{dt_locale}->code,
- $GROUPWARE_OPTIONS{dt_time_zone}->name,
- );
- # resolve calendar URIs
- my ( $base_uri, $calendar_uri, $calendar );
- $base_uri = URI->new($BASE_URI)
- if ($BASE_URI);
- $base_uri
- or $log->fatal('required base URI not provided') && exit 2;
- $base_uri->scheme
- or $base_uri->scheme('file');
- 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 => $ENV{CAL_DAV_USER},
- pass => $ENV{CAL_DAV_PASS},
- 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 $output_path;
- if ($OUTPUT_FILE) {
- $output_path = path($OUTPUT_FILE);
- $output_path->parent->mkpath;
- $output_path->remove;
- }
- my %vars;
- for (@events) {
- next unless $_->summary;
- push @{ $vars{events} }, $_;
- }
- my %tmpl;
- $tmpl{list} = path($SKELDIR)->child('list.md')->slurp_utf8;
- my $template = Text::Xslate->new(
- path => \%tmpl,
- syntax => 'TTerse',
- type => 'text',
- );
- my $content = $template->render( 'list', \%vars );
- if ($output_path) {
- $output_path->append_utf8($content);
- }
- else {
- print $content;
- }
- 1;
|