File Coverage

blib/lib/App/MergeCal.pm
Criterion Covered Total %
statement 20 31 64.5
branch 0 2 0.0
condition 0 2 0.0
subroutine 7 9 77.7
pod 2 2 100.0
total 29 46 63.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             App::MergeCal
4              
5             =head1 ABSTRACT
6              
7             Command line program that merges iCal files into a single calendar.
8              
9             =head1 SYNOPSIS
10              
11             use App::MergeCal;
12              
13             my $app = App::MergeCal->new;
14             $app->run;
15              
16             # Or, more likely, use the mergecal program
17              
18             =cut
19              
20 4     4   855569 use 5.24.0;
  4         43  
21 4     4   2413 use Feature::Compat::Class;
  4         2251  
  4         28  
22              
23             package App::MergeCal; # For PAUSE
24              
25             class App::MergeCal {
26              
27             our $VERSION = '0.0.5';
28              
29 4     4   2560 use Encode 'encode_utf8';
  4         51076  
  4         518  
30 4     4   2459 use Text::vFile::asData;
  4         29648  
  4         31  
31 4     4   2753 use LWP::Simple;
  4         306902  
  4         36  
32 4     4   4506 use JSON;
  4         46339  
  4         23  
33 4     4   657 use URI;
  4         11  
  4         3921  
34              
35             field $vfile_parser :param = Text::vFile::asData->new;
36             field $title :param;
37             field $output :param = '';
38             field $calendars :param;
39             field $objects;
40              
41             =head1 METHODS
42              
43             =head2 $app->calendars, $app->title, $app->output
44              
45             Accessors for fields.
46              
47             =cut
48              
49             method calendars { return $calendars }
50             method title { return $title }
51             method output { return $title }
52              
53             =head2 $app->run
54              
55             Main driver method.
56              
57             =cut
58              
59             method run {
60             $self->gather;
61             $self->render;
62             }
63              
64             =head2 $app->gather
65              
66             Access all of the calendars and gather their contents into $objects.
67              
68             =cut
69              
70             method gather {
71             $self->clean_calendars;
72             for (@$calendars) {
73             my $ics = get($_) or die "Can't get " . $_->as_string . "\n";
74             $ics = encode_utf8($ics);
75             open my $fh, '<', \$ics or die $!;
76             my $data = $vfile_parser->parse( $fh );
77              
78             push @$objects, @{ $data->{objects}[0]{objects} };
79             }
80             }
81              
82             =head2 $app->render
83              
84             Take all of the objects in $objects and write them to an output file.
85              
86             =cut
87              
88             method render {
89             my $combined = {
90             type => 'VCALENDAR',
91             properties => {
92             'X-WR-CALNAME' => [ { value => $title } ],
93             },
94             objects => $objects,
95             };
96              
97             my $out_fh;
98             if ($output) {
99             open $out_fh, '>', $output
100             or die "Cannot open output file [$output]: $!\n";
101             select $out_fh;
102             }
103              
104             say "$_\r" for Text::vFile::asData->generate_lines($combined);
105             }
106              
107             =head2 $app->clean_calendars
108              
109             Ensure that all of the calendars are URIs. If a calendar doesn't have a scheme
110             then it is assumed to be a file URI.
111              
112             =cut
113              
114             method clean_calendars {
115             for (@$calendars) {
116             $_ = URI->new($_) unless ref $_;
117             if (! $_->scheme) {
118             $_ = URI->new('file://' . $_);
119             }
120             }
121             }
122              
123             =head2 App::MergeCal->new, App::MergeCal->new_from_json, App::MergeCal->new_from_json_file
124              
125             Constructor methods.
126              
127             =over 4
128              
129             =item new
130              
131             Constructs an object from a hash of attribute/value pairs
132              
133             =item new_from_json
134              
135             Constructs an object from a JSON string representing attribute/value pairs.
136              
137             =item new_from_json_file
138              
139             Constructs an object from a file containing a JSON string representing
140             attribute/value pairs.
141              
142             =back
143              
144             =cut
145              
146             sub new_from_json {
147 0     0 1   my $class = shift;
148 0           my ($json) = @_;
149              
150 0           my $data = JSON->new->decode($json);
151              
152 0           return $class->new(%$data);
153             }
154              
155             sub new_from_json_file {
156 0     0 1   my $class = shift;
157 0   0       my $conf_file = $_[0] // 'config.json';
158              
159 0 0         open my $conf_fh, '<', $conf_file or die "$conf_file: $!";
160 0           my $json = do { local $/; <$conf_fh> };
  0            
  0            
161              
162 0           return $class->new_from_json($json);
163             }
164             }
165              
166             =head1 CONFIGURATION
167              
168             The behaviour of the program is controlled by a JSON file. The default name
169             for this file is C. The contents of the file will look something
170             like this:
171              
172             {
173             "title":"My Combined Calendar",
174             "output":"my_calendar.ics",
175             "calendars":[
176             "https://example.com/some_calendar.ics",
177             "https://example.com/some_other_calendar.ics",
178             ]
179             }
180              
181             This configuration will read the the calendars from the URLs listed and
182             combine their contents into a file called C (which you will
183             presumably make available on the internet).
184              
185             The configuration option is optional. If it is omitted, then the
186             output will be written to C.
187              
188             =head1 AUTHOR
189              
190             Dave Cross
191              
192             =head1 LICENSE
193              
194             Copyright (C) 2024, Magnum Solutions Ltd. All Rights Reserved.
195              
196             This script is free software; you can redistribute it and/or modify it
197             under the same terms as Perl itself.
198              
199             =cut
200              
201             1;