Successivo: , Precedente: , Su: Programmi vari   [Contenuti][Indice]


11.3.7 Estrarre programmi da un file sorgente Texinfo

Sia questo capitolo che il precedente (Una libreria di funzioni awk) presentano un numero elevato di programmi awk. Se si vuole fare pratica con questi programmi, è fastidioso doverli digitare di nuovo manualmente. È per questo che abbiamo pensato a un programma in grado di estrarre parti di un file in input Texinfo e metterli in file separati.

Questo Documento è scritto in Texinfo, il programma di formattazione di documenti del progetto GNU. Un solo file sorgente Texinfo può essere usato per produrre sia la documentazione stampata, usando TeX, sia quella online. (Texinfo è esaurientemente documentato nel libro Texinfo—The GNU Documentation Format, disponibile alla Free Software Foundation, e anche online.)

Per quel che ci riguarda, è sufficiente sapere tre cose riguardo ai file di input Texinfo:

Il programma seguente, extract.awk, legge un file sorgente Texinfo e fa due cose, basandosi sui commenti speciali. Dopo aver visto il commento ‘@c system …’, esegue un comando, usando il testo del comando contenuto nella riga di controllo e passandolo alla funzione system() (vedi la sezione Funzioni di Input/Output). Dopo aver trovato il commento ‘@c file nome_file’, ogni riga successiva è spedita al file nome_file, fino a che si trova un commento ‘@c endfile’. Le regole in extract.awk sono soddisfatte sia quando incontrano ‘@c’ che quando incontrano ‘@comment’ e quindi la parte ‘omment’ è opzionale. Le righe che contengono ‘@group’ e ‘@end group’ sono semplicemente ignorate. extract.awk usa la funzione di libreria join() (vedi la sezione Trasformare un vettore in una sola stringa).

I programmi di esempio nel sorgente Texinfo online di GAWK: Programmare efficacemente in AWK (gawktexi.in) sono stati tutti inseriti tra righe ‘file’ e righe ‘endfile’. La distribuzione di gawk usa una copia di extract.awk per estrarre i programmi di esempio e per installarne molti in una particolare directory dove gawk li può trovare. Il file Texinfo ha un aspetto simile a questo:

…
Questo programma ha una regola @code{BEGIN}
che stampa un messaggio scherzoso:

@example
@c file esempi/messages.awk
BEGIN @{ print "Non v'allarmate!" @}
@c endfile
@end example

Stampa anche qualche avviso conclusivo:

@example
@c file esempi/messages.awk
END @{ print "Evitate sempre gli archeologi annoiati!" @}
@c endfile
@end example
…

Il programma extract.awk inizia con l’impostare IGNORECASE a uno, in modo che un miscuglio di lettere maiuscole e minuscole nelle direttive non faccia differenza.

La prima regola gestisce le chiamate a system(), controllando che sia stato fornito un comando (NF dev’essere almeno tre) e controllando anche che il comando termini con un codice di ritorno uguale a zero, che sta a significare che tutto è andato bene:

# extract.awk --- estrae file ed esegue programmi dal file Texinfo

BEGIN    { IGNORECASE = 1 }

/^@c(omment)?[ \t]+system/ {
    if (NF < 3) {
        e = ("extract: " FILENAME ":" FNR)
        e = (e  ": riga `system' con formato errato")
        print e > "/dev/stderr"
        next
    }
    $1 = ""
    $2 = ""
    stat = system($0)
    if (stat != 0) {
        e = ("extract: " FILENAME ":" FNR)
        e = (e ": attenzione: system ha restituito " stat)
        print e > "/dev/stderr"
    }
}

La variabile e è stata usata per far sì che la regola sia agevolemente contenuta nella videata.

La seconda regola gestisce il trasferimento di dati in un file. Verifica che nella direttiva sia stato fornito un nome-file. Se il nome del file non è quello del file corrente, il file corrente viene chiuso. Mantenere aperto il file corrente finché non si trova un nuovo nome file permette di usare la ridirezione ‘>’ per stampare i contenuti nel file, semplificando la gestione dei file aperti.

Il ciclo for esegue il lavoro. Legge le righe usando getline (vedi la sezione Richiedere input usando getline). Se si raggiunge una fine-file inattesa, viene chiamata la funzione fine_file_inattesa(). Se la riga è una riga “endfile”, il ciclo viene abbandonato. Se la riga inizia con ‘@group’ o ‘@end group’, la riga viene ignorata, e si passa a quella seguente. Allo stesso modo, eventuali commenti all’interno degli esempi vengono ignorati.

Il grosso del lavoro è nelle poche righe che seguono. Se la riga non ha simboli ‘@’, il programma la può stampare così com’è. Altrimenti, ogni ‘@’ a inizio parola dev’essere eliminato. Per rimuovere i simboli ‘@’, la riga viene divisa nei singoli elementi del vettore a, usando la funzione split() (vedi la sezione Funzioni di manipolazione di stringhe). Il simbolo ‘@’ è usato come carattere di separazione. Ogni elemento del vettore a che risulti vuoto indica due caratteri ‘@’ contigui nella riga originale. Per ogni due elementi vuoti (‘@@’ nel file originale), va inserito un solo simbolo ‘@’ nel file in output.

Una volta terminato di esaminare il vettore, viene chiamata la funzione join() specificando nella chiamata il valore di SUBSEP (vedi la sezione Vettori multidimensionali), per riunire nuovamente i pezzi in una riga sola. La riga è poi stampata nel file di output:

/^@c(omment)?[ \t]+file/ {
    if (NF != 3) {
        e = ("extract: " FILENAME ":" FNR ": riga `file' con formato errato")
        print e > "/dev/stderr"
        next
    }
    if ($3 != file_corrente) {
        if (file_corrente != "")
            lista_file[file_corrente] = 1   # memorizza per chiudere dopo
        file_corrente = $3
    }

    for (;;) {
        if ((getline riga) <= 0)
            fine_file_inattesa()
        if (riga ~ /^@c(omment)?[ \t]+endfile/)
            break
        else if (riga ~ /^@(end[ \t]+)?group/)
            continue
        else if (riga ~ /^@c(omment+)?[ \t]+/)
            continue
        if (index(riga, "@") == 0) {
            print riga > file_corrente
            continue
        }
        n = split(riga, a, "@")
        # if a[1] == "", vuol dire riga che inizia per @,
        # non salvare un @
        for (i = 2; i <= n; i++) {
            if (a[i] == "") { # era un @@
                a[i] = "@"
                if (a[i+1] == "")
                    i++
            }
        }
        print join(a, 1, n, SUBSEP) > file_corrente
    }
}

È importante notare l’uso della ridirezione ‘>’ . L’output fatto usando ‘>’ apre il file solo la prima volta; il file resta poi aperto, e ogni scrittura successiva è aggiunta in fondo al file. (vedi la sezione Ridirigere l’output di print e printf). Ciò rende agevole mischiare testo del programma e commenti esplicativi (come è stato fatto qui) nello stesso file sorgente, senza nessun problema. Il file viene chiuso solo quando viene trovato un nuovo nome di file-dati oppure alla fine del file in input.

Quando si incontra un nuovo nome-file, invece di chiudere il file, il programma memorizza il nome del file corrente in lista_file. Ciò rende possibile mischiare il codice per più di un file nel file sorgente Texinfo in input. (Precedenti versioni di questo programma chiudevano davvero il file. Ma, a causa della ridirezione ‘>’, un file le cui parti non erano tutte una di seguito all’altra finiva per contenere errori.) Una regola END effettua la chiusura di tutti i file aperti, quando l’elaborazione è stata completata:

END {
    close(file_corrente)    # chiudi l'ultimo file
    for (f in lista_file)   # chiudi tutti gli altri
        close(f)
}

Per finire, la funzione fine_file_inattesa() stampa un appropriato messaggio di errore ed esce:

function fine_file_inattesa()
{
    printf("extract: %s:%d: fine-file inattesa, o errore\n",
                     FILENAME, FNR) > "/dev/stderr"
    exit 1
}

Successivo: , Precedente: , Su: Programmi vari   [Contenuti][Indice]