From 0512bbf5e2a67aecd4752f900a8b3981cd398d54 Mon Sep 17 00:00:00 2001 From: jpawlowski Date: Fri, 22 Mar 2019 17:03:29 +0000 Subject: [PATCH] 98_Installer: add FHEM package support git-svn-id: https://svn.fhem.de/fhem/trunk@18993 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/98_Installer.pm | 588 ++++++++++++++++++++++++-------- fhem/FHEM/Meta.pm | 700 +++++++++++++++++++++++++++----------- 2 files changed, 931 insertions(+), 357 deletions(-) diff --git a/fhem/FHEM/98_Installer.pm b/fhem/FHEM/98_Installer.pm index e4740be89..8c202f38b 100644 --- a/fhem/FHEM/98_Installer.pm +++ b/fhem/FHEM/98_Installer.pm @@ -47,6 +47,7 @@ BEGIN { ReadingsTimestamp defs modules + packages Log Log3 Debug @@ -240,12 +241,24 @@ sub Get($$@) { my $ret = CreateMetadataList( $hash, $cmd, $args[0] ); return $ret; } - elsif ( lc($cmd) eq 'zzgetmeta.json' ) { + elsif ( lc($cmd) eq 'showpackageinfo' ) { + return "usage: $cmd PACKAGE" if ( @args != 1 ); + + my $ret = CreateMetadataList( $hash, $cmd, $args[0] ); + return $ret; + } + elsif ( lc($cmd) eq 'zzgetmodulemeta.json' ) { return "usage: $cmd MODULE" if ( @args != 1 ); my $ret = CreateRawMetaJson( $hash, $cmd, $args[0] ); return $ret; } + elsif ( lc($cmd) eq 'zzgetpackagemeta.json' ) { + return "usage: $cmd PACKAGE" if ( @args != 1 ); + + my $ret = CreateRawMetaJson( $hash, $cmd, $args[0] ); + return $ret; + } else { my @fhemModules; foreach ( sort { "\L$a" cmp "\L$b" } keys %modules ) { @@ -253,12 +266,21 @@ sub Get($$@) { push @fhemModules, $_; } + my @fhemPackages; + foreach ( sort { "\L$a" cmp "\L$b" } keys %packages ) { + push @fhemPackages, $_; + } + my $list = 'search' . ' showModuleInfo:FHEM,' . join( ',', @fhemModules ) - . ' zzGetMETA.json:FHEM,' - . join( ',', @fhemModules ); + . ' showPackageInfo:' + . join( ',', @fhemPackages ) + . ' zzGetModuleMETA.json:FHEM,' + . join( ',', @fhemModules ) + . ' zzGetPackageMETA.json:' + . join( ',', @fhemPackages ); return "Unknown argument $cmd, choose one of $list"; } @@ -274,12 +296,12 @@ sub Event ($$) { && $hash->{".fhem"}{installer}{cmd} =~ m/^(install|uninstall|update)(?: (.+))/i ); - my $cmd = $1; - my $packages = $2; + my $cmd = $1; + my $pkgs = $2; my $list; - foreach my $package ( split / /, $packages ) { + foreach my $package ( split / /, $pkgs ) { next unless ( $package =~ /^(?:@([\w-]+)\/)?([\w-]+)(?:@([\d\.=<>]+|latest))?$/ ); @@ -917,8 +939,6 @@ sub CreateSearchList ($$$) { ) ); - my $webname = - AttrVal( $hash->{CL}{SNAME}, 'webname', 'fhem' ); my $FW_CSRF = ( defined( $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} ) ? '&fwcsrf=' . $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} @@ -947,7 +967,7 @@ sub CreateSearchList ($$$) { . $txtClose . $colClose; push @ret, - $colOpen . $txtOpen . 'Module Name' . $txtClose . $colClose; + $colOpen . $txtOpen . 'Device Type' . $txtClose . $colClose; } $found++; $foundDevices++; @@ -958,9 +978,7 @@ sub CreateSearchList ($$$) { my $linkDev = $device; $linkDev = - '' . $device . '' @@ -968,9 +986,7 @@ sub CreateSearchList ($$$) { my $linkMod = $defs{$device}{TYPE}; $linkMod = - '{NAME} . ' showModuleInfo ' . $defs{$device}{TYPE} @@ -1020,9 +1036,7 @@ sub CreateSearchList ($$$) { my $link = $module; $link = - '{NAME} . ' showModuleInfo ' . $module @@ -1042,6 +1056,57 @@ sub CreateSearchList ($$$) { } push @ret, $tableClose if ($foundModules); + # search for matching module + my $foundPackages = 0; + $linecount = 1; + foreach my $package ( sort { "\L$a" cmp "\L$b" } keys %packages ) { + if ( $package =~ m/^.*$search.*$/i ) { + unless ($foundPackages) { + push @ret, '

Packages

' . $lb; + push @ret, $tableOpen; + push @ret, + $colOpenMinWidth + . $txtOpen + . 'Package Name' + . $txtClose + . $colClose; + push @ret, + $colOpen . $txtOpen . 'Abstract' . $txtClose . $colClose; + } + $found++; + $foundPackages++; + + my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; + + FHEM::Meta::Load($package); + + my $abstract = ''; + $abstract = $packages{$package}{META}{abstract} + if ( defined( $packages{$package}{META} ) + && defined( $packages{$package}{META}{abstract} ) ); + + my $link = $package; + $link = + '
' + . $package . '' + if ($html); + + $l .= $colOpenMinWidth . $link . $colClose; + $l .= + $colOpen . ( $abstract eq 'n/a' ? '' : $abstract ) . $colClose; + + $l .= $rowClose; + + push @ret, $l; + $linecount++; + } + } + push @ret, $tableClose if ($foundPackages); + # search for matching keyword my $foundKeywords = 0; $linecount = 1; @@ -1053,7 +1118,11 @@ sub CreateSearchList ($$$) { $found++; $foundKeywords++; - push @ret, '

# ' . $keyword . '

'; + my $descr = FHEM::Meta::GetKeywordDesc( $keyword, $lang ); + push @ret, + '# ' + . $keyword . ''; my @mAttrs = qw( modules @@ -1093,11 +1162,13 @@ sub CreateSearchList ($$$) { my $link = $item; $link = - '{NAME} - . ' showModuleInfo ' + . ( + $type eq 'Module' + ? ' showModuleInfo ' + : ' showPackageInfo ' + ) . $item . $FW_CSRF . '">' . $item . '' @@ -1123,106 +1194,163 @@ sub CreateSearchList ($$$) { # search for matching maintainer my $foundMaintainers = 0; - my %maintainerInfo; $linecount = 1; foreach my $maintainer ( sort { "\L$a" cmp "\L$b" } - keys %FHEM::Meta::maintainerModules + keys %FHEM::Meta::maintainers ) { if ( $maintainer =~ m/^.*$search.*$/i ) { - $maintainerInfo{$maintainer}{modules} = - $FHEM::Meta::maintainerModules{$maintainer}; - } - } - foreach my $maintainer ( - sort { "\L$a" cmp "\L$b" } - keys %FHEM::Meta::maintainerPackages - ) - { - if ( $maintainer =~ m/^.*$search.*$/i ) { - $maintainerInfo{$maintainer}{packages} = - $FHEM::Meta::maintainerPackages{$maintainer}; - } - } - foreach my $maintainer ( sort { "\L$a" cmp "\L$b" } keys %maintainerInfo ) { - next - unless ( defined( $maintainerInfo{$maintainer}{modules} ) - || defined( $maintainerInfo{$maintainer}{packages} ) ); - - unless ($foundMaintainers) { - push @ret, '

Authors & Maintainers

' . $lb; - push @ret, $tableOpen; - push @ret, - $colOpenMinWidth . $txtOpen . 'Author' . $txtClose . $colClose; - push @ret, $colOpen . $txtOpen . 'Modules' . $txtClose . $colClose; - push @ret, $colOpen . $txtOpen . 'Packages' . $txtClose . $colClose; - } - $found++; - $foundMaintainers++; - - my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; - - my $modules = ''; - my $packages = ''; - - my $counter = 0; - foreach my $module ( sort { "\L$a" cmp "\L$b" } - @{ $maintainerInfo{$maintainer}{modules} } ) - { - $modules .= $lb if ($counter); - $counter++; - - if ($html) { - $modules .= - '' - . $module . ''; + unless ($foundMaintainers) { + push @ret, '

Authors & Maintainers

' . $lb; + push @ret, $tableOpen; + push @ret, + $colOpenMinWidth . $txtOpen . 'Name' . $txtClose . $colClose; + push @ret, + $colOpen . $txtOpen . 'Modules' . $txtClose . $colClose; + push @ret, + $colOpen . $txtOpen . 'Packages' . $txtClose . $colClose; } - else { - $modules .= $module; + $found++; + $foundMaintainers++; + + my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; + + my $mods = ''; + if ( defined( $FHEM::Meta::maintainers{$maintainer}{modules} ) ) { + my $counter = 0; + foreach my $mod ( sort { "\L$a" cmp "\L$b" } + @{ $FHEM::Meta::maintainers{$maintainer}{modules} } ) + { + if ($html) { + $mods .= '
' if ($counter); + $mods .= + '' + . $mod . ''; + } + else { + $mods .= "\n" unless ($counter); + $mods .= $mod; + } + $counter++; + } } + my $pkgs = ''; + if ( defined( $FHEM::Meta::maintainers{$maintainer}{packages} ) ) { + my $counter = 0; + foreach my $pkg ( sort { "\L$a" cmp "\L$b" } + @{ $FHEM::Meta::maintainers{$maintainer}{packages} } ) + { + if ($html) { + $pkgs .= '
' if ($counter); + $pkgs .= + '' + . $pkg . ''; + } + else { + $pkgs .= "\n" unless ($counter); + $pkgs .= $pkg; + } + $counter++; + } + } + + $l .= $colOpenMinWidth . $maintainer . $colClose; + $l .= $colOpen . $mods . $colClose; + $l .= $colOpen . $pkgs . $colClose; + + $l .= $rowClose; + + push @ret, $l; + $linecount++; } - $counter = 0; - foreach my $package ( sort { "\L$a" cmp "\L$b" } - @{ $maintainerInfo{$maintainer}{packages} } ) - { - $packages .= $lb if ($counter); - $counter++; - - # if ($html) { - # $packages .= - # '' - # . $package . ''; - # } - # else { - $packages .= $package; - - # } - } - - $l .= $colOpenMinWidth . $maintainer . $colClose; - $l .= $colOpen . $modules . $colClose; - $l .= $colOpen . $packages . $colClose; - - $l .= $rowClose; - - push @ret, $l; - $linecount++; } push @ret, $tableClose if ($foundMaintainers); + # search for matching Perl package + my $foundPerl = 0; + $linecount = 1; + foreach my $dependent ( + sort { "\L$a" cmp "\L$b" } + keys %{ $FHEM::Meta::dependents{pkgs} } + ) + { + next if ( FHEM::Meta::ModuleIsPerlCore($dependent) ); + next if ( FHEM::Meta::ModuleIsPerlPragma($dependent) ); + next if ( FHEM::Meta::ModuleIsInternal($dependent) ); + + if ( $dependent =~ m/^.*$search.*$/i ) { + unless ($foundPerl) { + push @ret, '

Perl packages

' . $lb; + push @ret, $tableOpen; + push @ret, + $colOpenMinWidth . $txtOpen . 'Name' . $txtClose . $colClose; + push @ret, + $colOpen + . $txtOpen + . 'Referenced from' + . $txtClose + . $colClose; + } + $found++; + $foundPerl++; + + my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; + + my $references = ''; + my $counter = 0; + foreach my $pkgReq (qw(requires recommends suggests)) { + next + unless ( + defined( + $FHEM::Meta::dependents{pkgs}{$dependent}{$pkgReq} + ) + ); + + foreach my $mod ( sort { "\L$a" cmp "\L$b" } + @{ $FHEM::Meta::dependents{pkgs}{$dependent}{$pkgReq} } ) + { + if ($html) { + $references .= '
' if ($counter); + $references .= + '' + . $mod . ''; + } + else { + $references .= "\n" unless ($counter); + $references .= $mod; + } + $counter++; + } + } + + $l .= $colOpenMinWidth . $dependent . $colClose; + $l .= $colOpen . $references . $colClose; + + $l .= $rowClose; + + push @ret, $l; + $linecount++; + } + } + push @ret, $tableClose if ($foundPerl); + return $header . join( "\n", @ret ) . $footer; } @@ -1235,20 +1363,40 @@ sub CreateSearchList ($$$) { sub CreateMetadataList ($$$) { my ( $hash, $getCmd, $modName ) = @_; $modName = 'Global' if ( uc($modName) eq 'FHEM' ); + my $modType = lc($getCmd) eq 'showmoduleinfo' ? 'module' : 'package'; # disable automatic links to FHEM devices delete $FW_webArgs{addLinks}; return 'Unknown module ' . $modName - unless ( defined( $modules{$modName} ) ); + if ( $modType eq 'module' && !defined( $modules{$modName} ) ); FHEM::Meta::Load($modName); - return 'No metadata found about module ' . $modName - unless ( defined( $modules{$modName}{META} ) - && scalar keys %{ $modules{$modName}{META} } > 0 ); + return 'Unknown package ' . $modName + if ( $modType eq 'package' + && !defined( $packages{$modName} ) ); - my $modMeta = $modules{$modName}{META}; + return 'No metadata found about module ' + . $modName + if ( + $modType eq 'module' + && ( !defined( $modules{$modName}{META} ) + || scalar keys %{ $modules{$modName}{META} } == 0 ) + ); + + return 'No metadata found about package ' + . $modName + if ( + $modType eq 'package' + && ( !defined( $packages{$modName}{META} ) + || scalar keys %{ $packages{$modName}{META} } == 0 ) + ); + + my $modMeta = + $modType eq 'module' + ? $modules{$modName}{META} + : $packages{$modName}{META}; my @ret; my $html = defined( $hash->{CL} ) && $hash->{CL}{TYPE} eq "FHEMWEB" ? 1 : 0; @@ -1320,6 +1468,11 @@ sub CreateMetadataList ($$$) { AttrVal( 'global', 'language', 'EN' ) ) ); + my $FW_CSRF = ( + defined( $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} ) + ? '&fwcsrf=' . $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} + : '' + ); push @ret, $tableOpen; @@ -1401,6 +1554,9 @@ sub CreateMetadataList ($$$) { next if ( $mAttr eq 'release_date' && ( !defined( $modMeta->{x_vcs} ) ) ); + next + if ( $mAttr eq 'command_reference' + && $modType eq 'package' ); my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; my $mAttrName = $mAttr; @@ -1409,11 +1565,6 @@ sub CreateMetadataList ($$$) { my $webname = AttrVal( $hash->{CL}{SNAME}, 'webname', 'fhem' ); - my $FW_CSRF = ( - defined( $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} ) - ? '&fwcsrf=' . $defs{ $hash->{CL}{SNAME} }{CSRFTOKEN} - : '' - ); $l .= $colOpenMinWidth . $txtOpen . $mAttrName . $txtClose . $colClose; @@ -1850,7 +2001,9 @@ sub CreateMetadataList ($$$) { # Add filename to module name $mAttrVal .= ' (' . $modMeta->{x_file}[2] . ')' - if ( $mAttr eq 'name' && $modName ne 'Global' ); + if ( $modType eq 'module' + && $mAttr eq 'name' + && $modName ne 'Global' ); $l .= $mAttrVal . $colClose; } @@ -1895,9 +2048,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ if ( $alias eq $authorName ) { $authorNameEmail = - '{NAME} . ' search ' . $alias @@ -1910,9 +2061,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ if ($html) { $authorNameEmail = $authorName - . ', alias {NAME} . ' search ' . $alias @@ -1945,16 +2094,21 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ my $counter = 0; foreach my $keyword ( @{ $modMeta->{$mAttr} } ) { $l .= ', ' if ($counter); + my $descr = FHEM::Meta::GetKeywordDesc( $keyword, $lang ); if ($html) { $l .= - '{NAME} . ' search ' . $keyword - . $FW_CSRF . '">' + . $FW_CSRF . '"' + . ( + $descr ne '' + ? ' title="' . $descr . '"' + : '' + ) + . '>' . $keyword . ''; } else { @@ -1984,15 +2138,108 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ push @ret, $tableClose; + # show FHEM modules who use this package + # if ( $modType eq '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, '

FHEM internal dependencies

'; + + push @ret, + $txtOpen . 'Hint:' + . $txtClose + . $lb + . 'Dependents can only be shown here if they were loaded into the metadata cache before.' + . $lb + . $lb; + + push @ret, $tableOpen; + + push @ret, + $colOpenMinWidth + . $txtOpen + . 'Importance' + . $txtClose + . $colClose; + + push @ret, + $colOpenMinWidth + . $txtOpen + . 'Dependent Modules' + . $txtClose + . $colClose; + } + + my $l = $linecount % 2 == 0 ? $rowOpenEven : $rowOpenOdd; + + my $importance = $mAttr; + $importance = 'required' if ( $mAttr eq 'requires' ); + $importance = 'recommended' if ( $mAttr eq 'recommends' ); + $importance = 'suggested' if ( $mAttr eq 'suggests' ); + + $l .= $colOpenMinWidth . $importance . $colClose; + $l .= $colOpenMinWidth . $dependents . $colClose; + + $l .= $rowClose; + + push @ret, $l; + $linecount++; + } + } + + push @ret, $tableClose . $lb if ( $linecount > 1 ); + + # } + push @ret, '

System Prerequisites

'; - my $moduleUsage = - defined( $modules{$modName}{LOADED} ) - ? $colorGreen . 'IN USE' . $colorClose - : $txtOpen . 'not' . $txtClose . ' in use'; + if ( $modType eq 'module' ) { + my $moduleUsage = + defined( $modules{$modName}{LOADED} ) + ? $colorGreen . 'IN USE' . $colorClose + : $txtOpen . 'not' . $txtClose . ' in use'; - push @ret, $lb . 'This FHEM module is currently ' . $moduleUsage . '.' - unless ( $modName eq 'Global' ); + push @ret, $lb . 'This FHEM module is currently ' . $moduleUsage . '.' + unless ( $modName eq 'Global' ); + } push @ret, '

Perl Packages

'; if ( defined( $modMeta->{prereqs} ) @@ -2012,7 +2259,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ if ( defined( $modMeta->{x_prereqs_src} ) && $modMeta->{x_prereqs_src} ne 'META.json' ); - my @mAttrs = qw( + @mAttrs = qw( requires recommends suggests @@ -2028,7 +2275,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ push @ret, $colOpenMinWidth . $txtOpen . 'Status' . $txtClose . $colClose; - my $linecount = 1; + $linecount = 1; foreach my $mAttr (@mAttrs) { next unless ( defined( $modMeta->{prereqs}{runtime}{$mAttr} ) @@ -2111,6 +2358,20 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ && !$isPerlPragma && $prereq ne 'perl' ); + $prereq = + '' + . $prereq . '' + if ( $html + && $isFhem ); + $l .= $colOpenMinWidth . $prereq @@ -2161,7 +2422,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ push @ret, $colOpenMinWidth . $txtOpen . 'Status' . $txtClose . $colClose; - my $linecount = 1; + $linecount = 1; foreach my $mAttr (@mAttrs) { next unless ( defined( $modMeta->{x_prereqs_nodejs}{runtime}{$mAttr} ) @@ -2263,7 +2524,7 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ push @ret, $colOpenMinWidth . $txtOpen . 'Status' . $txtClose . $colClose; - my $linecount = 1; + $linecount = 1; foreach my $mAttr (@mAttrs) { next unless ( defined( $modMeta->{x_prereqs_python}{runtime}{$mAttr} ) @@ -2376,21 +2637,33 @@ m/^([^<>\n\r]+?)(?:\s+(\(last release only\)))?(?:\s+(?:<(.*)>))?$/ sub CreateRawMetaJson ($$$) { my ( $hash, $getCmd, $modName ) = @_; $modName = 'Global' if ( uc($modName) eq 'FHEM' ); - - return '{}' - unless ( defined( $modules{$modName} ) ); + my $modType = lc($getCmd) eq 'zzgetmodulemeta.json' ? 'module' : 'package'; FHEM::Meta::Load($modName); return '{}' - unless ( defined( $modules{$modName}{META} ) - && scalar keys %{ $modules{$modName}{META} } > 0 ); + 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; - return $j->encode( $modules{$modName}{META} ); + if ( $modType eq 'module' ) { + return $j->encode( $modules{$modName}{META} ); + + } + else { + return $j->encode( $packages{$modName}{META} ); + } } # Checks whether a perl package is installed in the system @@ -2404,6 +2677,23 @@ sub __IsInstalledPerl($) { return FHEM::Meta->VERSION() if ( $pkg eq 'FHEM::Meta' || $pkg eq 'Meta' ); + my $fname = $pkg; + $fname =~ s/^.*://g; # strip away any parent module names + + # This is an internal Perl package + if ( defined( $packages{$fname} ) ) { + return $packages{$fname}{META}{version} + if ( defined( $packages{$fname}{META} ) ); + return 1; + } + + # This is an internal Perl package + if ( defined( $modules{$fname} ) ) { + return $modules{$fname}{META}{version} + if ( defined( $modules{$fname}{META} ) ); + return 1; + } + eval "require $pkg;"; return 0 @@ -2525,7 +2815,7 @@ sub __aUniq { "abstract": "Modul zum Update von FHEM, zur Installation von Drittanbieter FHEM Modulen und der Verwaltung von Systemvoraussetzungen" } }, - "version": "v0.1.0", + "version": "v0.2.0", "release_status": "testing", "author": [ "Julian Pawlowski " diff --git a/fhem/FHEM/Meta.pm b/fhem/FHEM/Meta.pm index c91167bf1..4efc03346 100644 --- a/fhem/FHEM/Meta.pm +++ b/fhem/FHEM/Meta.pm @@ -85,9 +85,8 @@ our %supportForumCategories = ( boardId => 18, }, 'Automatisierung' => { - description => -'Themen um Aufgaben mit FHEM zu automatisieren (at,notify,structure,watchdog,etc.)', - boardId => 20, + description => 'Aufgaben mit FHEM automatisieren', + boardId => 20, 'DOIF' => { boardId => 73, @@ -103,9 +102,8 @@ our %supportForumCategories = ( boardId => 52, }, 'Frontends' => { - description => - 'Themen zu FHEM Frontends wie z.B. FHEMWEB, FLOORPLAN, etc.', - boardId => 19, + description => 'FHEM Frontends', + boardId => 19, 'FHEMWEB' => { boardId => 75, @@ -137,50 +135,50 @@ our %supportForumCategories = ( boardId => 37, }, 'Sonstiges' => { - description => 'Sonstige Themen mit Bezug zu FHEM', + description => 'Sonstiger Bezug zu FHEM', boardId => 46, }, }, 'FHEM - Hausautomations-Systeme' => { '1Wire' => { - description => 'Themen zu 1Wire', + description => '1Wire', boardId => 26, }, 'EnOcean' => { - description => 'Themen zu EnOcean', + description => 'EnOcean', boardId => 27, }, 'Home Connect' => { description => -'Themen zu Geräten, API und Modulentwicklung rund um Home Connect in FHEM', + 'Geräte, API und Modulentwicklung rund um Home Connect', boardId => 97, }, 'Homematic' => { - description => 'Themen zu HomeMatic und Zubehör', + description => 'HomeMatic und Zubehör', boardId => 22, }, 'InterTechno' => { - description => 'Themen zu InterTechno', + description => 'InterTechno', boardId => 24, }, 'KNX/EIB' => { - description => 'Themen zu KNX/EIB', + description => 'KNX/EIB', boardId => 51, }, 'MAX' => { - description => 'Themen zu MAX', + description => 'MAX', boardId => 23, }, 'MQTT' => { - description => 'Themen zu MQTT', + description => 'MQTT', boardId => 94, }, 'RFXTRX' => { - description => 'Themen zu RFXTRX', + description => 'RFXTRX', boardId => 25, }, 'SlowRF' => { - description => 'Themen zu FS20, FHT, EM, WS, HMS', + description => 'FS20, FHT, EM, WS, HMS', boardId => 21, }, 'Zigbee' => { @@ -188,7 +186,7 @@ our %supportForumCategories = ( boardId => 99, }, 'ZWave' => { - description => 'Themen zu ZWave', + description => 'ZWave', boardId => 28, }, 'Sonstige Systeme' => { @@ -196,9 +194,8 @@ our %supportForumCategories = ( boardId => 29, }, 'Unterstützende Dienste' => { - description => -'Themen zu unterstützenden Diensten und Modulen wie z.B. Calendar, HCS, Twilight, Weather, etc.', - boardId => 44, + description => 'unterstützende Dienste und Module', + boardId => 44, 'Kalendermodule' => { boardId => 85, @@ -210,28 +207,28 @@ our %supportForumCategories = ( }, 'FHEM - Hardware' => { 'FRITZ!Box' => { - description => 'FHEM auf AVM FRITZ!Box', + description => 'AVM FRITZ!Box', boardId => 31, }, 'Network Attached Storage (NAS)' => { - description => 'FHEM auf NAS-Systemen (Synology, etc.)', + description => 'NAS-Systeme (Synology, etc.)', boardId => 30, }, 'Einplatinencomputer' => { description => -'FHEM auf Einplatinencomputern (z.B. Raspberry Pi, Beagle Bone, etc.)', + 'Einplatinencomputer (z.B. Raspberry Pi, Beagle Bone, etc.)', boardId => 32, }, 'Server - Linux' => { - description => 'FHEM auf Linux Servern', + description => 'Linux Server', boardId => 33, }, 'Server - Mac' => { - description => 'FHEM auf macOS', + description => 'Apple macOS', boardId => 63, }, 'Server - Windows' => { - description => 'FHEM auf Windows Server', + description => 'Microsoft Windows Server', boardId => 34, }, }, @@ -246,14 +243,12 @@ our %supportForumCategories = ( boardId => 60, }, 'Multimedia' => { - description => - 'Themen zu Multimediageräten, TV, Fernbedienungen, etc.', - boardId => 53, + description => 'Multimediageräte, TV, Fernbedienungen, etc.', + boardId => 53, }, 'Solaranlagen' => { - description => - 'Themen rund um Solaranlagen zur Wärme- oder Stromgewinnung', - boardId => 61, + description => 'Solaranlagen zur Wärme- oder Stromgewinnung', + boardId => 61, }, }, 'FHEM - Entwicklung' => { @@ -276,9 +271,8 @@ our %supportForumCategories = ( boardId => 6, }, 'Hard- und Firmware' => { - description => -'Themen in Bezug auf die CUL Hard- und Firmware. Dazu zählen alle Geräte der CUL und CUN Familie.', - boardId => 47, + description => 'CUL/CUN Hard- und Firmware', + boardId => 47, }, }, 'CUL - Entwicklung' => { @@ -299,7 +293,7 @@ our %supportForumCategories = ( 'Verschiedenes' => { 'Bastelecke' => { description => -'Projekte für Bastler, die gerne auch mal zum Lötkolben greifen.', +'Projekte für Bastler, die gerne auch mal zum Lötkolben greifen', boardId => 17, 'ESP8266' => { @@ -324,12 +318,10 @@ our %supportForumCategories = ( boardId => 67, }, 'Marktplatz - Kommerzielle Güter' => { - description => '', - boardId => 101, + boardId => 101, }, 'Marktplatz - Kommerzielle Dienstleistungen' => { - description => '', - boardId => 100, + boardId => 100, }, 'Off-Topic' => { description => 'Allgemeine Themen', @@ -347,13 +339,10 @@ our %supportForumCategories = ( } ); -our %moduleMaintainers; -our %packageMaintainers; -our %fileMaintainers; - -our %maintainerModules; -our %maintainerPackages; -our %maintainerFile; +our %maintainers; # maintainers and what they maintain +our %moduleMaintainers; # modules and who maintains them +our %packageMaintainers; # packages and who maintains them +our %fileMaintainers; # files and who maintains them our $coreUpdate; our %corePackageUpdates; @@ -364,6 +353,79 @@ our %packageUpdates; our %fileUpdates; our %keywords; +our %keywordDescription = ( + 'fhem-core' => { + 'en' => 'Belongs to the official FHEM core software', + 'de' => 'Gehört zum offiziellen Kern von FHEM', + }, + 'fhem-3rdparty' => { + 'en' => + 'Originates from a source outside of the official FHEM core software', + 'de' => + 'Stammt aus einer Quelle außerhalb des offiziellen Kern von FHEM', + }, + 'fhem-commercial' => { + 'en' => 'commercial relation', + 'de' => 'kommerzieller Zusammenhang', + }, + 'fhem-mod' => { + 'en' => 'FHEM module', + 'de' => 'FHEM Modul', + }, + 'fhem-pkg' => { + 'en' => 'FHEM development package that is used by FHEM modules', + 'de' => 'FHEM Entwickler Paket, welches in FHEM Modulen verwendet wird', + }, + 'fhem-mod-3rdparty' => { + 'en' => +'FHEM module that originates from a source outside of the official FHEM core software', + 'de' => +'FHEM Modul, welches aus einer Quelle außerhalb des offiziellen Kern von FHEM stammt', + }, + 'fhem-pkg-3rdparty' => { + 'en' => +'FHEM development package that originates from a source outside of the official FHEM core software', + 'de' => +'FHEM Entwickler Paket, welches aus einer Quelle außerhalb des offiziellen Kern von FHEM stammt', + }, + 'fhem-mod-commercial' => { + 'en' => +'commercial FHEM module that originates from a source outside of the official FHEM core software', + 'de' => +'kommerzielles FHEM Modul, welches aus einer Quelle außerhalb des offiziellen Kern von FHEM stammt', + }, + 'fhem-pkg-commercial' => { + 'en' => +'commercial FHEM development package that originates from a source outside of the official FHEM core software', + 'de' => +'kommerzielles FHEM Entwickler Paket, welches aus einer Quelle außerhalb des offiziellen Kern von FHEM stammt', + }, + 'fhem-mod-local' => { + 'en' => 'FHEM module that is maintained locally on the machine', + 'de' => 'FHEM Modul, welches lokal auf der Maschine verwaltet wird', + }, + 'fhem-pkg-local' => { + 'en' => + 'FHEM development package that is maintained locally on the machine', + 'de' => +'FHEM Entwickler Paket, welches lokal auf der Maschine verwaltet wird', + }, + 'fhem-mod-command' => { + 'en' => 'FHEM console text command w/o any FHEM device object visible', + 'de' => + 'FHEM Konsolen Text Kommando ohne sichtbares FHEM Geräte-Objekt', + }, + 'fhem-mod-device' => { + 'en' => 'represents a physical device', + 'de' => 'repräsentiert ein physisches Gerät', + }, + 'fhem-mod-helper' => { + 'en' => 'logical, non-physical device', + 'de' => 'logisches, nicht physisches Gerät', + }, +); + +our %dependents; # Package internal variables # @@ -538,6 +600,7 @@ my @perlCoreModules = qw( subs utf8 vars + version vmsish warnings warnings::register @@ -700,12 +763,20 @@ sub Load(;$$) { } foreach my $modName (@lmodules) { + $modName = 'Global' if ( uc($modName) eq 'FHEM' ); + my $type; - my $type = ( - exists( $modules{$modName} ) - ? 'module' - : ( exists( $packages{$modName} ) ? 'package' : undef ) - ); + 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); # Abort when module file was not indexed by @@ -727,58 +798,70 @@ sub Load(;$$) { && !$reload && defined( $packages{$modName}{META} ) && ref( $packages{$modName}{META} ) eq "HASH" ); + next + if ( $type eq 'module+package' + && !$reload + && defined( $modules{$modName}{META} ) + && ref( $modules{$modName}{META} ) eq "HASH" + && defined( $packages{$modName}{META} ) + && ref( $packages{$modName}{META} ) eq "HASH" ); - if ( $type eq 'module' + if ( ( $type eq 'module' || $type eq 'module+package' ) && defined( $modules{$modName}{META} ) ) { delete $modules{$modName}{META}; } - elsif ( $type eq 'package' + if ( ( $type eq 'package' || $type eq 'module+package' ) && defined( $packages{$modName}{META} ) ) { delete $packages{$modName}{META}; } - my $filePath; - if ( $modName eq 'Global' ) { - $filePath = $attr{global}{modpath} . "/fhem.pl"; - } - elsif ( $modName eq 'configDB' ) { - $filePath = $attr{global}{modpath} . "/configDB.pm"; - } - else { - $filePath = - $attr{global}{modpath} - . "/FHEM/" - . ( $type eq 'module' ? $modules{$modName}{ORDER} . '_' : '' ) - . $modName . '.pm'; - } - - my $ret = - InitMod( $filePath, - ( $type eq 'module' ? $modules{$modName} : $packages{$modName} ), - 1 ); - push @rets, $@ if ( $@ && $@ ne '' ); - push @rets, $ret if ( $ret && $ret ne '' ); - - if ( $type eq 'module' ) { - $modules{$modName}{META}{generated_by} = - $packages{Meta}{META}{name} . ' ' - . version->parse($v)->normal . ", $t" - if ( defined( $modules{$modName} ) - && defined( $modules{$modName}{META} ) ); - - foreach my $devName ( devspec2array( 'TYPE=' . $modName ) ) { - SetInternals( $defs{$devName} ); + foreach my $type ( split( '\+', $type ) ) { + my $filePath; + if ( $modName eq 'Global' ) { + $filePath = $attr{global}{modpath} . "/fhem.pl"; + } + elsif ( $modName eq 'configDB' ) { + $filePath = $attr{global}{modpath} . "/configDB.pm"; + } + else { + $filePath = + $attr{global}{modpath} + . "/FHEM/" + . ( $type eq 'module' ? $modules{$modName}{ORDER} . '_' : '' ) + . $modName . '.pm'; + } + + my $ret = InitMod( + $filePath, + ( + $type eq 'module' ? $modules{$modName} : $packages{$modName} + ), + 1 + ); + push @rets, $@ if ( $@ && $@ ne '' ); + push @rets, $ret if ( $ret && $ret ne '' ); + + if ( $type eq 'module' ) { + $modules{$modName}{META}{generated_by} = + $packages{Meta}{META}{name} . ' ' + . version->parse($v)->normal . ", $t" + if ( defined( $modules{$modName} ) + && defined( $modules{$modName}{META} ) ); + + foreach my $devName ( devspec2array( 'TYPE=' . $modName ) ) { + SetInternals( $defs{$devName} ); + } + } + else { + $packages{$modName}{META}{generated_by} = + $packages{Meta}{META}{name} . ' ' + . version->parse($v)->normal . ", $t" + if ( defined( $packages{$modName} ) + && defined( $packages{$modName}{META} ) ); } - } - else { - $packages{$modName}{META}{generated_by} = - $packages{Meta}{META}{name} . ' ' - . version->parse($v)->normal . ", $t" - if ( defined( $packages{$modName} ) - && defined( $packages{$modName}{META} ) ); } } @@ -853,6 +936,22 @@ sub GetModuleSourceOrigin { return ''; } +sub GetKeywordDesc { + my ( $keyword, $lang ) = @_; + $lang = 'en' unless ($lang); + return '' unless ( defined( $keywordDescription{$keyword} ) ); + + # turn fallback language around + $lang = 'de' + if ( !defined( $keywordDescription{$keyword}{$lang} ) + && $lang eq 'en' ); + + return $keywordDescription{$keyword}{$lang} + if ( defined( $keywordDescription{$keyword}{$lang} ) ); + + return ''; +} + sub ModuleIsCore { my ($module) = @_; return GetModuleSourceOrigin($module) eq 'fhem' ? 1 : 0; @@ -860,19 +959,32 @@ sub ModuleIsCore { sub ModuleIsInternal { my ($module) = @_; - return 1 + return 0 if ( ModuleIsPerlCore($module) || ModuleIsPerlPragma($module) ); + + return 'module' if ( $module eq 'fhem.pl' || $module eq 'FHEM' - || $module eq 'Global' - || $module eq 'FHEM::Meta' - || $module eq 'Meta' ); + || $module eq 'Global' ); + + my $fname = $module; + $fname =~ s/^.*://g; # strip away any parent module names + + return 'package' + if ( $fname eq 'Meta' ); + + return 'module' + if ( defined( $modules{$fname} ) && !defined( $packages{$fname} ) ); + return 'package' + if ( defined( $packages{$fname} ) && !defined( $modules{$fname} ) ); + return 'module+package' + if ( defined( $modules{$fname} ) && defined( $packages{$fname} ) ); my $p = GetModuleFilepath($module); # if module has a relative path, # assume it is part of FHEM return $p && ( $p =~ m/^(\.\/)?FHEM\/.+/ || $p =~ m/^(\.\/)?[^\/]+\.pm$/ ) - ? 1 + ? 'file' : 0; } @@ -981,10 +1093,11 @@ sub __GetMetadata { my $versionFrom; my $authorName; # not in use, see below my $authorMail; # not in use, see below - my $item_modtype; + my $item_modsubtype; my $item_summary; my $item_summary_DE; my $modName; + my $modType; # Static meta information $modMeta->{dynamic_config} = 1; @@ -1014,6 +1127,7 @@ sub __GetMetadata { $modMeta->{x_file} = \@file; $modName = $file[4]; + $modType = $file[3] || $file[2] eq 'fhem.pl' ? 'mod' : 'pkg'; } # grep info from file content @@ -1131,12 +1245,12 @@ m/(->\{VERSION\}\s+=\s+[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(?:\.\d{1,3})?)))/i # read items from POD elsif ($skip - && !$item_modtype + && !$item_modsubtype && $l =~ m/^=item\s+(device|helper|command)\s*$/i ) { return "=item (device|helper|command) pod must occur only once" - if ($item_modtype); - $item_modtype = lc($1); + if ($item_modsubtype); + $item_modsubtype = lc($1); } elsif ($skip && !$item_summary_DE @@ -1391,7 +1505,7 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? # used by this FHEM module. # We're not going deeper down for Meta.pm itself, # that means Meta.pm manual prereqs need to cover this. - if ( $modMeta->{x_file}[4] ne 'Meta' + if ( $modName ne 'Meta' && defined( $modMeta->{prereqs} ) && defined( $modMeta->{prereqs}{runtime} ) ) { @@ -1403,6 +1517,24 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? my $pkg ( keys %{ $modMeta->{prereqs}{runtime}{$pkgReq} } ) { + # Add to dependency index: + # packages to FHEM modules/packages + push @{ $dependents{pkgs}{$pkg}{$pkgReq} }, $modName + unless ( + grep ( /^$modName$/, + @{ $dependents{pkgs}{$pkg}{$pkgReq} } ) ); + + # dependents list + push @{ $dependents{$pkgReq}{$pkg} }, + $modName + unless ( + ModuleIsInternal($pkg) + || ( defined( $dependents{$pkgReq} ) + && defined( $dependents{$pkgReq}{$pkg} ) + && grep ( /^$modName$/, + @{ $dependents{$pkgReq}{$pkg} } ) ) + ); + # Found prereq that is a FHEM package if ( exists( $packages{$pkg} ) ) { Load($pkg); @@ -1493,9 +1625,10 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? $modMeta->{x_vcs} = \@vcs; # if there is no maintainer, we will assign someone acting - if ( !defined( $moduleMaintainers{ $modMeta->{x_file}[4] } ) - && !defined( $packageMaintainers{ $modMeta->{x_file}[4] } ) - && $modMeta->{x_file}[4] ne 'Meta' ) + if ( !defined( $moduleMaintainers{$modName} ) + && !defined( $packageMaintainers{$modName} ) + && GetModuleSourceOrigin($modName) ne '' + && $modName ne 'Meta' ) { Log 4, __PACKAGE__ @@ -1505,24 +1638,82 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? . "Added acting maintainer with limited support status"; # add acting maintainer - #TODO detect if module or package - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][0] = - $modMeta->{x_file}[0]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][1] = - $modMeta->{x_file}[1]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][2] = - $modMeta->{x_file}[2]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][3] = - $modMeta->{x_file}[3]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][4] = - $modMeta->{x_file}[4]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[0][5] = - $modMeta->{x_file}[5]; - $moduleMaintainers{ $modMeta->{x_file}[4] }[1] = - 'rudolfkoenig (acting)'; - $moduleMaintainers{ $modMeta->{x_file}[4] }[2] = 'limited'; - $moduleMaintainers{ $modMeta->{x_file}[4] }[3] = - __GetSupportForum('Sonstiges'); + if ( $modType eq 'mod' ) { + $moduleMaintainers{$modName}[0][0] = + $modMeta->{x_file}[0]; + $moduleMaintainers{$modName}[0][1] = + $modMeta->{x_file}[1]; + $moduleMaintainers{$modName}[0][2] = + $modMeta->{x_file}[2]; + $moduleMaintainers{$modName}[0][3] = + $modMeta->{x_file}[3]; + $moduleMaintainers{$modName}[0][4] = + $modName; + $moduleMaintainers{$modName}[0][5] = + $modMeta->{x_file}[5]; + $moduleMaintainers{$modName}[1] = 'rudolfkoenig (acting)'; + $moduleMaintainers{$modName}[2] = 'limited'; + $moduleMaintainers{$modName}[3] = + __GetSupportForum('Sonstiges'); + + # add acting maintainer to maintainer hashes + my $lastEditor = 'rudolfkoenig (acting)'; + push @{ $maintainers{$lastEditor}{modules} }, + $modName + unless ( + defined( $maintainers{$lastEditor} ) + && grep( m/^$lastEditor$/i, + @{ $maintainers{$lastEditor}{modules} } ) + ); + + # add last committer to maintainer hashes + $lastEditor = $modMeta->{x_vcs}[15]; + push @{ $maintainers{$lastEditor}{modules} }, + $modName + unless ( + defined( $maintainers{$lastEditor} ) + && grep( m/^$lastEditor$/i, + @{ $maintainers{$lastEditor}{modules} } ) + ); + } + else { + $packageMaintainers{$modName}[0][0] = + $modMeta->{x_file}[0]; + $packageMaintainers{$modName}[0][1] = + $modMeta->{x_file}[1]; + $packageMaintainers{$modName}[0][2] = + $modMeta->{x_file}[2]; + $packageMaintainers{$modName}[0][3] = + $modMeta->{x_file}[3]; + $packageMaintainers{$modName}[0][4] = + $modName; + $packageMaintainers{$modName}[0][5] = + $modMeta->{x_file}[5]; + $packageMaintainers{$modName}[1] = 'rudolfkoenig (acting)'; + $packageMaintainers{$modName}[2] = 'limited'; + $packageMaintainers{$modName}[3] = + __GetSupportForum('Sonstiges'); + + # add acting maintainer to maintainer hashes + my $lastEditor = 'rudolfkoenig (acting)'; + push @{ $maintainers{$lastEditor}{packages} }, + $modName + unless ( + defined( $maintainers{$lastEditor} ) + && grep( m/^$lastEditor$/i, + @{ $maintainers{$lastEditor}{packages} } ) + ); + + # add last committer to maintainer hashes + $lastEditor = $modMeta->{x_vcs}[15]; + push @{ $maintainers{$lastEditor}{packages} }, + $modName + unless ( + defined( $maintainers{$lastEditor} ) + && grep( m/^$lastEditor$/i, + @{ $maintainers{$lastEditor}{packages} } ) + ); + } } } @@ -1567,19 +1758,18 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? # meta name unless ( defined( $modMeta->{name} ) ) { - if ( $modMeta->{x_file}[4] eq 'Global' ) { + if ( $modName eq 'Global' ) { $modMeta->{name} = 'FHEM'; } else { $modMeta->{name} = $modMeta->{x_file}[1]; $modMeta->{name} =~ s/^\.\///; $modMeta->{name} =~ s/\/$//; - $modMeta->{name} =~ s/FHEM\/lib//; $modMeta->{name} =~ s/\//::/g; } - if ( $modMeta->{x_file}[4] ne 'Global' ) { + if ( $modName ne 'Global' ) { $modMeta->{name} .= '::' if ( $modMeta->{name} ); - $modMeta->{name} .= $modMeta->{x_file}[4]; + $modMeta->{name} .= $modName; } } @@ -1606,41 +1796,39 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? } # Fill mandatory attributes - unless ( $modMeta->{abstract} ) { - $modMeta->{abstract} = 'n/a'; - } - unless ( $modMeta->{description} ) { - $modMeta->{description} = 'n/a'; - } - unless ( $modMeta->{release_status} ) { - $modMeta->{release_status} = 'stable'; - } - unless ( $modMeta->{license} ) { - $modMeta->{license} = 'unknown'; - } - unless ( $modMeta->{author} ) { - $modMeta->{author} = ['unknown <>']; - } - unless ( defined( $modMeta->{x_support_status} ) ) { - if ( defined( $moduleMaintainers{$modName} ) - && ref( $moduleMaintainers{$modName} ) eq 'ARRAY' - && defined( $moduleMaintainers{$modName}[2] ) ) - { - $modMeta->{x_support_status} = - $moduleMaintainers{$modName}[2]; - } - elsif ( defined( $modMeta->{resources} ) - && $modMeta->{resources}{x_support_community} ) - { - $modMeta->{x_support_status} = 'supported'; + unless ( defined( $modMeta->{abstract} ) ) { + if ( $modType eq 'pkg' ) { + $modMeta->{abstract} = + 'FHEM development package that is used by FHEM modules'; } else { - $modMeta->{x_support_status} = 'unknown'; + $modMeta->{abstract} = 'n/a'; } } + unless ( defined( $modMeta->{description} ) ) { + if ( $modType eq 'pkg' ) { + $modMeta->{description} = + 'This is a FHEM-included Perl package that does not show up as a ' + . 'regular device in FHEM. \\n' + . 'That is because it provides additional functionality that can be used by real ' + . 'FHEM modules in order to share the same code basis.'; + } + else { + $modMeta->{description} = 'n/a'; + } + } + unless ( defined( $modMeta->{release_status} ) ) { + $modMeta->{release_status} = 'stable'; + } + unless ( defined( $modMeta->{license} ) ) { + $modMeta->{license} = 'unknown'; + } + unless ( defined( $modMeta->{author} ) ) { + $modMeta->{author} = ['unknown <>']; + } # Generate META information for FHEM core modules - if ( GetModuleSourceOrigin( $modMeta->{x_file}[4] ) eq 'fhem' ) { + if ( GetModuleSourceOrigin($modName) eq 'fhem' ) { if ( !$modMeta->{release_status} || $modMeta->{release_status} eq 'stable' ) @@ -1660,17 +1848,30 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? } if ( !$modMeta->{author} || $modMeta->{author}[0] eq 'unknown <>' ) { - if ( defined( $moduleMaintainers{ $modMeta->{x_file}[4] } ) ) { + if ( defined( $moduleMaintainers{$modName} ) ) { shift @{ $modMeta->{author} } if ( $modMeta->{author} && $modMeta->{author}[0] eq 'unknown <>' ); - foreach ( - split( - '/|,', $moduleMaintainers{ $modMeta->{x_file}[4] }[1] - ) - ) - { + foreach ( split( '/|,', $moduleMaintainers{$modName}[1] ) ) { + push @{ $modMeta->{author} }, "$_ <>"; + } + + # last update was not by one of the named authors + if ( defined( $modMeta->{x_vcs} ) ) { + my $lastEditor = $modMeta->{x_vcs}[15] . ' <>'; + push @{ $modMeta->{author} }, + $modMeta->{x_vcs}[15] . ' (last release only) <>' + unless ( + grep( m/^$lastEditor$/i, @{ $modMeta->{author} } ) ); + } + } + elsif ( defined( $packageMaintainers{$modName} ) ) { + shift @{ $modMeta->{author} } + if ( $modMeta->{author} + && $modMeta->{author}[0] eq 'unknown <>' ); + + foreach ( split( '/|,', $packageMaintainers{$modName}[1] ) ) { push @{ $modMeta->{author} }, "$_ <>"; } @@ -1685,13 +1886,24 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? } } unless ( $modMeta->{x_fhem_maintainer} ) { - if ( defined( $moduleMaintainers{ $modMeta->{x_file}[4] } ) ) { - foreach ( - split( - '/|,', $moduleMaintainers{ $modMeta->{x_file}[4] }[1] - ) - ) - { + if ( defined( $moduleMaintainers{$modName} ) ) { + foreach ( split( '/|,', $moduleMaintainers{$modName}[1] ) ) { + push @{ $modMeta->{x_fhem_maintainer} }, $_; + } + + # last update was not by one of the named authors + if ( defined( $modMeta->{x_vcs} ) ) { + my $lastEditor = $modMeta->{x_vcs}[15]; + push @{ $modMeta->{x_fhem_maintainer} }, + $modMeta->{x_vcs}[15] + unless ( + grep( m/^$lastEditor$/i, + @{ $modMeta->{x_fhem_maintainer} } ) + ); + } + } + elsif ( defined( $packageMaintainers{$modName} ) ) { + foreach ( split( '/|,', $packageMaintainers{$modName}[1] ) ) { push @{ $modMeta->{x_fhem_maintainer} }, $_; } @@ -1726,13 +1938,15 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? } } - unless ( defined( $modMeta->{resources} ) - && defined( $modMeta->{resources}{x_commandref} ) ) + unless ( + $modType ne 'mod' + || ( defined( $modMeta->{resources} ) + && defined( $modMeta->{resources}{x_commandref} ) ) + ) { if ( defined( $modMeta->{x_vcs} ) ) { $modMeta->{resources}{x_commandref}{web} = - 'https://fhem.de/commandref.html'; - $modMeta->{resources}{x_commandref}{modpath} = '#'; + 'https://fhem.de/commandref.html#' . $modName; } } @@ -1801,25 +2015,51 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? delete $modMeta->{x_fhem_maintainer_github}; } + unless ( defined( $modMeta->{x_support_status} ) ) { + if ( defined( $moduleMaintainers{$modName} ) + && ref( $moduleMaintainers{$modName} ) eq 'ARRAY' + && defined( $moduleMaintainers{$modName}[2] ) ) + { + $modMeta->{x_support_status} = + $moduleMaintainers{$modName}[2]; + } + elsif (defined( $packageMaintainers{$modName} ) + && ref( $packageMaintainers{$modName} ) eq 'ARRAY' + && defined( $packageMaintainers{$modName}[2] ) ) + { + $modMeta->{x_support_status} = + $packageMaintainers{$modName}[2]; + } + elsif ( defined( $modMeta->{resources} ) + && $modMeta->{resources}{x_support_community} ) + { + $modMeta->{x_support_status} = 'supported'; + } + else { + $modMeta->{x_support_status} = 'unknown'; + } + } + # Filter keywords that are reserved for FHEM core if ( defined( $modMeta->{keywords} ) && @{ $modMeta->{keywords} } > 0 ) { my @filtered; - foreach ( @{ $modMeta->{keywords} } ) { - push @filtered, lc($_) - unless ( lc($_) eq 'fhem-core' - || lc($_) eq 'fhem-mod' - || lc($_) eq 'fhem-pkg' - || lc($_) eq 'fhem-3rdparty' - || lc($_) eq 'fhem-mod-3rdparty' - || lc($_) eq 'fhem-pkg-3rdparty' - || lc($_) eq 'fhem-mod-local' - || lc($_) eq 'fhem-pkg-local' - || lc($_) eq 'fhem-commercial' - || lc($_) eq 'fhem-mod-commercial' - || lc($_) eq 'fhem-pkg-commercial' ); + foreach my $keyword ( @{ $modMeta->{keywords} } ) { + push @filtered, lc($keyword) + unless ( lc($keyword) eq 'fhem-core' + || lc($keyword) eq 'fhem-mod' + || lc($keyword) eq 'fhem-pkg' + || lc($keyword) eq 'fhem-3rdparty' + || lc($keyword) eq 'fhem-mod-3rdparty' + || lc($keyword) eq 'fhem-pkg-3rdparty' + || lc($keyword) eq 'fhem-mod-local' + || lc($keyword) eq 'fhem-pkg-local' + || lc($keyword) eq 'fhem-commercial' + || lc($keyword) eq 'fhem-mod-commercial' + || lc($keyword) eq 'fhem-pkg-commercial' ); } + delete $modMeta->{keywords}; $modMeta->{keywords} = \@filtered; } @@ -1843,35 +2083,37 @@ m/(^#\s+(?:\d{1,2}\.\d{1,2}\.(?:\d{2}|\d{4})\s+)?[^v\d]*(v?(?:\d{1,3}\.\d{1,3}(? if ( defined( $modMeta->{resources} ) && defined( $modMeta->{resources}{x_support_commercial} ) ) { - my $modType = $modMeta->{x_file}[3] ? 'mod' : 'pkg'; push @{ $modMeta->{keywords} }, "fhem-$modType-commercial"; } # add legacy POD info as Metadata - if ($item_modtype) { - $item_modtype = 'fhem-mod-' . $item_modtype; - push @{ $modMeta->{keywords} }, $item_modtype + if ($item_modsubtype) { + $item_modsubtype = "fhem-$modType-" . $item_modsubtype; + push @{ $modMeta->{keywords} }, $item_modsubtype if ( !defined( $modMeta->{keywords} ) - || !grep ( /^$item_modtype$/i, @{ $modMeta->{keywords} } ) ); + || !grep ( /^$item_modsubtype$/i, @{ $modMeta->{keywords} } ) ); + } + else { + push @{ $modMeta->{keywords} }, "fhem-$modType" + if ( !defined( $modMeta->{keywords} ) + || !grep ( /^fhem-$modType$/i, @{ $modMeta->{keywords} } ) ); } # Add some keywords about the module origin - my $modType = $modMeta->{x_file}[3] ? 'mod' : 'pkg'; - if ( GetModuleSourceOrigin( $modMeta->{x_file}[4] ) eq 'fhem' ) { + if ( GetModuleSourceOrigin($modName) eq 'fhem' ) { push @{ $modMeta->{keywords} }, 'fhem-core'; } - elsif ( GetModuleSourceOrigin( $modMeta->{x_file}[4] ) ne '' ) { + elsif ( GetModuleSourceOrigin($modName) ne '' ) { push @{ $modMeta->{keywords} }, "fhem-$modType-3rdparty"; } else { - my $modType = $modMeta->{x_file}[3] ? 'mod' : 'pkg'; push @{ $modMeta->{keywords} }, "fhem-$modType-local"; } # Add keywords to global index if ( @{ $modMeta->{keywords} } > 0 ) { foreach ( @{ $modMeta->{keywords} } ) { - if ( $modMeta->{x_file}[3] ) { + if ( $modType eq 'mod' ) { push @{ $keywords{$_}{modules} }, $modName if ( !defined( $keywords{$_}{modules} ) || !grep ( /^$modName$/i, @{ $keywords{$_}{modules} } ) ); @@ -1914,7 +2156,23 @@ sub __GenerateKeywordsFromSupportCommunity { $tag =~ s/ +/-/g; foreach ( split '/', $tag ) { - push @keywords, ( $_ =~ /^$prefix/ ? '' : $prefix ) . $_; + my $t = ( $_ =~ /^$prefix/ ? '' : $prefix ) . $_; + my $desc = + defined( $community->{subCommunity}{description} ) + ? $community->{subCommunity}{description} + : ( + defined( $community->{description} ) + ? $community->{description} + : '' + ); + + push @keywords, $t; + $keywordDescription{$t}{de} = $desc + unless ( + $desc eq '' + || ( defined( $keywordDescription{$t} ) + && defined( $keywordDescription{$t}{de} ) ) + ); } } @@ -1923,7 +2181,23 @@ sub __GenerateKeywordsFromSupportCommunity { $tag =~ s/ +/-/g; foreach ( split '/', $tag ) { - push @keywords, ( $_ =~ /^$prefix/ ? '' : $prefix ) . $_; + my $t = ( $_ =~ /^$prefix/ ? '' : $prefix ) . $_; + my $desc = + defined( $community->{subCommunity} ) + && defined( $community->{subCommunity}{description} ) + ? $community->{subCommunity}{description} + : ( + defined( $community->{description} ) ? $community->{description} + : '' + ); + + push @keywords, $t; + $keywordDescription{$t}{de} = $desc + unless ( + $desc eq '' + || ( defined( $keywordDescription{$t} ) + && defined( $keywordDescription{$t}{de} ) ) + ); } } @@ -1936,7 +2210,18 @@ sub __GenerateKeywordsFromSupportCommunity { $tag =~ s/ +/-/g; foreach ( split '/', $tag ) { - push @keywords, $_; + my $desc = + defined( $community->{description} ) + ? $community->{description} + : ''; + + push @keywords, $tag; + $keywordDescription{$tag}{de} = $desc + unless ( + $desc eq '' + || ( defined( $keywordDescription{$tag} ) + && defined( $keywordDescription{$tag}{de} ) ) + ); } } @@ -2065,7 +2350,8 @@ sub __GetMaintainerdata { # Register in global maintainer index foreach ( split '/|,', $maintainer[1] ) { - push @{ $maintainerModules{$_} }, $maintainer[0][4]; + push @{ $maintainers{$_}{modules} }, + $maintainer[0][4]; } # Generate keywords for global index @@ -2152,7 +2438,7 @@ sub __GetMaintainerdata { # Register in global maintainer index foreach ( split '/|,', $maintainer[1] ) { - push @{ $maintainerPackages{$_} }, + push @{ $maintainers{$_}{packages} }, $maintainer[0][4]; } @@ -2668,15 +2954,13 @@ sub __SetXVersion { =for :application/json;q=META.json Meta.pm { - "abstract": "FHEM component module to enable Metadata support", - "description": "n/a", + "abstract": "FHEM development package to enable Metadata support", "x_lang": { "de": { - "abstract": "FHEM Modul Komponente, um Metadaten Unterstützung zu aktivieren", - "description": "n/a" + "abstract": "FHEM Entwickler Paket, um Metadaten Unterstützung zu aktivieren" } }, - "version": "v0.3.2", + "version": "v0.4.0", "release_status": "testing", "author": [ "Julian Pawlowski "