Subversion Repositories doc-tools

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#!/usr/bin/perl

#  
#  File: php_autodoc_fornd.pl
#  Crée une "coquille documentaire" pour Natural Docs dans les fichiers PHP.
#
#  Bien entendu, cela doit être ensuite complété à la main
#
#  Version : 0.5.1
#
#  Language: Perl
#
#  Svn info: Résumé
#  $Id: php_autodoc_fornd.pl 3 2007-05-30 00:48:52Z axl $
#
#  Svn info: Révision
#  $Revision: 3 $
#
#  Svn info: Auteur de la révision
#  $Author: axl $
#

#
#  variable: $php_name
#  valeur : PHP
#
#  variable: $js_name
#  valeur : javascript
#
#  variable: $perl_name
#  valeur : Perl
#
$php_name='PHP';
$js_name='javascript';
$perl_name='Perl';

#use strict;

# Pour la gestion des options
use Getopt::Std;
# Pour la copie de fichiers
use File::Copy;

#  
#  Function: print_syntaxe
#
#  Affiche la syntaxe d'utilisation
#  
#  Retourne:
#
#  rien
#
 
sub print_syntaxe {
  # Chemin complet du script Perl
  my $NomScript = $0;
  # Nom du script seulement
  $NomScript =~ s!.*(/|\\)!!;
  print <<_FINSY_;
 
Syntaxe :
  $NomScript -i nom_fichier [-option1 [parametres]] [-option2 ...]
  $NomScript -h
   
Description des options :
  -i nom_fichier
     nom_fichier nom du (des) fichier(s)) d\'input (accepte les jokers)
  -x fichiers_exclus
     fichiers_exclus est un masque de fichiers exclus du traitement
     note : les fichiers .bak sont toujours exclus (option -x inutile pour eux)
  -d {n} n niveau de déboguage (1 seulement pour l\'instant)
  -e (except) est suivi de la liste des fonctions exclues du traitement
  -o (only) est suivi de la liste des seules fonctions incluses
     dans le traitement
     -e et -o sont incompatibles ; -o est prioritaire
     les mots de la liste sont separes par des virgules, points-virgules
     ou des espaces (dès que la liste contient des espaces, elle doit être
     entre doubles quotes).
     Les fonctions existantes actuellement sont :
     - constant
     - en_tete
     - function (tous langages confondus)
     - js_func  (fonctions javascript)
     - include
  -h afficher cette aide
     
Exemples :
  $NomScript -i *.php -x *.old  
  $NomScript -i *.php -x *_ND.php  
  $NomScript -i *.php -e include,js_func,constant  
  $NomScript -i *.php -o "en_tete function"
_FINSY_
  exit;
  }

#  
#  Function: output_en_tete
#
#  Ajoute les généralités concernant le fichier
#  
#  Parameters:
#  $O : Handle du fichier de sortie
#
#  $entree : Nom du fichier en entrée
#
#  Retourne:
#
#  rien
#  
 
sub output_en_tete {
  # Si désactivé retour
    if ($feature{'
en_tete'}==0) {
        return;
        }

  # Recup des paramètres
  my ($O, $entree) = @_;
 
  # Séparer répertoire et nom de fichier
  if ($entree=~m!^(.*)(\\|/)(.*)$!) {
    $repertoire=$1;
    # Si répertoire est '
.' alors on vide pour l'affichage
    #if ($repertoire eq '.') {
    #  $repertoire='';
    #  }
    $fichier=$3;
    }
  else {
    $repertoire='.';
    $fichier=$entree;
    }
 
  # En tête de fichier pour Natural Docs
  if ($fichier=~/\.php3?$/) {
    $langage = "\n  language: $php_name\n";  
    }
  elsif ($fichier=~/\.p(l|m)$/) {
    $langage = "\n  language: $perl_name\n";
    }  
  else {
    $langage = "\n";
    }
  $EnTete = <<_FIN_;
<?
/*
  file: $fichier
  Rôle du script...
 
  Repertoire: $repertoire
    $repertoire/
$langage
  svn info:  Résumé
    \$Id\$

  Svn info: Révision
  \$Revision\$

  Svn info: Auteur de la révision
  \$Author\$
*/
_FIN_
 
  $EnTete.="?>";
  print $O $EnTete;
  }
   
#  
#  Function: output_parameters
#
#  Ajoute les paramètres de la fonction (un par
#  ligne, séparés par une ligne vide)
#  
#  Retourne:
#
#  rien
#  
 
sub output_parameters {
  my ($param) = @_;
  print O "\n  Parameters:\n";
  # Sort les paramètres un par un pour commentaires manuels futurs
  while ($param=~s/^(\$[a-zA-Z_0-9]+)( *, *(.*))?$/$3/) {
    print O "  $1 :\n\n";
    }
  }
 
#  
#  Function: output_function
#
#  Ajoute le commentaire de fonction
#  
#  Retourne:
#
#  rien
#  
 
sub output_function {
    # Si désactivé retour quel que soit le langage en cours
    if ($feature{'function'}==0) {
        return;
        }
    # Si désactivé pour javascript et javascript en cours, retour
    if ( ($language_context eq $js_name) and ($feature{'js_func'}==0) ) {
        return;
        }

    my ($func_name,$param_liste)=@_;
    print O "/*\n  Function: $func_name\n";
    print O "\n  Language:\n$language_context\n";
    output_parameters($param_liste);
    print O "\n*/\n";
    }
 
#  
#  Function: output_constant
#
#  Ajoute la constante dont le nom et la valeur
#  ont été passés en paramètres
#  
#  Parameters:
#  $ConstName : Nom de la constante
#
#  $ConstValue : Valeur de la constante
#
#  Retourne:
#
#  rien
#  
 
sub output_constant {
  # Si désactivé retour
    if ($feature{'constant'}==0) {
        return;
        }

  my ($ConstName,$ConstValue) = @_;
    print O "/*\n  Constant: $ConstName\n\n  $ConstValue\n";
    print O "\n  Signification: ...\n*/\n";
  }

#  
#  Function: output_include
#
#  Ajoute la déclaration d'inclusion passée en paramètre
#  
#  Parameters:
#  $IncludeWord : commande d'inclusion
#
#  $IncludeFile : Fichier inclus (peut être une formule)
#
#  Retourne:
#
#  rien
#  
 
sub output_include {
  # Si désactivé retour
    if ($feature{'include'}==0) {
        return;
        }

  my ($IncludeWord,$IncludeFile) = @_;
    print O "/*\n  Inclusion: $IncludeWord\n\n";
    print O "  $IncludeFile\n*/\n";
  }
 
#  
#  Function: try_language_context
#
#  Essai de repérer le langage de programmation courant.
#  Suppose l'absence de changement au sein d'une ligne ce qui est une
#  simplification abusive, on le sait.
#
#  Toutes les regexp de ce script sont de vraies passoires laissant
#  délibérément de côté tout un tas de cas (par manque de temps par
#  rapport aux enjeux)
#
#  Modifie:
#
#  Les variables globales $language_context et @language_context_stack
#  
#  Retourne:
#
#  rien
#  
 
sub try_language_context {

    # Ignorer les courtes inclusions de PHP au sein du HTML
    # ou du javascript
    if (/^ *<\?(php)?.+\?>/) {
        return;
        }
    # Début PHP ?
    if (/^ *<\?(php)?/i) {
        push(@language_context_stack, $language_context);    
        $language_context=$php_name;
        if ($debug_level eq 1) {
            print "$line_number:dp:#$_ current : $language_context\n";
            print @language_context_stack; print "\n";
            }
        }
    # javascript sur une seule ligne -> on ne change rien  
    if (m:^ *<(!--)?SCRIPT +LANGUAGE=.?javascript.?>.*</script(--)?>:i) {    
        # On ne fait rien en l'état actuel du développement
        }
    # Sinon début javascript    
    else {
        if (/^ *<(!--)?SCRIPT +LANGUAGE=.?javascript.?>/i) {
            push(@language_context_stack, $language_context);    
            $language_context=$js_name;
            if ($debug_level eq 1) {
                print "$line_number:dj:#$_ current : $language_context\n";
                print @language_context_stack; print "\n";
                }
            return;
            }
        }
    # Fin javascript ?  
    if (m:</script(--)?>:i) {    
        $language_context=pop(@language_context_stack);
        if ($debug_level eq 1) {
            print "$line_number:fj:#$_ current : $language_context\n";
            print @language_context_stack; print "\n";
            }
        }
    # Fin PHP ?  (Hum regexp bidon on peut avoir un "<" qui ne soit pas
    # un "<?")
    if ((/\?>.*$/) and ($language_context eq $php_name)) {
        my $ligne=$_;
        while ($ligne=~/\?>.*<\?/) {
            $ligne=~s/^.*\?>.*<\?(.*)$/$1/;
            }
        if ($ligne=~/\?>.*$/) {
            $language_context=pop(@language_context_stack);
            if ($debug_level eq 1) {
                print "$line_number:fp:#$_ current : $language_context\n";
                print @language_context_stack; print "\n";
                }
            }  
        }
    }
 
#  
#  Function: try_to_skip_svnid
#
#  Efface le mot-clé Id de subversion s'il existe déjà dans le fichier
#  
#  Retourne:
#
#  Un booléen
#
#  - true : il n'y plus rien d'autre sur la ligne qu'un signe de début
#    de commentaire ligne et des espaces
#  - il reste des éléments d'information sur la ligne
#  
 
sub try_to_skip_svnid {
  if ($_=~/\$Id.*\$/) {
    $_=~s!^(.*?)\$Id[^\$]*\$(.*)$!$1$2!;
    if ($_=~/^(\/\/|#) *$/) {return 1;}
    }
  return 0;
  }
 
#  
#  Function: try_function_begin
#
#  Teste si une fonction débute et si oui traite en conséquence
#  
#  Parameters:
#
#  aucun
#
#  Retourne:
#
#  vrai (1) si début de fonction trouvé et faux (0) sinon
#  
 
sub try_function_begin {
    if ($_=~/^ *function *([a-zA-Z_0-9]+)\(([^)]*)(.*)$/i) {
        output_function($1,$2);
        print O $_;
        return 1;
        }
    else {
        return 0;
        }
    }
 
#  
#  Function: try_constant_define
#
#  Teste si une définition de constante est sur la ligne.
#  Si oui, traite en conséquence.
#  
#  Parameters:
#
#  aucun
#
#  Retourne:
#
#  vrai (1) si définition de constante trouvée et faux (0) sinon
#  
 
sub try_constant_define {
    # Attention aux références arrières \1 et \3 qu permettent de s'assurer
    # que la paire de quote (simple ou double) est cohérente
    if ($_=~/^ *define *\( *(['"])([a-zA-Z_0-9]+)\1 *, *(["']?)(.*?)\3 *\)/) {
        output_constant($2,$4);
        print O $_;
        return 1;
        }  
    else {
        return 0;
        }
    }
 
#  
#  Function: try_include_def
#
#  Teste si une inclusion de fichier est sur la ligne.
#  Si oui, traite en conséquence.
#  
#  Parameters:
#
#  aucun
#
#  Retourne:
#
#  vrai (1) si définition de constante trouvée et faux (0) sinon
#  
 
sub try_include_def {
    # Trouver le mot-clé
    my $regexp='^ *(include|require)(_once)? *\\(';

    # Trouver la succession d'éléments constants et littéraux
# (pas traité éléments variables : à faire)
    $regexp.="((($TYPE2_FILE_PATTERN|$LAXIST_CONST_PATTERN)\\.?)+)";
   
    # Fermer la regexp par la reconnaissance de la fin de parenthèse
    $regexp.=' *\\)';
   
    if ($_=~/$regexp/) {
        output_include($1.$2,$3);
        print O $_;
        return 1;
        }  
    else {
        return 0;
        }
    }
 
#  
#  Function: process_one_file
#
#  Traite un fichier en entrée en lui ajoutant les commentaires
#  
#  Parameters:
#  $rep : chemin de travail (sans le slash final)
#
#  $entree : Nom du fichier
#
#  Retourne:
#
#  rien
#  
 
sub process_one_file {
  $line_number=0;
  # Récupération chemin de tavail et nom de fichier
  my ($rep,$entree) = @_;
  # Initialisation de variable
  my $langage = "";
 
  # Calcul nom du fichier de sortie selon qu'il ait une extension ou pas
  if ($entree=~m/^(.*)\.(.*)$/) {
    $sortie=$rep."/$1_ND.$2";
    }
  else {
    $sortie=$rep."/$1_ND";
    }
   
  # Calcul du chemin du fichier d'entrée
  $inputFile="$rep/$entree";  
 
  # Calcul du fichier de backup
  my $backupFile="ND_backup_dir/$inputFile";
  $backupFile.='.bak';
   
  # Affichage écran du travail en cours
  print "Traitement de $entree vers $sortie\n";
 
  # Faire copie de sauvegarde
  copy($inputFile,$backupFile);

  # Ouvrir les fichiers d'entrée et de sortie
  open F,"$inputFile";
  open O,">$sortie";
 
  # Eliminer l'éventuel "./" du début de chemin pour les affichages
  $inputFile=~s/^(.\/)?(.*)/$2/;
 
  # Ajouter l'en-tête fichier pour Natural Docs
  output_en_tete(O, $inputFile);

  # Traitement du fichier proprement dit  
  while (<F>) {
    $line_number+=1;
    #print "$_\n";
    # Mettre à jour le contexte de langage
    try_language_context();
    # Enlever le Id subversion si présent (car ajouté dans en-tête)
    if (try_to_skip_svnid()) {
      next;
      }
    # Trouver, traiter les fonctions  
    if (try_function_begin()) {
      next;
      }    
    # Trouver, traiter les définition de constantes  
    if (try_constant_define()) {
      next;
      }    
    # Trouver, traiter les inclusions  
    if (try_include_def()) {
      next;
      }    
    # Ecrit la ligne courante dans le fichier de sortie
    print O $_;      
    }    
  close F;
  close O;
  }
 
#  
#  Function: process_many_files
#
#  Traite de multiples fichiers en entrée en leur ajoutant les commentaires
#  
#  Parameters:
#  $masque : répertoire de travail et masque des fichiers à traiter
#
#  $x_masque : masque de fichiers à exclure
#  (les fichier .bak sont toujours exclus quoiqu'il arrive)
#
#  Retourne:
#
#  rien
#  
 
sub process_many_files {
  # Récupérer les masques de fichiers à inclure et à exclure
  my ($masque,$x_masque) = @_;

  # Déterminer le répertoire de travail
  my $repertoire = $masque;
  # Si ni / ni \ alors c'est le répertoire courant
  if ($repertoire!~s!(.*)(/|\\)(.*)!$1!) {
    $repertoire=".";
    }
  else {
    # sinon récupérer juste le masque de fichiers
    $masque=~s!(.*)(/|\\)(.*)!$3!;
    }  
  # print "\nRepertoire : $repertoire\n"; # debug
 
  # Transformer les masques de nom de fichiers en regexp
  # Protéger les "."
  $masque=~s/\./\\./g;
  $x_masque=~s/\./\\./g;
  # Ajouter un "." devant les quantificateurs "*" et "?"
  $masque=~s/\*/.*/g;
  $masque=~s/\?/.?/g;
  $x_masque=~s/\*/.*/g;
  $x_masque=~s/\?/.?/g;
 
  my $enregistrement;
  my $nom_chemin;
  local *DH;

  unless (opendir(DH, $repertoire)) {
   return;
  }
 
  while (defined ($enregistrement = readdir(DH))) {
    next if($enregistrement eq "." or $enregistrement eq "..");
    # Passer les .bak
    next if($enregistrement=~/\.bak$/);
    # Passer les éléments exclus
    next if($enregistrement=~/^$x_masque$/);
   
    $nom_chemin = $repertoire."/".$enregistrement;
    if( -d $nom_chemin) {
      # print "\n$nom_chemin est un repertoire -> pas de traitement recursif\n";
      }
    else {
      if ($enregistrement=~/^$masque$/) {
        #print "\n$nom_chemin -> en cours de traitement";
        process_one_file($repertoire,$enregistrement);      
        }
      }
    # Plus tard pour le traitement récusrsif  
    # push(@tous, $HTML_enregistrement);
    # rechercher($nom_chemin) if(-d $nom_chemin);
    }
  closedir(DH);
  }  

#
#  Main part of script
#

# Récupérer les options de la ligne de commande
getopts( "i:x:e:o:d:s:h", \%opts) or print_syntaxe();
print_syntaxe() if defined($opt{h});

# Initialisation variables
$line_number=0;
$language_context='unknown';
@language_context_stack = ('stackbase_');
$TYPE2_FILE_PATTERN="('[A-Za-z0-9_.\-]+'|\"[A-Za-z0-9_.\-]+\")";
$CONST_PATTERN='[A-Z0-9_]+';
$LAXIST_CONST_PATTERN='[a-zA-Z0-9_]+';

# traiter les options de la ligne de commande

# récupérer le(s) nom(s) de(s) fichier(s) à traiter et de ceux à exclure
my $inputfile = $opts{i};
my $exclude_files = $opts{x};

# récupérer les options de travail
my $except_features = $opts{e};
my $only_features = $opts{o};
$debug_level = $opts{d};

# établir la liste des fonctionnalités actives
%feature = (
             en_tete   => 1,
             function  => 1,
             js_func => 1,
             constant => 1,
             include => 1,
             exec => 0
);

# traiter l'option -e (liste de fonctionnalités exclues du traitement)
my @exception_liste = split(/ *[ ,;] */,$except_features);
foreach my $val (@exception_liste) {
    $feature{$val}=0;
    }
   
# traiter l'option -o (traiter seulement les fonctionnalités listées)
if ($only_features ne "") {
    # désactiver toutes les fontionnalités
    foreach my $key (keys %feature) {
        $feature{$key}=0;
        }
    # réactiver uniquement cells de la liste fournie    
    my @restricted_liste = split(/ *[ ,;] */,$only_features);
    foreach my $val (@restricted_liste) {
        $feature{$val}=1;
        }
    }
   
# Si nom de fichier vide afficher syntaxe
if ($inputfile eq "") {
  print_syntaxe();
  }
                                         
# Tester le paramètre input (-i)
if ($inputfile=~/\*|\?/) {
  process_many_files($inputfile,$exclude_files);
  }
else {
  if (-e $inputfile) {
    # Attention BUG en perspective - PROVISOIRE (pas le temps)
    # Il faut d'abord séparer chemin du nom de fichier
    process_one_file(".",$inputfile);
    }
  else {
    print "\n$inputfile n'existe pas !\n";
    print_syntaxe();
    }      
  }

if ($debug_level eq 1) {
    print @language_context_stack;
    }

# Fin du script