';
$footer = '';
}
my $blockOpen = '';
my $tTitleOpen = '';
my $tTitleClose = '';
my $tOpen = '';
my $tCOpen = '';
my $tCClose = '';
my $tHOpen = '';
my $tHClose = '';
my $tBOpen = '';
my $tBClose = '';
my $tFOpen = '';
my $tFClose = '';
my $trOpen = '';
my $trOpenEven = '';
my $trOpenOdd = '';
my $thOpen = '';
my $thOpen2 = '';
my $thOpen3 = '';
my $tdOpen = '';
my $tdOpen2 = '';
my $tdOpen3 = '';
my $strongOpen = '';
my $strongClose = '';
my $tdClose = "\t\t\t";
my $thClose = "\t\t\t";
my $trClose = '';
my $tClose = '';
my $blockClose = '';
my $colorRed = '';
my $colorGreen = '';
my $colorClose = '';
if ($html) {
$blockOpen = '
';
$footer = '';
}
my $blockOpen = '';
my $tTitleOpen = '';
my $tTitleClose = '';
my $tOpen = '';
my $tCOpen = '';
my $tCClose = '';
my $tHOpen = '';
my $tHClose = '';
my $tBOpen = '';
my $tBClose = '';
my $tFOpen = '';
my $tFClose = '';
my $trOpen = '';
my $trOpenEven = '';
my $trOpenOdd = '';
my $thOpen = '';
my $thOpen2 = '';
my $thOpen3 = '';
my $tdOpen = '';
my $tdOpen2 = '';
my $tdOpen3 = '';
my $strongOpen = '';
my $strongClose = '';
my $tdClose = "\t\t\t";
my $thClose = "\t\t\t";
my $trClose = '';
my $tClose = '';
my $blockClose = '';
my $colorRed = '';
my $colorGreen = '';
my $colorClose = '';
if ($html) {
$blockOpen = '
'
: ''
)
. 'Based on data generated by '
. $lb
. $modMeta->{generated_by}
. $tdClose
. $trClose
. $tFClose;
push @ret, $tClose . $blockClose;
# show FHEM modules who use this package
@mAttrs = qw(
requires
recommends
suggests
);
$linecount = 1;
foreach my $mAttr (@mAttrs) {
next
unless ( defined( $FHEM::Meta::dependents{pkgs}{$modName}{$mAttr} )
&& ref( $FHEM::Meta::dependents{pkgs}{$modName}{$mAttr} ) eq
'ARRAY'
&& @{ $FHEM::Meta::dependents{pkgs}{$modName}{$mAttr} } > 0 );
my $dependents = '';
my $counter = 0;
foreach my $dependant ( sort { "\L$a" cmp "\L$b" }
@{ $FHEM::Meta::dependents{pkgs}{$modName}{$mAttr} } )
{
my $link = $dependant;
$link =
''
. $dependant . ''
if ($html);
$dependents .= ', ' if ($counter);
$dependents .= $link;
$counter++;
}
if ( $dependents ne '' ) {
if ( $linecount == 1 ) {
push @ret,
$blockOpen
. $tTitleOpen
. 'FHEM internal dependencies'
. $tTitleClose
. $tOpen;
push @ret, $tHOpen . $trOpen;
push @ret, $thOpen . 'Importance' . $thClose;
push @ret, $thOpen . 'Dependent Modules' . $thClose;
push @ret, $trClose . $tHClose;
}
my $l = $linecount % 2 == 0 ? $trOpenEven : $trOpenOdd;
my $importance = $mAttr;
$importance = 'required' if ( $mAttr eq 'requires' );
$importance = 'recommended' if ( $mAttr eq 'recommends' );
$importance = 'suggested' if ( $mAttr eq 'suggests' );
$l .= $tdOpen . $importance . $tdClose;
$l .= $tdOpen . $dependents . $tdClose;
$l .= $trClose;
push @ret, $l;
$linecount++;
}
}
push @ret,
$tFOpen
. $trOpen
. $tdOpen2
. $strongOpen . 'Hint:'
. $strongClose
. ' Dependents can only be shown here if they were loaded into the metadata cache before.'
. $tdClose
. $trClose
. $tFClose
. $tClose
. $blockClose
if ( $linecount > 1 );
if (
$modType eq 'module'
&& $modName ne 'Global'
&& ( !defined( $modules{$modName}{META} )
|| !defined( $modules{$modName}{META}{keywords} )
|| !
grep ( /^fhem-mod-command$/,
@{ $modules{$modName}{META}{keywords} } ) )
)
{
push @ret, $blockOpen . $tTitleOpen . 'Devices' . $tTitleClose . $tOpen;
my $linecount = 1;
if ( defined( $modules{$modName}{LOADED} )
&& $modules{$modName}{LOADED} )
{
my @instances = devspec2array( 'TYPE=' . $modName );
if ( @instances > 0 ) {
push @ret,
$tHOpen
. $trOpen
. $thOpen . 'Name'
. $thClose
. $thOpen . 'State'
. $thClose
. $trClose
. $tHClose
. $tBOpen;
foreach my $instance ( sort { "\L$a" cmp "\L$b" } @instances ) {
next if ( defined( $defs{$instance}{TEMPORARY} ) );
my $l = $linecount % 2 == 0 ? $trOpenEven : $trOpenOdd;
my $device = $instance;
$device =
''
. $instance . ''
if ($html);
$l .= $tdOpen . $device . $tdClose;
$l .= $tdOpen . $defs{$instance}{STATE} . $tdClose;
push @ret, $l;
$linecount++;
}
push @ret, $tBClose;
}
else {
push @ret,
$tBOpen
. $trOpen
. $tdOpen
. 'The module was once loaded into memory, '
. 'but currently there is no device defined.'
. $tdClose
. $trClose
. $tBClose;
}
}
else {
push @ret,
$tBOpen
. $trOpen
. $tdOpen
. 'The module is currently not in use.'
. $tdClose
. $trClose
. $tBClose;
}
push @ret, $tClose . $blockClose;
}
LoadInstallStatusPerl($modName);
push @ret,
$blockOpen
. $tTitleOpen
. 'System Prerequisites'
. $tTitleClose
. $tOpen
. $trOpen
. $tdOpen;
push @ret, $blockOpen . $tOpen . $tCOpen . 'Perl Packages' . $tCClose;
if ( defined( $modMeta->{prereqs} )
&& defined( $modMeta->{prereqs}{runtime} ) )
{
@mAttrs = qw(
requires
recommends
suggests
);
push @ret, $tHOpen . $trOpen;
push @ret, $thOpen . 'Name' . $thClose;
push @ret, $thOpen . 'Importance' . $thClose;
push @ret, $thOpen . 'Status' . $thClose;
push @ret, $trClose . $tHClose . $tBOpen;
$linecount = 1;
foreach my $mAttr (@mAttrs) {
next
unless ( defined( $modMeta->{prereqs}{runtime}{$mAttr} )
&& keys %{ $modMeta->{prereqs}{runtime}{$mAttr} } > 0 );
foreach
my $prereq ( sort keys %{ $modMeta->{prereqs}{runtime}{$mAttr} } )
{
my $isFhem = FHEM::Meta::ModuleIsInternal($prereq);
my $installed = $pkgStatus{Perl}{pkgs}{$prereq}{status};
my $l = $linecount % 2 == 0 ? $trOpenEven : $trOpenOdd;
my $importance = $mAttr;
$importance = 'required' if ( $mAttr eq 'requires' );
$importance = 'recommended' if ( $mAttr eq 'recommends' );
$importance = 'suggested' if ( $mAttr eq 'suggests' );
my $version = $modMeta->{prereqs}{runtime}{$mAttr}{$prereq};
$version = '' if ( !defined($version) || $version eq '0' );
my $inherited = '';
if (
defined( $modMeta->{prereqs}{runtime}{x_inherited} )
&& defined(
$modMeta->{prereqs}{runtime}{x_inherited}{$prereq}
)
)
{
$inherited = '[inherited]';
$inherited = ''
. $inherited
. ''
if ($html);
}
$prereq =
''
. $prereq . ''
if ( $html
&& $installed ne 'built-in'
&& $installed ne 'included' );
$prereq =
''
. $prereq . ''
if ( $html
&& $installed eq 'included' );
if (
$mAttr ne 'requires'
&& ( $installed eq 'missing'
|| $installed eq 'outdated' )
)
{
$installed = '';
}
elsif ($html) {
if ( $installed eq 'installed' ) {
$installed = $colorGreen . $installed . $colorClose;
}
elsif ($installed eq 'missing'
|| $installed eq 'outdated' )
{
$installed =
$colorRed
. $strongOpen
. uc($installed)
. $strongClose
. $colorClose;
}
}
$l .=
$tdOpen
. $prereq
. ( $inherited ne '' ? " $inherited" : '' )
. ( $version ne '' ? " ($version)" : '' )
. $tdClose;
$l .= $tdOpen . $importance . $tdClose;
$l .= $tdOpen . $installed . $tdClose;
$l .= $trClose;
push @ret, $l;
$linecount++;
}
}
push @ret, $tBClose;
push @ret,
$tFOpen
. $trOpenEven
. $tdOpen3
. $strongOpen . 'Hint:'
. $strongClose
. ' The module does not provide Perl prerequisites from its metadata.'
. $lb
. 'This result is based on automatic source code analysis '
. 'and can be incorrect.'
. 'Suggested Perl items may still be required if the module author had decided to implement some own dependency and/or error handling like returning an informative message instead of the original Perl error message.'
. $tdClose
. $trClose
. $tFClose
if ( defined( $modMeta->{x_prereqs_src} )
&& $modMeta->{x_prereqs_src} ne 'META.json' );
}
elsif ( defined( $modMeta->{x_prereqs_src} ) ) {
push @ret,
$tBOpen
. $trOpenOdd
. $tdOpen
. 'No known prerequisites.'
. $tdClose
. $trClose
. $tBClose;
}
else {
push @ret,
$tBOpen
. $trOpenOdd
. $tdOpen
. 'Module metadata do not contain any prerequisites.' . "\n"
. 'For automatic source code analysis, please install Perl::PrereqScanner::NotQuiteLite first.'
. $tdClose
. $trClose
. $tBClose;
}
push @ret, $tClose . $blockClose;
if ( defined( $modMeta->{x_prereqs_nodejs} )
&& defined( $modMeta->{x_prereqs_nodejs}{runtime} ) )
{
push @ret,
$blockOpen
. $tTitleClose
. $tOpen
. $tCOpen
. 'Node.js Packages'
. $tCClose;
my @mAttrs = qw(
requires
recommends
suggests
);
push @ret, $tHOpen . $trOpen;
push @ret, $thOpen . 'Name' . $thClose;
push @ret, $thOpen . 'Importance' . $thClose;
push @ret, $thOpen . 'Status' . $thClose;
push @ret, $trClose . $tHClose . $tBOpen;
$linecount = 1;
foreach my $mAttr (@mAttrs) {
next
unless ( defined( $modMeta->{x_prereqs_nodejs}{runtime}{$mAttr} )
&& keys %{ $modMeta->{x_prereqs_nodejs}{runtime}{$mAttr} } >
0 );
foreach my $prereq (
sort
keys %{ $modMeta->{x_prereqs_nodejs}{runtime}{$mAttr} }
)
{
my $l = $linecount % 2 == 0 ? $trOpenEven : $trOpenOdd;
my $importance = $mAttr;
$importance = 'required' if ( $mAttr eq 'requires' );
$importance = 'recommended' if ( $mAttr eq 'recommends' );
$importance = 'suggested' if ( $mAttr eq 'suggests' );
my $version =
$modMeta->{x_prereqs_nodejs}{runtime}{$mAttr}{$prereq};
$version = '' if ( !defined($version) || $version eq '0' );
my $check = __IsInstalledNodejs($prereq);
my $installed = '';
if ($check) {
if ( $check =~ m/^\d+\./ ) {
my $nverReq =
$version ne ''
? $version
: 0;
my $nverInst = $check;
#TODO suport for version range:
#https://metacpan.org/pod/CPAN::Meta::Spec#Version-Range
if ( $nverReq > 0 && $nverInst < $nverReq ) {
$installed .=
$colorRed
. 'OUTDATED'
. $colorClose . ' ('
. $check . ')';
}
else {
$installed = 'installed';
}
}
else {
$installed = 'installed';
}
}
else {
$installed = $colorRed . 'MISSING' . $colorClose
if ( $importance eq 'required' );
}
$installed = $colorGreen . $installed . $colorClose;
$prereq =
''
. $prereq . ''
if ($html);
$l .=
$tdOpen
. $prereq
. ( $version ne '' ? " ($version)" : '' )
. $tdClose;
$l .= $tdOpen . $importance . $tdClose;
$l .= $tdOpen . $installed . $tdClose;
$l .= $trClose;
push @ret, $l;
$linecount++;
}
}
push @ret, $tBClose . $tClose . $blockClose;
}
if ( defined( $modMeta->{x_prereqs_python} )
&& defined( $modMeta->{x_prereqs_python}{runtime} ) )
{
push @ret, $blockOpen . $tOpen . $tCOpen . 'Python Packages' . $tCClose;
my @mAttrs = qw(
requires
recommends
suggests
);
push @ret, $tHOpen . $trOpen;
push @ret, $thOpen . 'Name' . $thClose;
push @ret, $thOpen . 'Importance' . $thClose;
push @ret, $thOpen . 'Status' . $thClose;
push @ret, $trClose . $tHClose . $tBOpen;
$linecount = 1;
foreach my $mAttr (@mAttrs) {
next
unless ( defined( $modMeta->{x_prereqs_python}{runtime}{$mAttr} )
&& keys %{ $modMeta->{x_prereqs_python}{runtime}{$mAttr} } >
0 );
foreach my $prereq (
sort
keys %{ $modMeta->{x_prereqs_python}{runtime}{$mAttr} }
)
{
my $l = $linecount % 2 == 0 ? $trOpenEven : $trOpenOdd;
my $importance = $mAttr;
$importance = 'required' if ( $mAttr eq 'requires' );
$importance = 'recommended' if ( $mAttr eq 'recommends' );
$importance = 'suggested' if ( $mAttr eq 'suggests' );
my $version =
$modMeta->{x_prereqs_python}{runtime}{$mAttr}{$prereq};
$version = '' if ( !defined($version) || $version eq '0' );
my $check = __IsInstalledPython($prereq);
my $installed = '';
if ($check) {
if ( $check =~ m/^\d+\./ ) {
my $nverReq =
$version ne ''
? $version
: 0;
my $nverInst = $check;
#TODO suport for version range:
#https://metacpan.org/pod/CPAN::Meta::Spec#Version-Range
if ( $nverReq > 0 && $nverInst < $nverReq ) {
$installed .=
$colorRed
. 'OUTDATED'
. $colorClose . ' ('
. $check . ')';
}
else {
$installed = 'installed';
}
}
else {
$installed = 'installed';
}
}
else {
$installed = $colorRed . 'MISSING' . $colorClose
if ( $importance eq 'required' );
}
my $isPerlPragma = FHEM::Meta::ModuleIsPerlPragma($prereq);
my $isPerlCore =
$isPerlPragma ? 0 : FHEM::Meta::ModuleIsPerlCore($prereq);
my $isFhem =
$isPerlPragma || $isPerlCore
? 0
: FHEM::Meta::ModuleIsInternal($prereq);
if ( $isPerlPragma || $isPerlCore || $prereq eq 'perl' ) {
$installed =
$installed ne 'installed'
? "$installed (Perl built-in)"
: 'built-in';
}
elsif ($isFhem) {
$installed =
$installed ne 'installed'
? "$installed (FHEM included)"
: 'included';
}
elsif ( $installed eq 'installed' ) {
$installed = $colorGreen . $installed . $colorClose;
}
$prereq =
''
. $prereq . ''
if ( $html
&& !$isFhem
&& !$isPerlCore
&& !$isPerlPragma
&& $prereq ne 'perl' );
$l .=
$tdOpen
. $prereq
. ( $version ne '' ? " ($version)" : '' )
. $tdClose;
$l .= $tdOpen . $importance . $tdClose;
$l .= $tdOpen . $installed . $tdClose;
$l .= $trClose;
push @ret, $l;
$linecount++;
}
}
push @ret, $tBClose . $tClose . $blockClose;
}
push @ret, $tdClose . $trClose . $tClose . $blockClose;
return $header . join( "\n", @ret ) . $footer;
}
sub CreateRawMetaJson ($$$) {
my ( $hash, $getCmd, $modName ) = @_;
$modName = 'Global' if ( uc($modName) eq 'FHEM' );
my $modType = lc($getCmd) eq 'zzgetmodulemeta.json' ? 'module' : 'package';
FHEM::Meta::Load($modName);
return '{}'
unless (
(
$modType eq 'module'
&& defined( $modules{$modName}{META} )
&& scalar keys %{ $modules{$modName}{META} } > 0
)
|| ( $modType eq 'package'
&& defined( $packages{$modName}{META} )
&& scalar keys %{ $packages{$modName}{META} } > 0 )
);
my $j = JSON->new;
$j->allow_nonref;
$j->canonical;
$j->pretty;
if ( $modType eq 'module' ) {
return $j->encode( $modules{$modName}{META} );
}
else {
return $j->encode( $packages{$modName}{META} );
}
}
sub __GetDefinedModulesFromFile($) {
my ($filePath) = @_;
my @modules;
my $fh;
if ( open( $fh, '<' . $filePath ) ) {
while ( my $l = <$fh> ) {
if ( $l =~ /^define\s+\S+\s+(\S+).*/ ) {
my $modName = $1;
push @modules, $modName
unless ( grep ( /^$modName$/, @modules ) );
}
}
close($fh);
}
if (wantarray) {
return @modules;
}
elsif ( @modules > 0 ) {
return join( ',', @modules );
}
}
sub LoadInstallStatusPerl(;$) {
my ($modList) = @_;
my $t = TimeNow();
my @rets;
my $unused = 0;
my @lmodules;
# if modList is undefined or is equal to '1'
if ( !$modList || ( !ref($modList) && $modList eq '1' ) ) {
$unused = 1 if ( $modList && $modList eq '1' );
foreach ( keys %modules ) {
# Only process loaded modules
# unless unused modules were
# explicitly requested
push @lmodules,
$_
if (
$unused
|| ( defined( $modules{$_}{LOADED} )
&& $modules{$_}{LOADED} eq '1' )
);
}
}
# if a single module name was given
elsif ( !ref($modList) ) {
push @lmodules, $modList;
}
# if a list of module names was given
elsif ( ref($modList) eq 'ARRAY' ) {
foreach ( @{$modList} ) {
push @lmodules, $_;
}
}
# if a hash was given, assume every
# key is a module name
elsif ( ref($modList) eq 'HASH' ) {
foreach ( keys %{$modList} ) {
push @lmodules, $_;
}
}
# Wrong method use
else {
$@ =
__PACKAGE__ . "LoadInstallStatusPerl: ERROR: Unknown parameter value";
Log 1, $@;
return "$@";
}
foreach my $modName (@lmodules) {
$modName = 'Global' if ( uc($modName) eq 'FHEM' );
my $type;
if ( exists( $modules{$modName} ) && !exists( $packages{$modName} ) ) {
$type = 'module';
}
elsif ( exists( $packages{$modName} ) && !exists( $modules{$modName} ) )
{
$type = 'package';
}
elsif ( exists( $packages{$modName} ) && exists( $modules{$modName} ) )
{
$type = 'module+package';
}
next unless ($type);
foreach my $type ( split( '\+', $type ) ) {
FHEM::Meta::Load($modName);
next
unless (
( $type eq 'module' && defined( $modules{$modName}{META} ) )
|| ( $type eq 'package'
&& defined( $packages{$modName}{META} ) )
);
my $modMeta =
$type eq 'module'
? $modules{$modName}{META}
: $packages{$modName}{META};
$pkgStatus{Perl}{analyzed} = 2
unless ( defined( $modMeta->{x_prereqs_src} ) );
# Perl
if ( defined( $modMeta->{prereqs} )
&& defined( $modMeta->{prereqs}{runtime} ) )
{
my $modPreqs = $modMeta->{prereqs}{runtime};
foreach my $mAttr (qw(requires recommends suggests)) {
next
unless ( defined( $modPreqs->{$mAttr} )
&& keys %{ $modPreqs->{$mAttr} } > 0 );
foreach my $pkg ( keys %{ $modPreqs->{$mAttr} } ) {
push
@{ $pkgStatus{Perl}{pkgs}{$pkg}{ $type . 's' }{$mAttr}
},
$modName
unless (
grep ( /^$modName$/,
@{
$pkgStatus{Perl}{pkgs}{$pkg}{ $type . 's' }
{$mAttr}
} )
);
next
if (
defined( $pkgStatus{Perl}{pkgs}{$pkg}{status} ) );
my $fname = $pkg;
$fname =~
s/^.*://g; # strip away any parent module names
my $isPerlPragma = FHEM::Meta::ModuleIsPerlPragma($pkg);
my $isPerlCore =
$isPerlPragma
? 0
: FHEM::Meta::ModuleIsPerlCore($pkg);
my $isFhem =
$isPerlPragma || $isPerlCore
? 0
: FHEM::Meta::ModuleIsInternal($pkg);
if ( $pkg eq 'perl' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'built-in';
$pkgStatus{Perl}{installed}{$pkg} =
version->parse($])->numify;
}
elsif ( $pkg eq 'FHEM' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'included';
$pkgStatus{Perl}{installed}{$pkg} =
$modules{'Global'}{META}{version};
}
elsif ( $pkg eq 'FHEM::Meta' || $pkg eq 'Meta' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'included';
$pkgStatus{Perl}{installed}{$pkg} =
FHEM::Meta->VERSION();
}
elsif ($isPerlPragma) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'built-in';
$pkgStatus{Perl}{installed}{$pkg} = 0;
}
elsif ($isPerlCore) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'built-in';
$pkgStatus{Perl}{installed}{$pkg} = 0;
}
# This is a FHEM package
elsif ( $isFhem && $isFhem eq 'package' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'included';
$pkgStatus{Perl}{installed}{$pkg} =
defined( $packages{$fname}{META} )
? $packages{$fname}{META}{version}
: 0;
}
# This is a FHEM module being loaded as package
elsif ( $isFhem && $isFhem eq 'module' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'included';
$pkgStatus{Perl}{installed}{$pkg} =
defined( $modules{$fname}{META} )
? $modules{$fname}{META}{version}
: 0;
}
elsif ( $pkg =~ /^Win32::/ && $^O ne 'MSWin32' ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} = 'n/a';
}
else {
my $pkgpath = $pkg . '.pm';
$pkgpath =~ s/::/\//g;
# remove any ealier tries to load
# to get the original error message
foreach ( keys %INC ) {
delete $INC{$_}
if ( !$INC{$_} );
}
#FIXME disable warnings does not work here...
no warnings;
my $verbose = AttrVal( 'global', 'verbose', 3 );
$attr{global}{verbose} = 0;
eval "no warnings; require $pkg;";
$attr{global}{verbose} = $verbose;
use warnings;
if ( $@ && $@ =~ m/^Can't locate (\S+)\.pm/i ) {
my $missing = $1;
$missing =~ s/\//::/g;
$pkgStatus{Perl}{pkgs}{$missing}{status} =
'missing';
push @{ $pkgStatus{Perl}{missing}{$missing} },
defined( $modPreqs->{$mAttr}{$missing} )
? $modPreqs->{$mAttr}{$missing}
: 0;
$pkgStatus{Perl}{analyzed} = 1
if ( $modMeta->{x_prereqs_src} ne 'META.json'
&& !$pkgStatus{Perl}{analyzed} );
# If the error message does contain a
# different package name,
# the actual package is installed and
# misses another package by it's own
if ( $missing ne $pkg ) {
my $v = eval "$pkg->VERSION()";
$pkgStatus{Perl}{pkgs}{$pkg}{status} =
'installed';
$pkgStatus{Perl}{installed}{$pkg} =
$v ? $v : 0;
push @{ $pkgStatus{Perl}{pkgs}{$missing}
{ $type . 's' }{$mAttr} },
$modName
unless (
grep ( /^$modName$/,
@{
$pkgStatus{Perl}{pkgs}{$missing}
{ $type . 's' }{$mAttr}
} )
);
# Lets also update the module meta data
if ( $type eq 'module' ) {
$modMeta->{prereqs}
{runtime}{$mAttr}{$missing} = 0;
}
else {
$packages{$modName}{META}{prereqs}
{runtime}{$mAttr}{$missing} = 0;
}
}
}
else {
$pkgStatus{Perl}{pkgs}{$pkg}{status} =
'installed';
my $v = eval "$pkg->VERSION()";
$pkgStatus{Perl}{installed}{$pkg} = $v ? $v : 0;
}
}
# check for outdated version
if ( $pkgStatus{Perl}{pkgs}{$pkg}{status} eq 'installed'
|| $pkg eq 'perl' )
{
my $reqV = $modPreqs->{$mAttr}{$pkg};
my $instV = $pkgStatus{Perl}{installed}{$pkg};
if ( $reqV ne '0' && $instV ne '0' ) {
$reqV = version->parse($reqV)->numify;
$instV = version->parse($instV)->numify;
#TODO suport for version range:
# https://metacpan.org/pod/ \
# CPAN::Meta::Spec#Version-Range
if ( $reqV > 0 && $instV < $reqV ) {
$pkgStatus{Perl}{pkgs}{$pkg}{status} =
'outdated';
push @{ $pkgStatus{Perl}{outdated}{$pkg} },
$reqV;
$pkgStatus{Perl}{analyzed} = 1
if (
$modMeta->{x_prereqs_src} ne 'META.json'
&& !$pkgStatus{Perl}{analyzed} );
}
}
}
$pkgStatus{Perl}{pkgs}{$pkg}{timestamp} = $t;
}
}
}
#TODO
# nodejs
# python
}
}
# build installation hash
foreach my $area ( keys %pkgStatus ) {
foreach my $t (qw(missing outdated)) {
if ( defined( $pkgStatus{$area}{$t} )
&& ref( $pkgStatus{$area}{$t} ) eq 'HASH'
&& %{ $pkgStatus{$area}{$t} } > 0 )
{
foreach my $pkg ( keys %{ $pkgStatus{$area}{$t} } ) {
next
unless ( ref( $pkgStatus{$area}{$t}{$pkg} ) eq 'ARRAY' );
# detect minimum required version
# for missing and outdated packages
my $v = maxNum( 0, @{ $pkgStatus{$area}{$t}{$pkg} } );
$pkgStatus{$area}{$t}{$pkg} = $v;
if (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}{requires}
)
&& @{ $pkgStatus{$area}{pkgs}{$pkg}{modules}{requires} }
> 0
)
{
$pkgStatus{counter}{total}++;
$pkgStatus{counter}{$t}++;
$pkgStatus{counter}{required}{total}++;
$pkgStatus{counter}{required}{$t}++;
$pkgStatus{counter}{required}{$area}{total}++;
$pkgStatus{counter}{required}{$area}{$t}++;
$pkgStatus{counter}{$area}{total}++;
$pkgStatus{counter}{$area}{$t}++;
$pkgStatus{counter}{$area}{required}{total}++;
$pkgStatus{counter}{$area}{required}{$t}++;
$pkgStatus{required}{$area}{$pkg}{status} = $t;
$pkgStatus{required}{$area}{$pkg}{version} = $v;
$pkgStatus{required}{$area}{$pkg}{modules} =
$pkgStatus{$area}{pkgs}{$pkg}{modules}{requires};
# add other modules
if (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}
{recommends}
)
&& @{
$pkgStatus{$area}{pkgs}{$pkg}{modules}
{recommends}
} > 0
)
{
foreach my $modName (
@{
$pkgStatus{$area}{pkgs}{$pkg}{modules}
{recommends}
}
)
{
push
@{ $pkgStatus{required}{$area}{$pkg}{modules}
},
$modName
unless (
grep ( /^$modName$/,
@{
$pkgStatus{required}{$area}
{$pkg}{modules}
} )
);
}
}
if (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests}
)
&& @{
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests}
} > 0
)
{
foreach my $modName (
@{
$pkgStatus{$area}{pkgs}{$pkg}{modules}
{suggests}
}
)
{
push
@{ $pkgStatus{required}{$area}{$pkg}{modules}
},
$modName
unless (
grep ( /^$modName$/,
@{
$pkgStatus{required}{$area}
{$pkg}{modules}
} )
);
}
}
}
elsif (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}{recommends}
)
&& @{ $pkgStatus{$area}{pkgs}{$pkg}{modules}{recommends}
} > 0
)
{
$pkgStatus{counter}{total}++;
$pkgStatus{counter}{$t}++;
$pkgStatus{counter}{recommended}{total}++;
$pkgStatus{counter}{recommended}{$t}++;
$pkgStatus{counter}{recommended}{$area}{total}++;
$pkgStatus{counter}{recommended}{$area}{$t}++;
$pkgStatus{counter}{$area}{total}++;
$pkgStatus{counter}{$area}{$t}++;
$pkgStatus{counter}{$area}{recommended}{total}++;
$pkgStatus{counter}{$area}{recommended}{$t}++;
$pkgStatus{recommended}{$area}{$pkg}{status} = $t;
$pkgStatus{recommended}{$area}{$pkg}{version} = $v;
$pkgStatus{recommended}{$area}{$pkg}{modules} =
$pkgStatus{$area}{pkgs}{$pkg}{modules}{recommends};
# add other modules
if (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests}
)
&& @{
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests}
} > 0
)
{
foreach my $modName (
@{
$pkgStatus{$area}{pkgs}{$pkg}{modules}
{suggests}
}
)
{
push @{ $pkgStatus{recommended}{$area}{$pkg}
{modules} },
$modName
unless (
grep ( /^$modName$/,
@{
$pkgStatus{recommended}{$area}
{$pkg}{modules}
} )
);
}
}
}
elsif (
defined(
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests}
)
&& @{ $pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests} }
> 0
)
{
$pkgStatus{counter}{total}++;
$pkgStatus{counter}{$t}++;
$pkgStatus{counter}{suggested}{total}++;
$pkgStatus{counter}{suggested}{$t}++;
$pkgStatus{counter}{suggested}{$area}{total}++;
$pkgStatus{counter}{suggested}{$area}{$t}++;
$pkgStatus{counter}{$area}{total}++;
$pkgStatus{counter}{$area}{$t}++;
$pkgStatus{counter}{$area}{suggested}{total}++;
$pkgStatus{counter}{$area}{suggested}{$t}++;
$pkgStatus{suggested}{$area}{$pkg}{status} = $t;
$pkgStatus{suggested}{$area}{$pkg}{version} = $v;
$pkgStatus{suggested}{$area}{$pkg}{modules} =
$pkgStatus{$area}{pkgs}{$pkg}{modules}{suggests};
}
}
}
else {
$pkgStatus{counter}{$t} = 0;
$pkgStatus{counter}{required}{$t} = 0;
$pkgStatus{counter}{required}{$area}{$t} = 0;
$pkgStatus{counter}{recommended}{$t} = 0;
$pkgStatus{counter}{recommended}{$area}{$t} = 0;
$pkgStatus{counter}{suggested}{$t} = 0;
$pkgStatus{counter}{suggested}{$area}{$t} = 0;
$pkgStatus{counter}{$area}{$t} = 0;
$pkgStatus{counter}{$area}{required}{$t} = 0;
$pkgStatus{counter}{$area}{recommended}{$t} = 0;
$pkgStatus{counter}{$area}{suggested}{$t} = 0;
}
}
}
if (@rets) {
$@ = join( "\n", @rets );
return "$@";
}
return undef;
}
#TODO
# Checks whether a NodeJS package is installed in the system
sub __IsInstalledNodejs($) {
return 0 unless ( __PACKAGE__ eq caller(0) );
return 0 unless (@_);
my ($pkg) = @_;
return 0;
}
#TODO
# Checks whether a Python package is installed in the system
sub __IsInstalledPython($) {
return 0 unless ( __PACKAGE__ eq caller(0) );
return 0 unless (@_);
my ($pkg) = @_;
return 0;
}
sub __ToDay() {
my ( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) =
localtime( gettimeofday() );
$month++;
$year += 1900;
my $today = sprintf( '%04d-%02d-%02d', $year, $month, $mday );
return $today;
}
sub __aUniq {
my %seen;
grep !$seen{$_}++, @_;
}
1;
=pod
=encoding utf8
=item helper
=item summary Module to help with FHEM installations
=item summary_DE Modul zur Unterstuetzung bei FHEM Installationen
=begin html
Installer
Installer - Module to update FHEM, install 3rd-party FHEM modules and manage system prerequisites
Define
define <name> Installer
Example:
define fhemInstaller Installer
Get
checkPrereqs - list all missing prerequisites. If no parameter was given, the running live system will be inspected. If the parameter is a FHEM cfg file, inspection will be based on devices from this file. If the parameter is a list of module names, those will be used for inspection.
search - search FHEM for device names, module names, package names, keywords, authors and Perl package names.
showModuleInfo - list information about a specific FHEM module
showPackageInfo - list information about a specific FHEM package
zzGetModuleMETA.json - prints raw meta information of a FHEM module in JSON format
zzGetPackageMETA.json - prints raw meta information of a FHEM package in JSON format
Attributes
disable - disables the device
disabledForIntervals - disable device for interval time (13:00-18:30 or 13:00-18:30 22:00-23:00)
installerMode - sets the installation mode. May be update, developer or install with update being the default setting. Some get and/or set commands may be hidden or limited depending on this.
=end html
=begin html_DE
Installer
Eine deutsche Version der Dokumentation ist derzeit nicht vorhanden. Die englische Version ist hier zu finden: