DI RECENTE ACCOMAZZI...
CERCA
» Ricerca avanzata
MAILING LIST

Se vuoi iscriverti alla mailing list di Luca Accomazzi inserisci qui la tua mail:

Vuoi ricevere i messaggi immediatamente (50 invii / giorno) o in differita e in gruppo
(due invii / giorno)?

» Vuoi saperne di più?

AppleScript - parte II

AppleScript non è difficile da padroneggiare per chi conosca già almeno un linguaggio di programmazione, anche se le sue peculiarità possono disorientare il neofita — ma questo vale per qualsiasi linguaggio, sia di programmazione che umano.
Per avvicinare AppleScript, introduciamo innanzitutto l’istruzione di assegnamento. Grazie ad essa possiamo memorizzare un valore in una variabile:
set variabile to valore
oppure
copy valore to variabile
Per esempio, scrivendo set dataOdierna to “16 gennaio 1994” assegnamo alla variabile chiamata dataOdierna la stringa di caratteri indicata.
Le variabili non vanno dichiarate preventivamente, e il linguaggio è polimorfico (cioè la stessa variabile può contenere ora una stringa, ora un intero, ora un altro tipo ancora di dato).
AppleScript non possiede nessuna istruzione per fare input o output (è stato creato per controllare le applicazioni esistenti, e si suppone che il programmatore usi queste ultime per comunicare). Possiamo però usare display dialog per far apparire una finestra di dialogo che fornisce un messaggio all’utente. Formalmente, si tratta di una estensione al linguaggio (un po’ come gli XCMD di HyperCard), ma adempie bene al suo compito.
La sintassi è
display dialog “Un messaggio” ¬
default answer “Risposta predefinita” ¬
buttons {“Pulsante1”, “Pulsante2” …} ¬
default button numeroPulsanteDefault
Anche conoscendo queste sole istruzioni possiamo scrivere la nostra prima applicazione AppleScript, che mostra a video la data e l’ora corrente. Eccone il testo:

set adesso to current date
set orarioDaMostrare to adesso as string
display dialog orarioDaMostrare

Per comprendere il programma bisogna sapere che “current date” è una variabile predefinita di AppleScript che contiene data e ora corrente, e che la clausola “as string” forza la conversione di una variabile di tipo data in una variabile di tipo stringa.


Volendo usare AppleScript da solo (e non in congiunzione con applicazioni Macintosh esistenti), come faremo in questo articolo, le interazioni con l’utente sono limitate alla funzione display dialog.

Una pendola per computer

AppleScript fornisce, naturalmente, i due costrutti base della programmazione: il ciclo e la selezione (cioè il mezzo per ripetere un blocco di istruzioni più volte e il mezzo per eseguire una istruzione solo se una certa condizione è verificata.
La sintassi della selezione è semplice:
if soldiInBanca > 0 -- se…
display dialog “Tutto bene”
else -- …altrimenti…
displayDialog “Siamo in rosso!”
end if -- …fine se
Il ciclo è un po’ più complesso da realizzare, perché ve ne sono diverse varianti. La forma base è:
repeat
-- istruzione da ripetere
end repeat
Armati di questi due strumenti possiamo procedere alla scrittura di una versione più complessa del programma orologio:

set adesso to current date
repeat
if adesso ¤ (current date) then
set adesso to current date
set ora to adesso as string
display dialog ora
end if
end repeat

Questa seconda versione del programma mostra la finestra di dialogo ogni qual volta l’ora cambia. Disgraziatamente, ciò avviene una volta al secondo! Ma possiamo ottenete un programma che ci dice che ore sono una volta al minuto (o all’ora) semplicemente trascurando le cifre meno significative dell’orario. Dato che un orario viene rappresentato come “giorno mese anno hh:mm:ss”, dove h sono le ore, m i minuti, e s i secondi, ci basta imporre ad AppleScript di ignorare le ultime due lettere della frase (ovvero le ultime due lettere dell’ultima parola.
Ecco dunque la versione definitiva del programma orologio, che trasforma un calcolatore da cinque milioni in una pendola da salotto:

set adesso to current date
set oraLunga to adesso as string
set ora1 to characters 1 thru 5 of last word of oraLunga
set ora2 to ""
repeat
set adesso to current date
set oraLunga to adesso as string
set ora1 to characters 1 thru 5 of last word of oraLunga
if ora1 ¤ ora2 then
set ora2 to ora1
display dialog oraLunga
end if
end repeat

Così come mostrato, il programma mostra l’orologio ogni minuto, ma basta scrivere “characters 1 thru 4” per vedere il dialogo ogni dieci minuti, oppure “1 thru 2” per vederlo una volta ogni ora. Potremmo facilmente aggiungere un bel “bong”, o un “tic-tac”, dato che una delle estensioni ad AppleScript fornite insieme con il software di base permette di far emettere un suono a Mac...


Un progetto ambizioso

Ora che abbiamo introdotto le basi di AppleScript possiamo utilizzare il linguaggio per realizzare qualcosa di più significativo. Non è stato facile immaginare qualcosa di sufficientemente complesso per dimostrare le potenzialità del linguaggio, e tuttavia abbastanza breve da trovare spazio sulle pagine della rivista. Ma ecco l’idea.
In una ditta lavorano tre tecnici e un commesso; ciascuno di loro è dotato di un Mac. Il commesso riceve delle ordinazioni: prende un appunto per ciascuna di esse, scrivendo di cosa si tratta e assegnando una priorità. La priorità è un numero compreso tra uno (massima urgenza) e dieci (non c’è alcuna fretta).
I tecnici sbrigano le ordinazioni: quando uno di loro si libera riceve in affidamento la commessa più urgente e, a parità di urgenza, quella che aspetta da più tempo.
Svilupperemo ora tre semplici applicazioni AppleScript che risolvono questo problema. La prima (chiamata Insert) risiederà sul Mac del commesso, e servirà a introdurre una ordinazione nel sistema. La seconda (Checker) risiederà su ciascun Mac dei tecnici. La terza (Server) funge da coordinatore delle attività, e può risiedere su un Mac qualsiasi.
In altri termini, stiamo sviluppando un applicazione client-server, capace di gestire un qualsiasi numero di clienti (non è, infatti, limitata a tre tecnici), e che gira in rete: il tutto con poche righe di codice di un linguaggio di programmazione non specializzato, e senza una base di dati che lavori per noi. Non è poco.


La struttura dati

Per realizzare il nostro obiettivo, utilizzeremo una lista semplice di dati. La lista memorizzerà tutte le commesse in attesa di esecuzione, e sarà memorizzata dall’applicazione Server nella variabile listaEventi. Ogni volta che il commesso aggiunge una ordinazione, alla lista verranno aggiunte due voci: un promemoria che descrive l’ordinazione (stringa di caratteri) e un indice numerico di priorità (un intero). Quando un tecnico si dece pronto a un nuovo lavoro, Server rintraccia all’interno della lista l’ordinazione a maggior priorità, la passa al tecnico e la rimuove dall’elenco dei lavori da fare.
La listaEventi sarà memorizzata come property di Server. Una property è una variabile dalle caratteristiche sofisticate, utilizzabile in AppleScript per la programmazione orientata all’oggetto. Nel nostro codice ci limitiamo a sfruttarne una caratteristica interessante: la permanenza; i dati inserti in una variabile property sono infatti insensibili alla terminazione dell’applicazione. Se il programma Server viene chiuso, o il Mac sul quale gira viene spento per mancanza di corrente, la listaEventi non viene cancellata.


L’applicazione Server, discussa nell’articolo, deve restare permanentemente aperta per poter ricevere i messaggi dagli altri Macintosh sulla rete. Per ottenere questo effetto basta cliccare sul pulsante”Stay open” durante il salvataggi

Le applicazioni

Líapplicazione Checker
Vediamo per prima l’applicazione Checker, la più semplice delle tre. Si tratta dell’applicazione che gira sui Mac dei tecnici. Quando un tecnico esegue questo programma esso non fa altro che mettersi in connessione con il Server e chiedergli quale sia il lavoro più urgente. Ricevuto un messaggio di risposta, Checker lo mostra a video.
Per realizzare la comunicazione tra applicazioni useremo la parola chiave AppleScript “tell”. Per mettere in contatto tra loro l’applicazione AppleScript che stiamo scrivendo e un altra applicazione qualsiasi (sviluppata in AppleScript — come Server — o tradizionale — come Excel) infatti basta scrivere:
tell application “Nome applicazione”
-- elenco di ordini per l’applicazione
end tell
Oppure:
tell application “Nome” to -- un ordine
Checker manda a Server il messaggio controllaEvento. Questo messaggio scatena una procedura all’interno di Server, che vedremo tra poco. Il risultato restituito da Server viene automaticamente messo nella variabile “result” di Checker. A Checker, dunque, non resta che eseguire un display dialog result.
Il programma AppleScript “Checker” (vedere “Il codice di Checker”) va scritto con lo Script editor di AppleScript, e salvato come applicazione.

Il codice di Checker

tell application "Server" to controllaEvento()
if result = "" then
display dialog "Non ci sono compiti in lista" ¬
buttons { "Annulla" } default button 1
else
display dialog result buttons { "OK" } default button 1
end if

Questo codice, così com’è, suppone che l’applicazione Server si trovi sullo stesso Mac sul quale gira essa stessa. Questo per permettere a ogni lettore di usarla, anche se non ha a disposizione una rete di Macintosh. Per far funzionare Checker in rete basta sostituire la prima riga con:
tell application "Server" of machine “nome Mac” of zone “Nome zona AppleTalk” to controllaEvento()
Su una rete composta da una sola zona non è necessaria la clausola “of zone”.


L’applicazione Insert
Insert è l’applicazione del commesso. Deve chiedere al commesso una descrizione, un numero di priorità, e poi passarli a Server.
Insert fa uso di una variante del ciclo repeat, che è stato introdotto in precedenza:
repeat while unaCondizione
-- blocco di istruzioni
end repeat
In questa variante le istrizioni del blocco vengono ripetute se e sino a quando la condizione espressa resta vera.
Nel realizzare Insert ho voluto dimostrare una delle caratteristiche più avanzate di AppleScript: il controllo delle eccezioni. Si tratta di una possibilità che solitamente mettono a disposizione solo i linguaggi più sofisticati, come C++ o Ada.
All’interno di Insert si vede solo un uso banalizzato di questa caratteristica: l’ho utilizzata per intrappolare il caso in cui il numero della priorità venga scritto scorrettamente dal commesso. Lo stesso effetto era ottenibile in molti altri modi più tradizionali.
La sintassi da utilizzare per il controllo delle eccezioni è
try
-- blocco di codice
on error
-- codice che viene eseguito se avviene un errore nel blocco
end try
Lo spazio dedicato a questo intero articolo basterebbe appena ad illustrare un meccanismo tanto importante, eclettico e significativo come il controllo delle eccezioni: quindi, a malincuore, passiamo oltre.


Il codice di Insert

set risultato to display dialog "Promemoria?" default answer "Descrizione" ¬
buttons {"OK", "Annulla"} default button 1
if button returned of risultato = "OK" then
set suoNome to text returned of risultato
set bisognaChiederePriorita to true
repeat while bisognaChiederePriorita
try
set risultato to display dialog "Urgenza? (1...10, 1 è maggiore)" ¬
default answer "10" ¬
buttons {"OK", "Annulla"} default button 1
if button returned of risultato = "OK" then
set suaPriorita to (text returned of risultato as integer)
set bisognaChiederePriorita to false
end if
on error
display dialog "Qui mi serve un numero intero, capo!" ¬
buttons "Rifo…" default button 1
end try
end repeat
--aggiungi l'evento in lista
tell application "Server" to aggiungereEvento(suoNome, suaPriorita)
end if



L’applicazione Server
All’interno del codice di Server, l’applicazione che esegue veramente il lavoro necessario, troviamo tre blocchi: il primo, di una sola riga, dichiara listaEventi come una proprietà di Server (e quindi unità permanente di memoria), e la inizializza a lista vuota.
Il secondo blocco è il codice controllaEvento(), che viene invocato quando Checker richiede la restituzione della commessa più urgente. Al suo interno si trova un’altra variante del ciclo repeat: per ripetere un blocco di istruzioni un numero prefissato di volte si scrive:
repeat with variabile from valoreIniziale to valoreFinale by intervallo
-- blocco istruzioni
end repeat
Questo ciclo è tipicamente chiamato “ciclo for” nella maggior parte dei linguaggi di programmazione. Una variabile viene inizializzata a valoreIniziale; a ogni iterazione del ciclo viene incrementata di “intervallo” unità; il ciclo termina quando la variabile supera il valoreFinale.
Dentro controllaEvento() vediamo anche il metodo da utilizzare per accedere agli oggetti dentro listaEventi: length of listaEventi sarà il numero di oggetti contenuti dalla lista, mentre item k of listaEventi sarà il k-esimo oggetto della lista.
Il blocco più massiccio all’interno di controllaEvento() è quello che toglie l’evento appena selezionato dalla lista. Concettualmente, questa azione potrebbe venire realizzata semplicemente scrivendo:
set listaEventi to (items 1 thru (eventoScelto - 2) of listaEventi) & (items (eventoScelto + 1) thru numEventi of listaEventi)
Disgraziatamente, se il codice è quello appena visto AppleScript si blocca quando viene selezionato il primo o l’ultimo evento. Nel caso del primo evento, per esempio, la riga di codice si concretizza nella selezione degli elementi da 1 a zero e da 2 a numEventi. Se AppleScript risolvesse l’istruzione supponendo (correttamente) che l’insieme da 1 a zero è un insieme vuoto, non ci sarebbe bisogno di altro codice. Disgraziatamente, AppleScript considera “gli elementi da 1 a zero” un errore: quindi è necessario esprimere come casi speciali la selezione della prima commissione, dell’ultima commissione, e dell’unica commissione in lista.
Il terzo blocco di codice di Server è il codice usato per aggiungereEvento(). Queste poche righe di codice si limitano ad aggiungere in coda alla lista i dati passati da Insert.


Il codice di Server

property listaEventi : {} -- permanente!

on controllaEvento()
-- qui il problema è che se ho inserito il record
-- {"Esempio", 1} AppleScript mette in lista
-- due campi: "Esempio" e "1".
set eventoScelto to 0
set prioritaScelta to 11
set numEventi to length of listaEventi
-- caso speciale: la lista è vuota
if numEventi = 0 then return ""
-- controlla tutti gli eventi della lista
repeat with i from 2 to numEventi by 2
-- se ne trovi uno che ha priorità maggiore dell'attuale selezionato
if prioritaScelta > (item i of listaEventi) then
-- ... allora selezionalo
set prioritaScelta to item i of listaEventi
set eventoScelto to i
end if
end repeat
-- Prendi nota di quale evento va restituito
set risultato to item (eventoScelto - 1) of listaEventi
-- ... e poi toglilo dalla lista
if numEventi = 2 then
set listaEventi to {}
else if eventoScelto = 2 then
set listaEventi to items 3 thru numEventi of listaEventi
else if eventoScelto = numEventi then
set listaEventi to items 1 thru (numEventi - 2) of listaEventi
else
set listaEventi to (items 1 thru (eventoScelto - 2) of listaEventi) ¬
& (items (eventoScelto + 1) thru numEventi of listaEventi)
end if
-- restituisci il risultato
return risultato
end controllaEvento -- controllaEvento

to aggiungereEvento(nomeEvento, prioritaEvento)
set prioritaNumerica to prioritaEvento as integer --accertiamoci sia un numero
copy {nome:nomeEvento, priorita:prioritaNumerica} to nuovoEvento
set listaEventi to listaEventi & nuovoEvento -- aggiungi in coda
return "OK!"
end aggiungereEvento


Conclusione
AppleScript è un linguaggio semplice da apprendere ma molto evoluto e ricco di costrutti sofisticati. In questo articolo ho preferito mostrarne le potenzialità realizzando una applicazione relativamente sofisticata piuttosto che limitarmi ad elencare qualche parola chiave: in quel caso nessuno avrebbe potuto capire quale flessibilità e potenza si nasconde in AppleScript.


Questo articolo fa parte di uno dei miei percorsi. Se vuoi saperne di più su questo argomento, visita il resto del percorso cliccando qui.