J'ai le module Foo :: Bar :: Baz
, qui se trouve sur /some_path/Foo/Bar/Baz.pm
, et un script perl qui s'exécute à partir de < code> / some_path / Foo / Bar . Le script doit analyser tous les modules de ce dossier.
Comment puis-je obtenir le nom du module ( Foo :: Bar :: Baz
) par nom de fichier ( Baz.pm
)? Je ne peux pas simplement couper la fin de .pm
et aussi le chemin de lib
dans @INC n'est pas déterministe
Premièrement, je devais utiliser new_from_file et name méthodes de Module :: Info , mais malheureusement
Le module chargé avec new_from_file () n'aura pas ces informations
On dirait que j'ai besoin d'un module qui effectue une analyse de code statique. Quelqu'un connaît son nom?
3 Réponses :
J'ai utilisé PPI pour analyser (et réécrire) le code Perl. Si je me souviens bien, un peu de courbe d'apprentissage, mais cela a plutôt bien fonctionné. Dans votre cas, il vous suffit de trouver l'instruction package <...>
à partir du fichier. Tout ce qui est plus complexe que cela, comme quelqu'un utilisant autopackage , et PPI ne serait probablement pas en mesure de vous aider , non plus.
Il n'est pas nécessaire que les noms de paquet correspondent au nom de fichier depuis lequel il a été chargé, ou pour le premier paquet à . Mais pour tout module correctement écrit que vous pouvez utiliser
normalement et même certains modules bizarres, Module :: Metadata peut trouver ces informations. En règle générale, vous devez travailler avec des noms de fichiers absolus quel que soit votre répertoire actuel, ce qui peut vous aider. ( File :: Spec et Path :: Tiny peut vous aider)
use strict; use warnings; use Module::Metadata; my $metadata = Module::Metadata->new_from_file('/path/to/File.pm'); my $package = $metadata->name;
En gros, vous ne pouvez pas, sans analyser vos fichiers. La bonne façon est d'utiliser un module perl standard comme https://perldoc.pl/Module::Metadata a>, mais si vous voulez le faire vous-même, avec l'extrait suivant, vous pouvez extraire tous les espaces de noms de chaque fichier (oui .. un fichier n'est qu'un conteneur, vous pouvez donc avoir plus de paquets à l'intérieur). et sale façon:
{ 'lib/Bar.pm' => [ 'Apple::Steve' ], 'lib/Foo.pm' => [ 'Banana::Joe', 'Papaya::Frank' ] };
En supposant que nous ayons deux fichiers:
1) lib / Foo.pm
(avec 2 packages à l'intérieur)
package Apple::Steve 0.013 ; ...code... 1 ;
2) lib/Bar.pm
package Banana::Joe 0.003 ; ...code... package Papaya::Frank 0.001 ; ...code... 1 ;
$ LES PACKAGES
seront:
my $PACKAGES ; my @files = ('lib/Foo.pm','lib/Bar.pm') ; my $row ; map { open PKGFILE , '<', $_ or die "Got error opening file $_: $!" ; while ( $row = <MODULE>){ if ( $row =~ /\s*package\s+([\S]+)(.*?)[;{]/ ){ # rx modified on Grinnz review push @{$PACKAGES->{ $_ }} , $1 ; # last ; # <== uncomment if you are sure each file # contains only one package } ; } close PKGFILE ; } @files ;
[0-9.] *
n'est pas suffisant pour analyser le composant VERSION d'une instruction de package, et les instructions de package peuvent accepter des BLOCS contenant le contenu du package depuis Perl 5.14. Je suggère fortement de ne pas essayer de réinventer cela.
@Grinnz Vous avez raison, j'ai corrigé le regex ... c'est toujours un regex fragile qui ne tient pas compte des répétitions dans le code perl d'une ligne ni dans d'autres cas exotiques. C'est juste pour l'école, même si cela peut traiter les 99,99% des cas. BTW Je pense qu'il est plus éducatif d'essayer de faire les choses par eux-mêmes, au moins pour la culture personnelle, avant d'utiliser les cours des autres. Sinon, la programmation en perl deviendra comme la programmation en Java, où la seule chose à savoir est le nom de la classe à instancier sans savoir comment cela fonctionne sous le capot. Mais ce n'est que mon opinion, je me trompe probablement.
Je pense qu'il est plus efficace de regarder le code source de ces modules suggérés pour votre problème, que d'essayer de réinventer tous les problèmes qu'il a déjà résolus.
Avez-vous une chance de fournir une explication plus détaillée sur ce que vous essayez de faire ou attendez comme résultat? Je suis presque sûr que mon Devel :: Examine :: Subs peut faire quoi vous avez besoin, mais cela fait un moment, et j'aurais besoin de plus amples éclaircissements pour les tests (j'utilise PPI en arrière-plan).
Vous supposez qu'il existe un nom de module (unique, unique). Ce n'est pas forcément le cas. Que voulez-vous faire s'il y a 0 ou 2+ noms?
Modifiez votre script pour qu'il prenne deux entrées: le répertoire de base (
/ some_path
) et un nom de package (Foo :: Bar :: Baz
, ou peut-être mêmeFoo :: Barre :: *
)Merci à tous pour les réponses, j'ai finalement utilisé Module :: Metadata pour mon projet .