File Coverage

blib/lib/Linux/DesktopFiles.pm
Criterion Covered Total %
statement 71 113 62.8
branch 21 66 31.8
condition 7 31 22.5
subroutine 4 7 57.1
pod 5 5 100.0
total 108 222 48.6


line stmt bran cond sub pod time code
1             package Linux::DesktopFiles;
2              
3             # This module is designed to be pretty fast.
4             # The best uses of this module is to generate real
5             # time menus, based on the content of desktop files.
6              
7 2     2   322117 use 5.014;
  2         7  
8              
9             #use strict;
10             #use warnings;
11              
12             our $VERSION = '0.26';
13              
14             our %TRUE_VALUES = (
15             'true' => 1,
16             'True' => 1,
17             '1' => 1
18             );
19              
20             sub _make_case_insensitive {
21 0     0   0 lc($_[0]) =~ tr/_a-z0-9/_/cr;
22             }
23              
24             sub new {
25 1     1 1 173877 my ($class, %opt) = @_;
26              
27             my %data = (
28             keep_unknown_categories => 0,
29             unknown_category_key => 'Other',
30              
31             case_insensitive_cats => 0,
32              
33             skip_filename_re => undef,
34             skip_entry => undef,
35             substitutions => undef,
36              
37 1 50       65 terminal => (defined($opt{terminal}) ? undef : $ENV{TERM}),
38              
39             terminalize => 0,
40             terminalization_format => q{%s -e '%s'},
41              
42             desktop_files_paths => [
43             qw(
44             /usr/local/share/applications
45             /usr/share/applications
46             )
47             ],
48              
49             keys_to_keep => [qw(Exec Name Icon)],
50              
51             categories => [
52             qw(
53             AudioVideo
54             Development
55             Education
56             Game
57             Graphics
58             Network
59             Office
60             Science
61             Settings
62             System
63             Utility
64             Engineering
65             Amusement
66             Documentation
67             Adult
68             Core
69             COSMIC
70             GTK
71             Qt
72             Motif
73             Java
74             ConsoleOnly
75             )
76             ],
77              
78             subcategories => {
79             AudioVideo => [
80             qw(
81             Audio
82             AudioVideoEditing
83             DiscBurning
84             Midi
85             Mixer
86             Music
87             Player
88             Recorder
89             Sequencer
90             Tuner
91             TV
92             Video
93             )
94             ],
95             Development => [
96             qw(
97             Building
98             Database
99             Debugger
100             GUIDesigner
101             IDE
102             Profiling
103             ProjectManagement
104             RevisionControl
105             Translation
106             WebDevelopment
107             )
108             ],
109             Education => [
110             qw(
111             Art
112             ArtificialIntelligence
113             Astronomy
114             Biology
115             Chemistry
116             ComputerScience
117             Construction
118             DataVisualization
119             Economy
120             Electricity
121             Geography
122             Geology
123             Geoscience
124             History
125             Humanities
126             ImageProcessing
127             Music
128             Languages
129             Literature
130             Maps
131             Math
132             NumericalAnalysis
133             MedicalSoftware
134             ParallelComputing
135             Physics
136             Robotics
137             Spirituality
138             Sports
139             )
140             ],
141             Game => [
142             qw(
143             ActionGame
144             AdventureGame
145             ArcadeGame
146             BoardGame
147             BlocksGame
148             CardGame
149             KidsGame
150             LogicGame
151             RolePlaying
152             Shooter
153             Simulation
154             SportsGame
155             StrategyGame
156             Emulation
157             )
158             ],
159             Graphics => [
160             qw(
161             2DGraphics
162             VectorGraphics
163             RasterGraphics
164             3DGraphics
165             Scanning
166             Photography
167             Publishing
168             Viewer
169             ImageProcessing
170             )
171             ],
172             Network => [
173             qw(
174             Email
175             Dialup
176             InstantMessaging
177             Chat
178             IRCClient
179             Feed
180             FileTransfer
181             HamRadio
182             News
183             P2P
184             RemoteAccess
185             Telephony
186             TelephonyTools
187             VideoConference
188             WebBrowser
189             WebDevelopment
190             Monitor
191             )
192             ],
193             Office => [
194             qw(
195             Calendar
196             ContactManagement
197             Database
198             Dictionary
199             Chart
200             Email
201             Finance
202             FlowChart
203             PDA
204             ProjectManagement
205             Presentation
206             Spreadsheet
207             WordProcessor
208             Photography
209             Publishing
210             )
211             ],
212             Science => [
213             qw(
214             Art
215             Construction
216             Languages
217             ArtificialIntelligence
218             Astronomy
219             Biology
220             Chemistry
221             ComputerScience
222             DataVisualization
223             Economy
224             Electricity
225             Geography
226             Geology
227             Geoscience
228             History
229             Humanities
230             ImageProcessing
231             Literature
232             Maps
233             Math
234             NumericalAnalysis
235             MedicalSoftware
236             Physics
237             Robotics
238             Spirituality
239             Sports
240             ParallelComputing
241             Electronics
242             )
243             ],
244             Settings => [
245             qw(
246             DesktopSettings
247             HardwareSettings
248             Printing
249             PackageManager
250             Security
251             Accessibility
252             )
253             ],
254             System => [
255             qw(
256             Emulator
257             FileManager
258             TerminalEmulator
259             Filesystem
260             Monitor
261             Security
262             )
263             ],
264             Utility => [
265             qw(
266             TextTools
267             Archiving
268             FileTools
269             Accessibility
270             Calculator
271             Clock
272             TextEditor
273             )
274             ],
275             GTK => [
276             qw(
277             GNOME
278             XFCE
279             )
280             ],
281             Qt => [
282             qw(
283             KDE
284             DDE
285             )
286             ],
287             },
288              
289             %opt,
290             );
291              
292 1         3 $data{_file_keys_re} = do {
293 1         2 my %seen;
294 10         15 my @keys = map { quotemeta($_) }
295 1 50       1 grep { !$seen{$_}++ } (@{$data{keys_to_keep}}, qw(Hidden NoDisplay Categories), ($data{terminalize} ? qw(Terminal) : ()));
  10         20  
  1         7  
296              
297 1         3 local $" = q{|};
298 1         146 qr/^(@keys)=(.*\S)/m;
299             };
300              
301             {
302 1         5 my @cats = @{$data{categories}};
  1         3  
  1         5  
303              
304 1 50       6 if ($data{case_insensitive_cats}) {
305 0         0 @cats = map { _make_case_insensitive($_) } @cats;
  0         0  
306             }
307              
308 1         4 @{$data{_categories}}{@cats} = ();
  1         9  
309             }
310              
311 1         2 foreach my $subcat (keys %{$data{subcategories}}) {
  1         8  
312              
313 13         19 my @subcats = @{$data{subcategories}{$subcat}};
  13         42  
314              
315 13 50       39 if ($data{case_insensitive_cats}) {
316 0         0 $subcat = _make_case_insensitive($subcat);
317             }
318              
319 13         18 @{$data{_subcategories}{$subcat}}{@subcats} = ();
  13         150  
320             }
321              
322 1         9 bless \%data, $class;
323             }
324              
325             sub get_desktop_files {
326 0     0 1 0 my ($self) = @_;
327              
328 0         0 my %table;
329 0         0 foreach my $dir (@{$self->{desktop_files_paths}}) {
  0         0  
330 0 0       0 opendir(my $dir_h, $dir) or next;
331              
332             #<<<
333 0   0     0 my $is_local = (
334             index($dir, '/local/') != -1
335             or index($dir, '/.local/') != -1
336             );
337             #>>>
338              
339 0         0 foreach my $file (readdir $dir_h) {
340 0 0       0 if (substr($file, -8) eq '.desktop') {
341 0 0 0     0 if ($is_local or not exists($table{$file})) {
342 0         0 $table{$file} = "$dir/$file";
343             }
344             }
345             }
346             }
347              
348 0 0       0 wantarray ? values(%table) : [values(%table)];
349             }
350              
351             # Used for unescaping strings
352             my %Chr = (s => ' ', n => "\n", r => "\r", t => "\t", '\\' => '\\');
353              
354             sub parse_desktop_file {
355 2     2 1 1797 my ($self, $desktop_file) = @_;
356              
357             # Check the filename and skip it if it matches `skip_filename_re`
358 2 50       10 if (defined $self->{skip_filename_re}) {
359 0 0       0 substr($desktop_file, rindex($desktop_file, '/') + 1) =~ /$self->{skip_filename_re}/ && return;
360             }
361              
362             # Open and read the desktop file
363 2 50       98 sysopen my $desktop_fh, $desktop_file, 0 or return;
364 2         52 sysread $desktop_fh, (my $file), -s $desktop_file;
365              
366             # Locate the "[Desktop Entry]" section
367 2 50       31 if ((my $index = index($file, "]\n", index($file, "[Desktop Entry]") + 15)) != -1) {
368 2         9 $file = substr($file, 0, $index);
369             }
370              
371             # Parse the entry data
372 2         44 my %info = $file =~ /$self->{_file_keys_re}/g;
373              
374             # Ignore the file when `NoDisplay` is true
375 2 50       7 if (exists $info{NoDisplay}) {
376 0 0       0 return if exists $TRUE_VALUES{$info{NoDisplay}};
377             }
378              
379             # Ignore the file when `Hidden` is true
380 2 50       5 if (exists $info{Hidden}) {
381 0 0       0 return if exists $TRUE_VALUES{$info{Hidden}};
382             }
383              
384             # If no 'Name' entry is defined, create one with the name of the file
385 2   33     7 $info{Name} //= substr($desktop_file, rindex($desktop_file, '/') + 1, -8);
386              
387             # Unescape string escapes (\n, \t, etc.)
388 2   0     14 $info{$_} =~ s{\\(.)}{ $Chr{$1} // $1 }eg for (keys %info);
  0         0  
389              
390             # Handle `skip_entry`
391 2 50 33     13 if (defined($self->{skip_entry}) and ref($self->{skip_entry}) eq 'ARRAY') {
392 0         0 foreach my $pair_ref (@{$self->{skip_entry}}) {
  0         0  
393 0 0 0     0 if (exists($info{$pair_ref->{key}}) and $info{$pair_ref->{key}} =~ /$pair_ref->{re}/) {
394 0         0 return;
395             }
396             }
397             }
398              
399             # Make user-defined substitutions
400 2 50 33     7 if (defined($self->{substitutions}) and ref($self->{substitutions}) eq 'ARRAY') {
401 0         0 foreach my $pair_ref (@{$self->{substitutions}}) {
  0         0  
402 0 0       0 if (exists $info{$pair_ref->{key}}) {
403 0 0       0 if ($pair_ref->{global}) {
404 0         0 $info{$pair_ref->{key}} =~ s/$pair_ref->{re}/$pair_ref->{value}/g;
405             }
406             else {
407 0         0 $info{$pair_ref->{key}} =~ s/$pair_ref->{re}/$pair_ref->{value}/;
408             }
409             }
410             }
411             }
412              
413             # Parse categories
414 2   50     8 my @file_categories = split(/;/, $info{Categories} // '');
415 2         14 my @file_subcategories = @file_categories;
416              
417 2 50       5 if ($self->{case_insensitive_cats}) {
418 0         0 @file_categories = map { _make_case_insensitive($_) } @file_categories;
  0         0  
419             }
420              
421 2         3 my @cats = grep { exists($self->{_categories}{$_}) } @file_categories;
  8         17  
422              
423             # Skip entry when there are no categories and `keep_unknown_categories` is false
424             # When `keep_unknown_categories` is true, set `@cats` to `unknown_category_key`.
425 2 50       4 if (!@cats) {
426 0 0       0 if ($self->{keep_unknown_categories}) {
427 0         0 push @cats, $self->{unknown_category_key};
428             }
429             else {
430 0         0 return;
431             }
432             }
433              
434             # Store the categories
435 2         5 $info{Categories} = \@cats;
436              
437             # Add subcategories
438 2         3 my %subcategories;
439              
440 2         3 foreach my $cat (@file_categories) {
441 8 100       15 if (exists $self->{_subcategories}{$cat}) {
442 4         5 push @{$subcategories{$cat}}, grep { exists $self->{_subcategories}{$cat}{$_} } @file_subcategories;
  4         9  
  16         23  
443             }
444             }
445              
446 2         5 $info{SubCategories} = \%subcategories;
447              
448             # Remove `% ...` from the value of `Exec`
449 2 50       15 index($info{Exec}, ' %') != -1 and $info{Exec} =~ s/ +%.*//s;
450              
451             # Terminalize
452 2 0 33     6 if ( $self->{terminalize}
      33        
453             and defined($info{Terminal})
454             and exists($TRUE_VALUES{$info{Terminal}})) {
455 0         0 $info{Exec} = sprintf($self->{terminalization_format}, $self->{terminal}, $info{Exec});
456             }
457              
458             # Check and clean the icon name
459 2 50       8 if (exists $info{Icon}) {
460 2         3 my $icon = $info{Icon};
461              
462 2         3 my $abs;
463 2 50       5 if (substr($icon, 0, 1) eq '/') {
464 0 0       0 if (-f $icon) { # icon is specified as an absolute path
465 0         0 $abs = 1;
466             }
467             else { # otherwise, take its basename
468 0         0 $icon = substr($icon, 1 + rindex($icon, '/'));
469             }
470             }
471              
472             # Remove any icon extension
473 2 50       6 if (!$abs) {
474 2         4 $icon =~ s/\.(?:png|jpe?g|svg|xpm)\z//i;
475             }
476              
477             # Store the icon back into `%info`
478 2         4 $info{Icon} = $icon;
479             }
480              
481 2 100       39 wantarray ? (%info) : \%info;
482             }
483              
484             sub parse {
485 1     1 1 56 my ($self, $hash_ref, @desktop_files) = @_;
486              
487 1         3 foreach my $desktop_file (@desktop_files) {
488 1   50     4 my $entry = $self->parse_desktop_file($desktop_file) // next;
489              
490             # Push the entry into its belonging categories
491 1         1 foreach my $category (@{$entry->{Categories}}) {
  1         3  
492 2         9 push @{$hash_ref->{$category}}, $entry;
  2         5  
493             }
494             }
495              
496 1         3 $hash_ref;
497             }
498              
499             sub parse_desktop_files {
500 0     0 1   my ($self) = @_;
501 0           my %categories;
502 0           $self->parse(\%categories, $self->get_desktop_files);
503 0 0         wantarray ? (%categories) : \%categories;
504             }
505              
506             1;
507              
508             __END__