File Coverage

blib/lib/X11/FreeDesktop/DesktopEntry.pm
Criterion Covered Total %
statement 12 123 9.7
branch 0 66 0.0
condition 0 12 0.0
subroutine 4 24 16.6
pod 0 20 0.0
total 16 245 6.5


line stmt bran cond sub pod time code
1             # $Id: DesktopEntry.pm,v 1.9 2005/01/12 17:13:02 jodrell Exp $
2             # Copyright (c) 2005 Gavin Brown. All rights reserved. This program is
3             # free software; you can redistribute it and/or modify it under the same
4             # terms as Perl itself.
5             package X11::FreeDesktop::DesktopEntry;
6 1     1   5868 use Carp;
  1         3  
  1         63  
7 1     1   5 use vars qw($VERSION $ROOT_GROUP $DEFAULT_GROUP $DEFAULT_LOCALE @REQUIRED $VERBOSE $SILENT);
  1         1  
  1         79  
8 1     1   963 use utf8;
  1         13  
  1         4  
9 1     1   29 use strict;
  1         2  
  1         2199  
10              
11             our $VERSION = '0.04';
12             our $ROOT_GROUP = '_root';
13             our $DEFAULT_GROUP = 'Desktop Entry';
14             our $DEFAULT_LOCALE = 'C';
15             our @REQUIRED = qw(Encoding Name Type);
16             our $VERBOSE = 0;
17             our $SILENT = 0;
18              
19             =pod
20              
21             =head1 NAME
22              
23             X11::FreeDesktop::DesktopEntry - an interface to Freedesktop.org .desktop files.
24              
25             =head1 SYNOPSIS
26              
27             use X11::FreeDesktop::DesktopEntry;
28              
29             my $entry = X11::FreeDesktop::DesktopEntry->new_from_data($data);
30              
31             print $entry->get_value('Name');
32              
33             print $entry->Exec;
34              
35             $entry->set_value('Name', 'Example Program');
36              
37             print $entry->as_string;
38              
39             $entry->reset;
40              
41             =head1 DESCRIPTION
42              
43             This module provides an object-oriented interface to files that comply with the
44             Freedesktop.org desktop entry specification. You can query the file for
45             available values, modify them, and also get locale information as well.
46              
47             =head1 CONSTRUCTOR
48              
49             X11::FreeDesktop::DesktopEntry doesn't have the standard C constructor.
50             This allows subclasses to implement their own backend-specific constructor
51             without needing to re-implement the constructor, which can be a pain I<(for an
52             example subclass that uses L as a backend, see the C
53             module in the PerlPanel distribution)>.
54              
55             my $entry = X11::FreeDesktop::DesktopEntry->new_from_data($data);
56              
57             If there is an error reading or parsing the data, the constructor will
58             C and return an undefined value.
59              
60             =cut
61              
62             sub new_from_data {
63 0     0 0   my ($package, $data) = @_;
64 0           my $self = { _raw => $data };
65 0           bless($self, $package);
66 0 0         return undef unless ($self->parse);
67 0           return $self;
68             }
69              
70             sub parse {
71 0     0 0   my $self = shift;
72 0           my @lines = split(/[\r\n]/, $self->{_raw});
73 0           my ($current_group, $last_key);
74 0           for (my $i = 0 ; $i < scalar(@lines) ; $i++) {
75 0           chomp(my $line = $lines[$i]);
76              
77 0 0         if ($line =~ /^[\s\t\r\n]*$/) {
    0          
    0          
    0          
78             # ignore whitespace:
79 0           next;
80              
81             } elsif ($line =~ /^\s*\#(.+)$/) {
82             # the spec requires that we be able to preserve comments, so
83             # we need to note the position that the comment occurred at, relative
84             # to the current group and last key:
85 0 0         push(@{$self->{comments}->{(defined($current_group) ? $current_group : $ROOT_GROUP)}->{$last_key}}, $1);
  0            
86            
87             } elsif ($line =~ /^\[([^\[]+)\]/) {
88             # defines a new group:
89 0           $current_group = $1;
90 0           $self->{data}->{$current_group} = {};
91              
92             } elsif ($current_group ne '') {
93             # got a key=value pair:
94 0           my ($key, $value) = split(/\s*=\s*/, $line, 2);
95 0           $last_key = $key;
96 0           my $locale = $DEFAULT_LOCALE;
97              
98             # check for the Key[postfix] format:
99 0 0         if ($key =~ /\[([^\[]+)\]$/) {
100 0           $locale = $1;
101 0           $key =~ s/\[$locale\]$//;
102             }
103 0 0         if (defined($self->{data}->{$current_group}->{$key}->{$locale})) {
104 0 0         carp(sprintf(
105             'Parse error on %s line %s: value already exists for \'%s\' in \'%s\', skipping later entry',
106             $self->{uri},
107             $i+1,
108             $last_key,
109             $current_group,
110             )) if ($VERBOSE == 1);
111              
112             } else {
113 0           $self->{data}->{$current_group}->{$key}->{$locale} = $value;
114              
115             }
116              
117             } else {
118             # an error:
119 0 0         carp(sprintf('Parse error on %s line %s: no group name defined', $self->{uri}, $i+1)) unless ($SILENT == 1);
120 0           return undef;
121              
122             }
123             }
124 0           return 1;
125             }
126              
127             =pod
128              
129             =head1 METHODS
130              
131             $entry->is_valid($locale);
132              
133             Returns a true or false valid depending on whether the required keys exist for
134             the given C<$locale>. A list of the required keys can be found in the
135             Freedesktop.org specification. If C<$locale> is omitted, it will default to
136             'C'.
137              
138             =cut
139              
140             sub is_valid {
141 0     0 0   my ($self, $locale) = @_;
142 0 0         $locale = (defined($locale) ? $locale : $DEFAULT_LOCALE);
143              
144 0           foreach my $key (@REQUIRED) {
145 0 0         if (!defined($self->get_value($key, $DEFAULT_GROUP, $locale))) {
146 0           return undef;
147             }
148              
149             }
150 0           return 1;
151             }
152              
153             =pod
154             my @groups = $entry->groups;
155              
156             This returns an array of scalars containing the I included in the
157             file. Groups are defined by a line like the following in the file itself:
158              
159             [Desktop Entry]
160              
161             A valid desktop entry file will always have one of these, at the top.
162              
163             =cut
164              
165             sub groups {
166 0     0 0   return keys(%{$_[0]->{data}});
  0            
167             }
168              
169             =pod
170              
171             $entry->has_group($group);
172              
173             Returns true or false depending on whether the file has a section with the name
174             of C<$group>.
175              
176             =cut
177              
178             sub has_group {
179 0     0 0   return defined($_[0]->{data}->{$_[1]});
180             }
181              
182             =pod
183              
184             my @keys = $entry->keys($group, $locale);
185              
186             Returns an array of the available keys in C<$group> and the C<$locale> locale.
187             Both these values revert to defaults if they're undefined. When C<$locale> is
188             defined, the array will be folded in with the keys from 'C', since locales
189             inherit keys from the default locale. See the C method for
190             another example of this inheritance.
191              
192             =cut
193              
194             sub keys {
195 0     0 0   my ($self, $group, $locale) = @_;
196 0 0         $group = (defined($group) ? $group : $DEFAULT_GROUP);
197 0           my %keys;
198 0           foreach my $key (CORE::keys(%{$self->{data}->{$group}})) {
  0            
199             # add the key if $locale is defined and a value exists for that locale, or if $locale isn't defined:
200 0 0 0       $keys{$key}++ if ((defined($locale) && defined($self->{data}->{$group}->{$key}->{$locale})) || !defined($locale));
      0        
201             }
202 0 0         if ($locale ne $DEFAULT_LOCALE) {
203             # fold in the keys for the default locale:
204 0           foreach my $key ($self->keys($group, $DEFAULT_LOCALE)) {
205 0           $keys{$key}++;
206             }
207             }
208 0           return sort(keys(%keys));
209             }
210              
211             =pod
212              
213             $entry->has_key($key, $group);
214              
215             Returns true or false depending on whether the file has a key with the name of
216             C<$key> in the C<$group> section. If C<$group> is omitted, then the default
217             group (C<'Desktop Entry'>) will be used.
218              
219             =cut
220              
221             sub has_key {
222 0 0   0 0   return defined($_[0]->{data}->{defined($_[2]) ? $_[2] : $DEFAULT_GROUP}->{$_[1]});
223             }
224              
225             =pod
226              
227             my @locales = $entry->locales($key, $group);
228              
229             Returns an array of strings naming all the available locales for the given
230             C<$key>. If C<$key> or C<$group> don't exist in the file, this method will
231             C and return undef. There should always be at least one locale in the
232             returned array - the default locale, 'C'.
233              
234             =cut
235              
236             sub locales {
237 0     0 0   my ($self, $key, $group) = @_;
238 0 0         $group = (defined($group) ? $group : $DEFAULT_GROUP);
239              
240 0 0         if (!$self->has_group($group)) {
    0          
241 0 0         carp(sprintf('get_value(): no \'%s\' group found', $group)) if ($VERBOSE == 1);
242 0           return undef;
243              
244             } elsif (!$self->has_key($key, $group)) {
245 0 0         carp(sprintf('get_value(): no \'%s\' key found in \'%s\'', $key, $group)) if ($VERBOSE == 1);
246 0           return undef;
247              
248             } else {
249 0           return CORE::keys(%{$self->{data}->{$group}->{$key}});
  0            
250              
251             }
252             }
253              
254             =pod
255              
256             my $string = $entry->get_value($key, $group, $locale);
257              
258             Returns the value of the key named by C<$key>. C<$group> is optional, and will
259             be set to the default if omitted (see above). C<$locale> is also optional, and
260             defines the locale for the string (defaults to 'C' if omitted). If the
261             requested key does not exist for a non-default C<$locale> of the form C,
262             then the module will search for a value for the C locale. If nothing is
263             found, this method will attempt to return the value for the 'C' locale. If
264             this value does not exist, this method will return undef.
265              
266             =cut
267              
268             sub get_value {
269 0     0 0   my ($self, $key, $group, $locale) = @_;
270 0 0         $group = (defined($group) ? $group : $DEFAULT_GROUP);
271 0 0         $locale = (defined($locale) ? $locale : $DEFAULT_LOCALE);
272              
273 0           ($locale, undef) = split(/\./, $locale, 2); # in case locale is of the form xx_YY.UTF-8
274              
275 0           my $rval;
276 0 0         if (!defined($self->{data}->{$group}->{$key}->{$locale})) {
277 0 0         if ($locale =~ /^[a-z]{2}_[A-Z]{2}$/) {
278 0           my ($base, undef) = split(/_/, $locale, 2);
279 0           $rval = $self->get_value($key, $group, $base);
280              
281             } else {
282 0 0         $rval = ($locale eq $DEFAULT_LOCALE ? undef : $self->get_value($key, $group, $DEFAULT_LOCALE));
283              
284             }
285              
286             } else {
287 0           $rval = $self->{data}->{$group}->{$key}->{$locale};
288              
289             }
290              
291 0           utf8::decode($rval);
292 0           return $rval;
293             }
294              
295             =pod
296              
297             $entry->set_value($key, $value, $locale, $group);
298              
299             This method sets the value of the C<$key> key in the C<$locale> locale and
300             C<$group> group to be C<$value>. If C<$locale> and C<$group> are omitted, the
301             defaults are used. C<$value> is always interpreted as a string. This method
302             always returns true.
303              
304             =cut
305              
306             sub set_value {
307 0     0 0   my ($self, $key, $value, $locale, $group) = @_;
308 0 0         $group = (defined($group) ? $group : $DEFAULT_GROUP);
309 0 0         $locale = (defined($locale) ? $locale : $DEFAULT_LOCALE);
310 0           ($locale, undef) = split(/\./, $locale, 2); # in case locale is of the form xx_YY.UTF-8
311 0           $self->{data}->{$group}->{$key}->{$locale} = $value;
312 0           return 1;
313             }
314              
315             =pod
316              
317             my $data = $entry->as_string;
318              
319             This method returns a scalar containing the full entry in .desktop format. This
320             data can then be used to write the entry to disk.
321              
322             =cut
323              
324             sub as_string {
325 0     0 0   my $self = shift;
326 0           my $data;
327              
328 0 0         if (defined($self->{comments}->{$ROOT_GROUP})) {
329 0           foreach my $key (CORE::keys(%{$self->{comments}->{$ROOT_GROUP}})) {
  0            
330 0           foreach my $comment (@{$self->{comments}->{$ROOT_GROUP}->{$key}}) {
  0            
331 0           $data .= sprintf("# %s\n", $comment);
332             }
333             }
334             }
335              
336 0           foreach my $group (sort($self->groups)) {
337 0           $data .= sprintf("[%s]\n", $group);
338              
339 0 0 0       if (defined($self->{comments}->{$group}) && defined($self->{comments}->{$group}->{''})) {
340 0           foreach my $comment (@{$self->{comments}->{$group}->{''}}) {
  0            
341 0           $data .= sprintf("# %s\n", $comment);
342             }
343             }
344              
345 0           foreach my $key (sort($self->keys($group))) {
346 0           foreach my $locale (sort($self->locales($key, $group))) {
347 0 0         my $name = sprintf('%s%s', $key, ($locale ne $DEFAULT_LOCALE ? sprintf('[%s]', $locale) : ''));
348 0           $data .= sprintf("%s=%s\n", $name, $self->get_value($key, $group, $locale));
349              
350 0 0 0       if (defined($self->{comments}->{$group}) && defined($self->{comments}->{$group}->{$name})) {
351 0           foreach my $comment (@{$self->{comments}->{$group}->{$name}}) {
  0            
352 0           $data .= sprintf("# %s\n", $comment);
353             }
354             }
355              
356             }
357             }
358              
359 0           $data .= "\n";
360             }
361              
362 0           return $data;
363             }
364              
365             =pod
366              
367             $entry->reset;
368              
369             This method restores the entry to its initial state - it undoes any changes
370             made to the values stored in the entry.
371              
372             =cut
373              
374             sub reset {
375 0     0 0   my $self = shift;
376 0           $self->{data} = {};
377 0           return $self->parse;
378             }
379              
380             =pod
381              
382             =head1 CONVENIENCE METHODS
383              
384             my $name = $entry->Name($locale);
385             my $generic_name = $entry->GenericName($locale);
386             my $comment = $entry->Comment($locale);
387             my $type = $entry->Type($locale);
388             my $icon = $entry->Icon($locale);
389             my $exec = $entry->Exec($locale);
390             my $url = $entry->URL($locale);
391             my $startup_notify = $entry->StartupNotify($locale);
392              
393             These methods are shortcuts for the mostly commonly accessed fields from a
394             desktop entry file. If undefined, $locale reverts to the default.
395              
396             =cut
397              
398 0     0 0   sub Name { $_[0]->get_value('Name', $DEFAULT_GROUP, $_[1]) }
399 0     0 0   sub GenericName { $_[0]->get_value('GenericName', $DEFAULT_GROUP, $_[1]) }
400 0     0 0   sub Comment { $_[0]->get_value('Comment', $DEFAULT_GROUP, $_[1]) }
401 0     0 0   sub Type { $_[0]->get_value('Type', $DEFAULT_GROUP, $_[1]) }
402 0     0 0   sub Icon { $_[0]->get_value('Icon', $DEFAULT_GROUP, $_[1]) }
403 0     0 0   sub Exec { $_[0]->get_value('Exec', $DEFAULT_GROUP, $_[1]) }
404 0     0 0   sub URL { $_[0]->get_value('URL', $DEFAULT_GROUP, $_[1]) }
405 0 0   0 0   sub StartupNotify { return ($_[0]->get_value('StartupNotify', $DEFAULT_GROUP, $_[1]) eq 'true' ? 1 : undef) }
406              
407             =pod
408              
409             =head1 NOTES
410              
411             Please note that according to the Freedesktop.org spec, key names are case-sensitive.
412              
413             =head1 SEE ALSO
414              
415             The Freedesktop.org Desktop Entry Specification at L.
416              
417             =head1 AUTHOR
418              
419             Gavin Brown Egavin.brown@uk.comE.
420              
421             =head1 COPYRIGHT
422              
423             Copyright (c) 2005 Gavin Brown. This program is free software, you can use it and/or modify it under the same terms as Perl itself.
424              
425             =cut
426              
427             1;