line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package XML::GSA::Group; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
38
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
34
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
6
|
use XML::Writer; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
18
|
|
7
|
1
|
|
|
1
|
|
5
|
use Data::Dumper; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
57
|
|
8
|
1
|
|
|
1
|
|
5
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
56
|
|
9
|
1
|
|
|
1
|
|
6
|
use DateTime (); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
22
|
|
10
|
1
|
|
|
1
|
|
5
|
use Date::Parse (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2023
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub new { |
13
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
14
|
|
|
|
|
|
|
|
15
|
0
|
|
0
|
|
|
|
return bless { 'records' => [], 'action' => 'add', @_, }, |
16
|
|
|
|
|
|
|
ref $class || $class; |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
#getters |
20
|
|
|
|
|
|
|
sub writer { |
21
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
22
|
|
|
|
|
|
|
|
23
|
0
|
|
|
|
|
|
return $self->{'writer'}; |
24
|
|
|
|
|
|
|
} |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub xml { |
27
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
28
|
|
|
|
|
|
|
|
29
|
0
|
|
|
|
|
|
return $self->{'xml'}; |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub to_string { |
33
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
34
|
|
|
|
|
|
|
|
35
|
0
|
|
|
|
|
|
return $self->{'xml'}; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub records { |
39
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
40
|
|
|
|
|
|
|
|
41
|
0
|
|
0
|
|
|
|
return $self->{'records'} || []; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
#getters and setters |
45
|
|
|
|
|
|
|
sub action { |
46
|
0
|
|
|
0
|
1
|
|
my ( $self, $value ) = @_; |
47
|
|
|
|
|
|
|
|
48
|
0
|
0
|
0
|
|
|
|
$self->{'action'} = $value |
49
|
|
|
|
|
|
|
if $value && $value =~ /(add|delete)/; |
50
|
|
|
|
|
|
|
|
51
|
0
|
|
|
|
|
|
return $self->{'action'}; |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
#other public methods |
55
|
|
|
|
|
|
|
sub create { |
56
|
0
|
|
|
0
|
1
|
|
my ( $self, $feed ) = @_; |
57
|
|
|
|
|
|
|
|
58
|
0
|
0
|
0
|
|
|
|
return unless $feed && ref $feed eq 'XML::GSA'; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
#always |
61
|
0
|
|
|
|
|
|
my $writer = XML::Writer->new( OUTPUT => 'self', ); |
62
|
0
|
|
|
|
|
|
$self->{'writer'} = $writer; |
63
|
|
|
|
|
|
|
|
64
|
0
|
|
|
|
|
|
my %attributes; |
65
|
0
|
0
|
|
|
|
|
$attributes{'action'} = $self->action |
66
|
|
|
|
|
|
|
if defined $self->action; |
67
|
|
|
|
|
|
|
|
68
|
0
|
|
|
|
|
|
$self->writer->startTag( 'group', %attributes ); |
69
|
|
|
|
|
|
|
|
70
|
0
|
0
|
|
|
|
|
for my $record ( @{ $self->records || [] } ) { |
|
0
|
|
|
|
|
|
|
71
|
0
|
|
|
|
|
|
$self->_add_record( $record, $feed ); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
0
|
|
|
|
|
|
$self->writer->endTag('group'); |
75
|
|
|
|
|
|
|
|
76
|
0
|
|
|
|
|
|
my $xml = $self->writer->to_string; |
77
|
0
|
|
|
|
|
|
$self->{'xml'} = $xml; |
78
|
|
|
|
|
|
|
|
79
|
0
|
|
|
|
|
|
return $xml; |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
#private methods |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
#adds a record to a feed |
85
|
|
|
|
|
|
|
sub _add_record { |
86
|
0
|
|
|
0
|
|
|
my ( $self, $record, $feed ) = @_; |
87
|
|
|
|
|
|
|
|
88
|
0
|
0
|
0
|
|
|
|
return unless $self->writer && $record && ref $record eq 'HASH'; |
|
|
|
0
|
|
|
|
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
#url and mimetype are mandatory parameters for the record |
91
|
0
|
0
|
0
|
|
|
|
return unless $record->{'url'} && $record->{'mimetype'}; |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
|
my $attributes = $self->_record_attributes( $record, $feed ); |
94
|
|
|
|
|
|
|
|
95
|
0
|
0
|
|
|
|
|
$self->writer->startTag( 'record', %{ $attributes || {} } ); |
|
0
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
|
97
|
0
|
0
|
0
|
|
|
|
if ( $record->{'metadata'} && ref $record->{'metadata'} eq 'ARRAY' ) { |
98
|
0
|
|
|
|
|
|
$self->_add_metadata( $record->{'metadata'} ); |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
0
|
0
|
|
|
|
|
$self->_record_content($record) |
102
|
|
|
|
|
|
|
if $feed->type eq 'full'; |
103
|
|
|
|
|
|
|
|
104
|
0
|
|
|
|
|
|
$self->writer->endTag('record'); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
#adds record content part |
108
|
|
|
|
|
|
|
sub _record_content { |
109
|
0
|
|
|
0
|
|
|
my ( $self, $record ) = @_; |
110
|
|
|
|
|
|
|
|
111
|
0
|
0
|
0
|
|
|
|
return unless $self->writer && $record->{'content'}; |
112
|
|
|
|
|
|
|
|
113
|
0
|
0
|
|
|
|
|
if ( $record->{'mimetype'} eq 'text/plain' ) { |
|
|
0
|
|
|
|
|
|
114
|
0
|
|
|
|
|
|
$self->writer->dataElement( 'content', $record->{'content'} ); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
elsif ( $record->{'mimetype'} eq 'text/html' ) { |
117
|
0
|
|
|
|
|
|
$self->writer->cdataElement( 'content', $record->{'content'} ); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
#else { |
121
|
|
|
|
|
|
|
#TODO support other mimetype with base64 encoding content |
122
|
|
|
|
|
|
|
#} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
#creates record attributes |
126
|
|
|
|
|
|
|
sub _record_attributes { |
127
|
0
|
|
|
0
|
|
|
my ( $self, $record, $feed ) = @_; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
#must be a full record url |
130
|
|
|
|
|
|
|
#that is: if no base url, the url in record must start with http |
131
|
|
|
|
|
|
|
#base url and url in record can't include the domain at the same time |
132
|
0
|
0
|
0
|
|
|
|
if (( !$feed->base_url && $record->{'url'} !~ /^http/ ) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
133
|
|
|
|
|
|
|
|| ( $feed->base_url |
134
|
|
|
|
|
|
|
&& $feed->base_url =~ /^http/ |
135
|
|
|
|
|
|
|
&& $record->{'url'} =~ /^http/ ) |
136
|
|
|
|
|
|
|
) |
137
|
|
|
|
|
|
|
{ |
138
|
0
|
|
|
|
|
|
return {}; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
#mandatory attributes |
142
|
0
|
0
|
|
|
|
|
my %attributes = ( |
143
|
|
|
|
|
|
|
'url' => $feed->base_url |
144
|
|
|
|
|
|
|
? sprintf( '%s%s', $feed->base_url, $record->{'url'} ) |
145
|
|
|
|
|
|
|
: $record->{'url'}, |
146
|
|
|
|
|
|
|
'mimetype' => $record->{'mimetype'}, |
147
|
|
|
|
|
|
|
); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
####optional attributes#### |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
#action is delete or add |
152
|
0
|
0
|
0
|
|
|
|
$attributes{'action'} = $record->{'action'} |
153
|
|
|
|
|
|
|
if $record->{'action'} |
154
|
|
|
|
|
|
|
&& $record->{'action'} =~ /^(delete|add)$/; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
#lock is true or false |
157
|
0
|
0
|
0
|
|
|
|
$attributes{'lock'} = $record->{'lock'} |
158
|
|
|
|
|
|
|
if $record->{'lock'} |
159
|
|
|
|
|
|
|
&& $record->{'lock'} =~ /^(true|false)$/; |
160
|
|
|
|
|
|
|
|
161
|
0
|
0
|
|
|
|
|
$attributes{'displayurl'} = $record->{'displayurl'} |
162
|
|
|
|
|
|
|
if $record->{'displayurl'}; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
#validate datetime format |
165
|
0
|
0
|
|
|
|
|
if ( $record->{'last-modified'} ) { |
166
|
0
|
|
|
|
|
|
my $date = $self->_to_RFC822_date( $record->{'last-modified'} ); |
167
|
|
|
|
|
|
|
|
168
|
0
|
0
|
|
|
|
|
$attributes{'last-modified'} = $date |
169
|
|
|
|
|
|
|
if $date; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
#allowed values for authmethod |
173
|
0
|
0
|
0
|
|
|
|
$attributes{'authmethod'} = $record->{'authmethod'} |
174
|
|
|
|
|
|
|
if $record->{'authmethod'} |
175
|
|
|
|
|
|
|
&& $record->{'authmethod'} =~ /^(none|httpbasic|ntlm|httpsso)$/; |
176
|
|
|
|
|
|
|
|
177
|
0
|
0
|
0
|
|
|
|
$attributes{'pagerank'} = $record->{'pagerank'} |
178
|
|
|
|
|
|
|
if $feed->type ne 'metadata-and-url' && defined $record->{'pagerank'}; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
#true or false and only for web feeds |
181
|
0
|
0
|
0
|
|
|
|
$attributes{'crawl-immediately'} = $record->{'crawl-immediately'} |
|
|
|
0
|
|
|
|
|
182
|
|
|
|
|
|
|
if $feed->datasource eq 'web' |
183
|
|
|
|
|
|
|
&& $record->{'crawl-immediately'} |
184
|
|
|
|
|
|
|
&& $record->{'crawl-immediately'} =~ /^(true|false)$/; |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
#for web feeds |
187
|
0
|
0
|
0
|
|
|
|
$attributes{'crawl-once'} = $record->{'crawl-once'} |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
188
|
|
|
|
|
|
|
if ( $feed->datasource eq 'web' |
189
|
|
|
|
|
|
|
&& $feed->type() eq 'metadata-and-url' |
190
|
|
|
|
|
|
|
&& $record->{'crawl-once'} |
191
|
|
|
|
|
|
|
&& $record->{'crawl-once'} =~ /^(true|false)$/ ); |
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
|
|
|
return \%attributes; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub _add_metadata { |
197
|
0
|
|
|
0
|
|
|
my ( $self, $metadata ) = @_; |
198
|
|
|
|
|
|
|
|
199
|
0
|
0
|
0
|
|
|
|
return unless $self->writer && scalar @{ $metadata || [] }; |
|
0
|
0
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
|
$self->writer->startTag('metadata'); |
202
|
0
|
0
|
|
|
|
|
for my $meta ( @{ $metadata || [] } ) { |
|
0
|
|
|
|
|
|
|
203
|
0
|
0
|
0
|
|
|
|
next unless $meta->{'name'} && $meta->{'content'}; |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
|
my $content = $meta->{'content'}; |
206
|
|
|
|
|
|
|
|
207
|
0
|
0
|
|
|
|
|
if( ref $content eq 'ARRAY' ) { |
208
|
0
|
0
|
|
|
|
|
$content = join ';', @{ $content || [] }; |
|
0
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
0
|
|
|
|
|
|
my %attributes = ( |
212
|
|
|
|
|
|
|
'name' => $meta->{'name'}, |
213
|
|
|
|
|
|
|
'content' => $content, |
214
|
|
|
|
|
|
|
); |
215
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
$self->writer->dataElement( 'meta', '', %attributes ); |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
|
$self->writer->endTag('metadata'); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
#receives a string representing a datetime and returns its RFC822 representation |
223
|
|
|
|
|
|
|
sub _to_RFC822_date { |
224
|
0
|
|
|
0
|
|
|
my ( $self, $value ) = @_; |
225
|
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
|
my $epoch = Date::Parse::str2time($value); |
227
|
|
|
|
|
|
|
|
228
|
0
|
0
|
|
|
|
|
unless ($epoch) { |
229
|
0
|
|
|
|
|
|
carp("Unknown date format received"); |
230
|
0
|
|
|
|
|
|
return; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
|
my $datetime = DateTime->from_epoch( |
234
|
|
|
|
|
|
|
'epoch' => $epoch, |
235
|
|
|
|
|
|
|
'time_zone' => 'local', |
236
|
|
|
|
|
|
|
); |
237
|
|
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
return $datetime->strftime('%a, %d %b %Y %H:%M:%S %z'); |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
1; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head1 NAME |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
XML::GSA::Group - A class that represents a group in gsa xml |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=head1 VERSION |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Version 0.07 |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=cut |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
our $VERSION = '0.07'; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=head1 METHODS |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=head2 new( C<$params> ) |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Create a new XML::GSA::Group object: |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
my $gsa = XML::GSA->new('records' => [], 'action' => 'add'); |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Arguments of this method are an anonymous hash of parameters: |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head3 records |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
An arrayref of hashrefs where each of the hashrefs represents a gsa xml record |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=head3 action |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
A string that can be 'add' or 'delete' that defines what this group will do to the gsa indexer |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=cut |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=head2 create( C<$feed> ) |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
Receives an instance of XML::GSA so that when creating the group, one know to what feed will it belong. This is necessary because the type of feed influences the type of parameters acccepted by the group. |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=cut |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head2 action |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Getter or the action attribute |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head2 xml |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
Getter for the xml generated by the `create` method. |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=head2 to_string |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
Getter for the xml generated by the `create` method. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head2 records |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Getter for the array of groups records added to this class |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head2 writer |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Getter for the XML::Writer object used in this class to create the xml |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=cut |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=head1 AUTHOR |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
Shemahmforash, C<< >> |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=head1 BUGS |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
312
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
313
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head1 SUPPORT |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
perldoc XML::GSA |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
You can also look for information at: |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=over 4 |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker (report bugs here) |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
L |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
L |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=item * CPAN Ratings |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
L |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=item * Search CPAN |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
L |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=item * Github Repository |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
L |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=back |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
Copyright 2013-2014 Shemahmforash. |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify |
356
|
|
|
|
|
|
|
it under the terms of the GNU General Public License as published by |
357
|
|
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or |
358
|
|
|
|
|
|
|
(at your option) any later version. |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, |
361
|
|
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
362
|
|
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
363
|
|
|
|
|
|
|
GNU General Public License for more details. |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License |
366
|
|
|
|
|
|
|
along with this program. If not, see L. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=cut |