more fixes for external command error handling
[scpubgit/System-Introspector.git] / lib / System / Introspector / Util.pm
1 use strictures 1;
2
3 package System::Introspector::Util;
4 use Exporter 'import';
5 use IPC::Run qw( run );
6 use IPC::Open2;
7 use File::Spec;
8 use Scalar::Util qw( blessed );
9 use Capture::Tiny qw( capture_stderr );
10
11 our @EXPORT_OK = qw(
12     handle_from_command
13     handle_from_file
14     output_from_command
15     output_from_file
16     lines_from_command
17     files_from_dir
18     transform_exceptions
19 );
20
21 do {
22     package System::Introspector::_Exception;
23     use Moo;
24     has message => (is => 'ro');
25 };
26
27 sub fail { die System::Introspector::_Exception->new(message => shift) }
28 sub is_report_exception { ref(shift) eq 'System::Introspector::_Exception' }
29
30 sub files_from_dir {
31     my ($dir) = @_;
32     my $dh;
33     opendir $dh, $dir
34         or fail "Unable to read directory $dir: $!";
35     my @files;
36     while (defined( my $item = readdir $dh )) {
37         next if -d "$dir/$item";
38         push @files, $item;
39     }
40     return @files;
41 }
42
43 sub transform_exceptions (&) {
44     my ($code) = @_;
45     my $result = eval { $code->() };
46     if (my $error = $@) {
47         return { error => $error->message }
48             if is_report_exception $error;
49         die $@;
50     }
51     return $result;
52 }
53
54 sub output_from_command {
55     my ($command, $in) = @_;
56     $in = ''
57         unless defined $in;
58     my ($out, $err) = ('', '');
59     my $ok = run($command, \$in, \$out, \$err);
60     return $out, $err, $ok
61         if wantarray;
62     $command = join ' ', @$command
63         if ref $command;
64     fail "Error running command ($command): $err"
65         unless $ok;
66     return $out;
67 }
68
69 sub lines_from_command {
70     my ($command) = @_;
71     my $output = output_from_command $command;
72     chomp $output;
73     return split m{\n}, $output;
74 }
75
76 sub handle_from_command {
77     my ($command) = @_;
78     my $pipe;
79     local $@;
80     my $ok = eval {
81         my $out;
82         my $child_pid;
83         my @lines;
84         my ($err) = capture_stderr {
85           $child_pid = open2($out, File::Spec->devnull, $command);
86           @lines = <$out>;
87           close $out;
88           waitpid $child_pid, 0;
89         };
90         my $content = join '', @lines;
91         my $status = $? >> 8;
92         $err = "Unknown error"
93             unless defined $err;
94         fail "Command error ($command): $err\n"
95             if $status;
96         open $pipe, '<', \$content;
97         1;
98     };
99     unless ($ok) {
100         my $err = $@;
101         die $err
102             if blessed($err) and $err->isa('System::Introspector::_Exception');
103         fail "Error from command '$command': $err";
104     }
105     return $pipe;
106 }
107
108 sub handle_from_file {
109     my ($file) = @_;
110     open my $fh, '<', $file
111         or fail "Unable to read $file: $!";
112     return $fh;
113 }
114
115 sub output_from_file {
116     my ($file) = @_;
117     my $fh = handle_from_file $file;
118     return <$fh>
119         if wantarray;
120     return do { local $/; <$fh> };
121 }
122
123 1;