File Coverage

blib/lib/App/DuckPAN/TemplateSet.pm
Criterion Covered Total %
statement 48 48 100.0
branch 4 4 100.0
condition n/a
subroutine 11 11 100.0
pod 0 1 0.0
total 63 64 98.4


line stmt bran cond sub pod time code
1             package App::DuckPAN::TemplateSet;
2             our $AUTHORITY = 'cpan:DDG';
3             # ABSTRACT: Set of templates an Instant Answer
4             $App::DuckPAN::TemplateSet::VERSION = '1019';
5             # A group of templates is a template set. Conceptually this represents a
6             # sub-type of an instant answer type. For example, a Goodie can be a standard
7             # Goodie or a Cheat Sheet goodie, each of which corresponds to a template set.
8             #
9             # Each template set can have required and optional templates. 'required' templates
10             # are always used to generate output files, while the user's confirmation is
11             # needed before each optional template is processed.
12              
13 2     2   12 use Moo;
  2         4  
  2         11  
14              
15 2     2   534 use Try::Tiny;
  2         4  
  2         103  
16 2     2   15 use List::Util qw(all);
  2         4  
  2         196  
17 2     2   857 use Algorithm::Combinatorics qw(combinations);
  2         4971  
  2         121  
18              
19 2     2   14 use namespace::clean;
  2         5  
  2         10  
20              
21             has name => (
22             is => 'ro',
23             required => 1,
24             doc => 'Name of the template set',
25             );
26              
27             has description => (
28             is => 'ro',
29             required => 1,
30             doc => 'Description of the template set',
31             );
32              
33             has subdir_support => (
34             is => 'ro',
35             default => 1,
36             doc => 'Does this template set support creation of Instant Answers inside sub-directories? ' .
37             'For example, Cheat Sheet Instant Answers do not support it, while Standard Goodie ' .
38             'and Spice ones do have support.',
39             );
40              
41             has required_templates => (
42             is => 'ro',
43             required => 1,
44             doc => 'Arrayref of App::DuckPAN::Template instances that represent mandatory templates',
45             );
46              
47             has optional_templates => (
48             is => 'ro',
49             required => 1,
50             doc => 'Arrayref of App::DuckPAN::Template instances that represent optional templates',
51             );
52              
53             has optional_template_combinations => (
54             is => 'ro',
55             lazy => 1,
56             builder => 1,
57             init_arg => undef,
58             doc => 'Arrayref of possible optional template combinations, ' .
59             'which themselves are arrayrefs of templates.',
60             );
61              
62             # All possible template combinations are generated from the list in the
63             # 'optional_templates' attribute. They are sorted by the following rules:
64             #
65             # 1. Combinations are sorted by length (ascending)
66             # 2. In each combination, the templates are sorted in the same order that
67             # they appear in the 'optional_templates' attribute
68             # 3. Combinations of the same length are then sorted based on the same rule
69             sub _build_optional_template_combinations {
70 1     1   11 my $self = shift;
71 1         2 my @templates = @{$self->optional_templates};
  1         5  
72              
73             # Map of template -> position in template list
74 1         4 my %template_pos = map { ($templates[$_] => $_) } 0..$#templates;
  3         10  
75              
76             # All combinations of all lengths; sorted
77 1         2 my @template_combinations;
78              
79 1         4 for my $length (1..@templates) {
80             # Generate all combinations of length $length
81 3         13 my @combinations = combinations(\@templates, $length);
82              
83             # Sort the tempates in each combination
84 3         238 for my $combination (@combinations) {
85 7         18 @$combination = sort { $template_pos{$a} <=> $template_pos{$b} } @$combination;
  6         22  
86             }
87              
88             # Sort the array of combinations
89             @combinations = sort {
90             # The comparison function compares two arrayrefs of templates.
91             # Templates from both the arrayrefs are compared one by one and the
92             # function returns the value when it finds a difference.
93 3         10 for my $i (0..$length-1) {
  6         15  
94 6         18 my $cmp = $template_pos{$a->[$i]} <=> $template_pos{$b->[$i]};
95              
96 6         15 return $cmp;
97             }
98             } @combinations;
99              
100 3         10 push @template_combinations, @combinations;
101             }
102              
103 1         5 return \@template_combinations;
104             }
105              
106             # check if all templates in the array @templates are optional
107             sub _are_templates_optional {
108 3     3   8 my ($self, @templates) = @_;
109 3         5 my %optional_templates = map { ($_ => 1) } @{$self->optional_templates};
  5         21  
  3         11  
110              
111 3     3   25 return all { $optional_templates{$_} } @templates;
  3         30  
112             }
113              
114             # Use the template to generate necessary files. Takes 2 parameters:
115             # $vars: variables to substitute in the templates
116             # $optional_templates: arrayref of template instances taken from $self->optional_templates
117             sub generate {
118 3     3 0 5689 my ($self, $vars, $optional_templates) = @_;
119 3         5 my @created_files;
120             my $error;
121              
122             # Verify that $optional_templates has templates from within $self->optional_templates
123 3 100       11 $self->_are_templates_optional(@$optional_templates)
124             or die "Unknown template(s) in \$optional_templates";
125              
126 2         15 for my $template (@{$self->required_templates}, @$optional_templates) {
  2         8  
127             try {
128 5     5   382 push @created_files, $template->generate($vars);
129             } catch {
130 1     1   89 $error = $_;
131 5         40 };
132              
133 5 100       89 last if $error;
134             }
135              
136 2         14 return (created_files => [ @created_files ], error => $error);
137             }
138              
139             1;
140              
141             __END__
142              
143             =pod
144              
145             =head1 NAME
146              
147             App::DuckPAN::TemplateSet - Set of templates an Instant Answer
148              
149             =head1 VERSION
150              
151             version 1019
152              
153             =head1 AUTHOR
154              
155             DuckDuckGo <open@duckduckgo.com>, Zach Thompson <zach@duckduckgo.com>, Zaahir Moolla <moollaza@duckduckgo.com>, Torsten Raudssus <torsten@raudss.us> L<https://raudss.us/>
156              
157             =head1 COPYRIGHT AND LICENSE
158              
159             This software is Copyright (c) 2013 by DuckDuckGo, Inc. L<https://duckduckgo.com/>.
160              
161             This is free software, licensed under:
162              
163             The Apache License, Version 2.0, January 2004
164              
165             =cut