Spinner är den WWW-server som Lysator använder. Det är också en WWW-server som är synnerligen utbyggbar.
Servern är mestadels skriven i µLPC, se artikeln om µLPC i denna tidning (hoppas jag), eller de WWW sidor som man kan hitta vid http://www.signum.se/~hubbe/µLPC.html.
Spinner i sig kan inte mycket, det enda det centrala programmet kan är modulhantering, det vet ingenting om filer, HTTP eller andra udda saker. Allt sådant hanteras av moduler.
När jag beskriver modulskrivande kommer jag att utgå från att du har grundläggande kunskaper i µLPC, så om du inte har det är det nog lämpligt att läsa lite om µLPC innan du skriver din modul.
De tre vanligaste modultyperna är helt klart file extension moduler, location moduler och parser moduler.
Location:
En Location modul är en modultyp som hanterar allt under ett visst directory i det virtuella filsystemet, t.ex. /cgi-bin/. Det är också den modultyp som (oftast) hittar de filer som sedan skickas vidare till extension moduler, mer om det senare.
Ett exempel på en location module är schemaservern (http://www.lysator.liu.se/schema/).
File extension:
En file_extension modul är en modul som hanterar en eller flera 'extensions', t.ex. cgi, efter det att en location modul har lokaliserat filen.
Ett exempel på en dylik modul är Main SGML parsern, som per default parsar alla filer som slutar på '.html', med hjälp av 'parser' modulerna.
Parser:
En parser modul är en modul som defenierar en eller flera nya tagar till SPML, som sedan hanteras av en modul av typen main_parser. Det kan bara finnas en main parser i varje virtuell server i spinner, men det kan finnas hur många parser moduler som helst.
Sedan finns det en hög med modultyper som inte är lika vanliga:
Auth:
En modul som hanterar authentifiering av användare, och dessutom håller
en användardatabas till t.ex. användarfilsystemsmodulen (som är en
location modul).
Directory:
Den här modultypen genererar fillistningar, om man inte har en sådan
så kan inte spinner automatgenerera fillistningar för directories, och
saker som index.html kommer inte att fungera. Det kan bara finnas en
sådan i varje virtuell server.
Extension:
En typ av extension modul som anropas _innan_ location moduler
anropas, och därför inte har någon filpekare att leka med. Nyttan kan
diskuteras. (se det fina flödesschemat nedan..)
First:
En modul som, precis som namnet antyder, anropas innan alla andra
modultyper, förutom moduler av typen 'First'.
Last:
En typ av moduler som anropas efter alla andra moduler, om ingen av
de andra typerna hittade något.
Main parser:
En modul som hanterar parsning. Det kan bara finnas en sådan i varje virtuell server. Den här modultypen får alla moduler av typen parser skickade till sig, men inget annat. För att den ska få chansen att utföra någon parsning måste den även vara en extension eller location, eller möjligtvis extension utan fil modul.
Types:
Den här modultypen hanterar helt enkelt extension -> content-type
mappning i de fall där de moduler som har körts innan inte har sagt
vilken content-type filen har.
URL:
Den här typen av moduler kan användas för att implementera 'symboliska
länkar' och liknande otyg, den får url-en och kan returnera en ny, som
sedan skickas igenom hela härligheten igen, vilket även gör att den
här modultypen lämpar sig utmärkt till att implementera infinit
rekursion.
Varje modul kan ha en eller flera olika modultyper (i teorin kan man faktiskt ha moduler helt utan typ, men det blir lite meningslöst, eftersom en sådan modul aldrig kommer att anropas av spinner.)
I bilden är 'mapping' 'object' och '-1' returtypen (och i fallet -1 även värdet) från respektive modul.
Mitt första tips är att man konfigurerar upp sin egen testserver.
Om man vill använda samma filer som lysators huvudserver använder, förutom konfigurationsfilerna, skriver man:
(cd /usr/www/spinner/server; ./start --config-dir=$HOME/.spinner_configurations)
Detta kommer att starta din aldeles egna spinner, sedan är det bara att koppla upp sig till dess konfigurationsinterface och konfigurera den.
Mer om det finns att läsa på URLen http://spinner.infovav.se/.
Lämplig variabel att ändra på är Global Variables -> Module directory, som anger var den ska leta efter moduler, Detta är en kommaseparerad lista av directoryn.
Ett lämpligt värde kan ju vara modules/, localmodules/, /users/du/spinner_modules/ eller liknande.
Nu kan du börja skriva din egen modul, nedan följer en sorts steg-för-steg beskrivning av hur man gör det. Jag utgår som sagt från att du har grundläggande kunskaper i µLPC programmering.
Vi börjar med ett modulskelett, de funktioner och ärvningar som alla moduler måste innehålla.
#include <module.h> inherit "module"; inherit "spinnerlib";
array register_module() { return ({ MODULTYP, "modulnamn", "moduldokumentation", 0, 0 eller 1, }); }
Först inkluderas filen 'module.h'. Den innehåller en massa defines för konstanter, t.ex. alla modultyper. Den innehåller även en del makron som kan användas när man vill ha värdet på en viss variabel, mer om det senare.
Sedan ärvs filen module. Den defenierar en massa funktioner av typen defvar(), set() och query(), samt tillhandahåller skänskvärden till många olika statusfunktiner som man kan implementera i sin modul om man vill, men som inte behövs i de allra flesta fall.
Filen spinnerlib som sedan ärvs inkluderar en hel massa bekvämlighetsfunktioner, och är alltså inte helt nödvändig, men det är mycket bra att ha med den. Du kan ju titta på avsnittet om 'Att returnera något' för att se varför.
Sedan kommer någonting som är intressantare, funktionen register_module.
Denna (något ointuitiva) funktion ska returnera en array som defenierar modulens funktion, samt vad den heter, och om den kan ha mer än en samtidig instans aktiv i varje server.
Det första elementet i arrayen är modultypen. Detta är en bitwise-or (|) av modultyper. Vanligast är att varje modul bara har en typ, men t.ex. htmlparse.lpc i normaldistributionen av Spinner är en MODULE_MAIN_PARSER, en MODULE_PARSER och en MODULE_FILE_EXTENSION.
Det andra elementet är modulens namn, det tredje en (hyffsat) kortfattad beskrivning av vad modulen gör, det fjärde elementet är reserverat för framtida användning, och det femte elementet är '0' om det kan finnas mer än en kopia av modulen samtidigt i varje virtuell server (som filsystemsmodulen), eller 1 om det bara kan finnas en kopia i varje virtuell server (som 'tablify' modulen).
Eftersom vi i detta exempel vill skriva en modul som adderar ett par 'tagar' till SPML, väljer vi nedanstående utseende på register_module funktionen:
array register_module() { return ({ MODULE_PARSER, "Gazonk", "The gazonk module. This module adds a container and a non-container: " "<foo></foo> and <bar>", 0, 1, }); }
Nu kan modulen registrera sig, men det räcker ju inte riktigt, hur får man den att verkligen hantera <foo> </foo> och <bar>?
Nu kommer vi till modultypsspecifika callbacksfunktioner (kom på ett bättre namn själv!).
Dessa är:
void create();
Anropas av µLPC automatisk när objektet (instansen av din modul) skapas. Här defenierar man lämpligtvis sina modullokala Spinner variabler med hjälp av 'defvar'.
void start();
Anropas av spinner precis innan modulen ska vara redo att ta emot requests (requests hanteras med de modultypsspecifika callbackfunktionerna).
string status();
Anropas av konfigurationsinterfacet i tid och otid för att få status om modulen. Ska returnera en sträng, beståendes av HTML som tål att stoppas i en <dd> i en <dl> lista.
string info();
Om man defenierar den här funktionen används resultatet istället för element tre i det som register_module() returnerar för att beskriva modulen i konfigurationsinterfacet.
string|void check_variable(string variable, mixed will_be_set_to);
Om man behöver kontrollera värdet hos variabler innan de sätts defenitivt så gör man lämpligtvis det i den här funktionen. Oftast behöver man ju dock inte kontrollera variablers giltighet, eftersom alla värden som man kan sätta dem till är giltiga. Mer information om spinner variabler i allmänhet kommer senare.
string query_name();
Ska returnera namnet på modulen, används istället för element två i arrayen som register_module() returnerar för att beskriva modulen i konfigurationsinterfacet. Kan vara mycket användbart om man kan ha mer än en kopia av en modul.
Användaren kan dock själv sätta vilket namn som helst på modulen i konfigurationsinterfacet.
array auth(array from);
array userinfo(string username);
array userlist();
array user_from_uid(int uid);
Se userdb.lpc modulen. Oftast behöver man inte implementera en egen användardatabas och authentifieringsmodul, och det hör inte till grunderna i spinnerprogrammering.
mapping parse_directory(object request_id);
Givet en intern url (i request_id->not_query) ska den här funktionen returnera en directorylistning i HTML.
array (string) query_extensions();
array (string) query_file_extensions();
Returnera en array med extensions som modulen tänker hantera. Lämpligtvis implementerar enligt följande:
array (string) query_extensions() { return query("extensions"); }(förutsatt att variabeln extensions är defenierad, som en TYPE_STRING_LIST, se avsnittet om variabler senare).
mapping handle_extension(string extension, object request_id);
mapping handle_file_extension(object file, string ext, object request_id);
Returnera antingen ett resultat eller 0 (noll). Anropas av Spinner när modulen ska hantera en extension, i fallet handle_extension innan en location modul har hittat en fil som passar (används knappast alls, den enda modulen som finns som använder det är explicit timestamp modulen, som returnerar last modification date (mtime) på en fil, om man lägger till .timestamp till filnamnet.)
Vanligtvis gör man file_extension moduler, då är det det senare fallet som gäller. 'file' är en klon av /precompiled/file, se µLPC dokumentationen.
Kortfattat kan man nämna att 'file' har metoden read(), kan vara lämpligt i det här fallet.
mapping first_try(object request_id);
Används bland annat av logger.lpc, den där modulen som loggar requests i användarnas egna directoryn på Lysator. Om man returnerar en response mapping (mer om den senare... (ja, det börjar bli tjatigt nu..)) så kommer det att tas som svaret.
Den här funktionen anropas aldrig vid interna requests, dvs, vid <insert> och liknande påhitt.
mapping last_resort(object request_id);
Används av relay modulen, om man sätter priority till last.
Anropas bara om ingen annan modul har lyckats hitta ett matchande dokument, och aldrig vid interna requests.
string query_location();
Returnera vilken position i det virtuella filsystemet modulen ska få. Även här använder man lämpligen värdet av en variabel, eftersom användaren då själv kan välja var modulen ska hamna.
object|mapping find_file(string file_name, object request_id);
Returnera ett öppet filobjekt (see µLPC dokumentation om /precompiled/file) om det finns en fil som matchar file_name som den här modulen hanterar, eller en response mapping (lämpligen genererad med http_* funktionerana) om man inte vill att extension moduler ska användas igenom. Mappingen kan vara mycket praktisk om man vill returnera att använderen ska skriva in ett lösenord, eller en redirect till en ny fil.
En bra funktion att använda i den här funktionen kan vara open(filename, mode), som returnerar just ett sådant öppet filobjekt.
Observera att om modulen har location (query_location returnerar) "/foo/", och filen "/foo/bar/" efterfrågas, kommer location modulen att få "bar/" som första argument.
request_id finns dokumenterad i en annan artikel i denna tidning, den om skript i spinner, liksom response mapping.
array find_dir(string dir_name, object request_id);
Returnera en array med filnamn eller noll. dir_name som file_name hos find_file.
Det finns en default find_dir i module.lpc som returnerar 0. Så om man inte vill returnera några directorylistningar kan man bara låta bli att defeniera den här funktionen.
array stat_file(string file_name, object request_id);
file_name som hos find_file.
Returnera resultatet av en file_stat (se µLPC dokumentationen) eller noll.
Om man vill kan man skippa även den här funktionen, directorymodulen och en del andra moduler kan dock kräva att den finns för att din modul ska fungera precis som ett "vanligt" filsystem.
string real_file(string file_name, object request_id);
Returnera vad filen file_name egentligen heter (t.ex om modulen sune hittar sina filer i /usr/www/foo/, och filen bar efterfrågas i den, så ska den returnera /usr/www/foo/bar).
Den här metoden är inte alls nödvändig, det kan ju t.ex. hända att dina filer egentligen inte existerar på disken, men den snabbar upp saker som cgimoduler och µLPC skript.
void add_parse_module(object module);
void remove_parse_module(object module);
Registrera och avregistrera en parse modul i main_parse modulen. Används bara av main parser modulen, du vill nog inte skriva en sådan, iallafall inte som första modul.
Se htmlparse.lpc för mer info..
mapping (string:function(string,mapping(string:string),object,object,mapping(string:string):string)) query_tag_callers();
mapping (string:function(string,mapping(string:string),string,object,object,mapping(string:string):string)) query_container_callers();
Anropas från main_parsern för att registrera en parse modul.
Returvärdet är en mapping enligt: ([ "tag":funktion, ... ]), eller en tom mapping ([]).
Mer om de här funktionerna i exemplet nedan.
array (string|int) type_from_extension(string ext);
Anropas från Spinner i content-type modulen om ingen av modulerna sa vilken typ filen hade. Resultatet är enligt ({ "content-type", "content-encoding"|0 }); Det kan som sagt bara finnas en content-type modul i varje virtuell server, så du behöver nog inte skriva en sådan här modul heller.
object|mapping remap_url(object request_id);
Returnera antingen request_id (med iallafall en eller annan variabel modifierad, förhoppningsvis) , i vilket fall den används för att göra en ny request (vilket innebär att modulen kommer att anropas igen, se upp!), eller också så kan funktionen returnera en mapping med någon av http_... funktionerna (se nedan).
Slutligen så kan man returnera noll, i vilket fall allt bara fortsätter.
Notera att man kan ändra på variabler i request_id, och sedan returnera noll.
Efter denna (mycket) långa utläggning kan det vara dags att gå igenom något mer konkret.
Så, raskt hoppar vi tillbaka till vår gazonk modul.
Vi kan se i tabellen att en dylik (MODULE_PARSER, om ni har glömt bort vilken typ det skulle vara) modul ska ha initieringsfunktionerna query_container_callers och query_tag_callers, med typer som ser ut som om de hade hittats på av en tangentbordstillverkare.
Eftersom vi inte vill plåga våra stackars fingrar mer än nödvändigt så skriver vi:
mapping query_tag_callers() { }
mapping query_container_callers() {
}
Nu finns funktionerna, men vad ska de egentligen returnera?
/* A simple parse type module for spinner. * This module defines two new tags, foo and bar, one is a container * and the other one is a stand alone tag. */
#include <module.h>
inherit "module"; inherit "spinnerlib";
array register_module() { return ({ MODULE_PARSER, "Gazonk", "The gazonk module. This module adds a container and a non-container: " "<foo></foo> and <bar>", 0, 1, });
/* A container gets the contents as the third argument. * Example: <foo bar=gazonk>Hi!</foo> --> * container_foo("foo", "Hi!", (["bar":"gazonk"]), ...); */ string container_foo(string tag_name, string contents, mapping arguments, object request_id, object file, mapping defines) { if(arguments->lower) return lower_case(contents); if(arguments->upper) return upper_case(contents); if(arguments->reverse) return reverse(contents); }
string tag_bar(string tag_name, mapping arguments, object request_id, object file, mapping defines) { int i; string res="";
if(arguments->num) i=(int)arguments->num; else i=30;
#define SUNE (("abcdefghijklmnopqrstuvwxyzåäö")/"")
while(i--) res += SUNE[random(sizeof(SUNE))];
#undef SUNE
return res; }
mapping query_tag_callers() { return ([ "bar":tag_bar, ]); }
mapping query_container_callers() { return ([ "foo":tag_foo, ]); }
Nu har vi faktiskt en färdig parse-modul!
I och för sig kanske inte så spännande, men den gör iallafall någonting.
Dukumentation om variabeln request_id, och om hur man kan returnera data, finns i artikeln om µLPC skript.
int time;
När gjordes uppkopplingen? Sekunder sedan 00:00:00 den 1/1 1970.
object conf;
Current configuration, en pekare till den virtuella server som
requesten kom till (alltså den server som din modul ligger i).
Du behöver nog inte den här variabeln, om du inte vill utföra udda
operationer.
string raw_url;
URLen helt oparsad som den kom från klienten.
mapping (string:string) variables;
I HTTP finns det en kul sak som heter 'variabler', de kommer oftast
från en 'form'.
I den här mappingen finns allihop instoppade, färdigparsade.
Exempelvis:
En förfrågan efter filen /goo/bar?hmm=hej&foo=%20%20 kommer
att resultera att variables sätts till ([ "hmm":"hej", "foo":" "
]).
mapping (string:array (string)) misc;
Blandat smått och gott.. Du behöver nog inte bry dig, men man kan ju
alltid skriva ut fältet för att se vad det innehåller:
perror(sprintf("%O", request_id->misc));
Här kan du även defeniera dina egna variabler, om du behöver det.
Du bör dock se till att dina variabler har unika namn, om du inte vill
råka ut för otrevliga överraskningar.
list (string) prestate;
Prestate används mycket, iallafall på Lysator.
Det är en lista med strängar, man kan alltså göra saker som
if(request_id->prestate->nobg)
no_background = 1;
Prestate skrivs innan filnamet I URLen, så här: /(foo,bar)/goo/bar
list (string) config;
Config fungerar precis som prestate, men det lagras inte undan i
URLen, utan i client-side cookies. Detta har flera fördelar:
Men även några nackdelar:
För att addera något till 'config' låter man användaren accessa en fil enligt:
http://din.server:port/<+config,-config,...>/riktig/URL.
Användaren kommer då att få configen tillagd till cookien
Spinner-Config i sin klient, och få en redirect till
/riktig/URL. Detta är mycket Spinner specifikt..
list (string) supports;
Vad klienten kan hantera.
Innehållet i den här listan kommer från filen etc/supports, per
default. Det hela kan ändras i konfigurationsinterfacet.
Används lämpligtis för att konditionellt generera kod till olika klienter
Exempel:
string remoteaddr;
IP-nummret till datorn på andra sidan förbindelsen, som en sträng
("158.126.90.157")
array (string) client;
Vilken klient som frågar efter sidan, en array eftersom protokollet
(HTTP/1.*) dumt nog tillåter flera User-Agent: header rader, och man
vet ju aldrig..
Lämpligtvis använder man '*' operatorn för att få en sträng av det
hela (request_id->client*""). Ofta är det mycket smartare att använda
supports istället för client.
array (string) referer;
Den (eller de..) sidor som refererade till den här sidan.
list (string) pragma;
Alla pragma headrar klienten har skickat. Intressant är 'no-cache',
som skickas när man trycker på 'reload' av de flesta klienter.
string prot;
Det protokoll som användes för att generara frågan, troligtvis
HTTP/1.0 eller HTTP/1.1, men även saker som FTP, GOPHER och HTTP/0.9
kan förekomma.
string method;
Metod, troligtvis 'GET'.
string rest_query="";
Allt efter ett '?' i URL-en som inte är variabler.
string raw;
Hela requesten som rå data.
string query;
Allt efter '?' i URLen.
string not_query;
Allt innan '?' i URLen exklusive prestate, det är det här som används
av spinner för att få fram vilka moduler som requesten ska mappas
genom.
string data;
Allt i en BODY i requesten. Oftast inte mycket, men vid metoden POST
används det till alla variabler.
array (int|string) auth;
Antingen 0 (ingen authentifiering skickad av klienten)
({ 0, "username:password" }) (authentifiering skickad, men auth
modulen tycker inte att den är korrekt, och
användaren finns inte ens)
({ 0, "username" }) (authentifiering skickad, men auth
modulen tycker inte att den är korrekt, och
användaren finns)
({ 1, "username" }) (authentifiering skickad, och auth modulen tycker att den är korrekt)
mapping (string:string) cookies;
Alla cookies som klienten har skickat.
request_id - objektet med all info (tm)
Nedan följer en mycket kortfattad beskrivning av de flesta variablerna
i objektet i fråga:
if(request_id->supports->tables)
return make_table();
else
return make_pre();
Att själv sätta ihop en mapping av det slag som ska returneras i de flesta fall (extension, last, first, file_extension) när man har lyckats hitta en fil som verkar bra kan vara lite knivigt. Därför finns det en hel del hjälpfunktioner.
Alla är defenierade i filen lpc/http.lpc, som ärvs av spinnerlib, som man i sin tur lämpligen ärver i sina moduler.
Funktionerna finns beskrivna i artikeln om µLPC skript.
Returnera en sträng som resultat, typen är text/html om inget annat
anges.
mapping http_file_answer(object fp, string|void type, void|int len);
Returnera en fil som resultat, typen är text/html om inget annat
anges. Om man inte anger längden kommer spinner att ta reda på den
själv. fp ska vara en instans av /precompiled/file, eller ett objekt
som implementerar samma metoder (du vill inte, jag lovar..)
mapping http_redirect( string url, object|void request_id )
En redirect till den url som anges. Om man skickar med request_id, och
dessutom anger en relativ URL kommer både prestate och state att
adderas till URLen.
mapping http_auth_failed(string realm)
Begär lösenord (authentifiering) inom namnrymden 'realm' på denna
server. Netscape sparar undan ett användarnamn och lösenord för varje
realm på varje server i minnet.
mapping http_auth_required(string realm, string message)
Samma sak som auth_failed, nästan, men man kan skicka med ett
felmeddelande som visas om användaren väljer 'nej tack'.
mapping http_low_answer(int error_code, string message)
Returnera ett meddelande med 'error_code' som felkod.
Se HTTP specifikationen för en lista med dylika.
mapping http_pipe_in_progress();
Tala om för Spinner att du vill ta hand om allt hädanefter helt
själv. Inte den funktion som man avänder varje dag, men de olika proxy
modulerna använder den.
Om du hör till de som tycker att det är kul att
pilla med detaljerna så är det här hur returvärdet egentligen ser ut:
(alla fält kan utelämnas)
file
data len type leave_me error
([
"file":file_object,
"data":"string",
"len":int,
"type":"main/sub",
"raw":0|1,
"leave_me":0|1,
"error":int,
])
Ett filobjekt som ska skickas till klienten. Skickas efter eventuell "data".
En sträng som ska skickas innan "file".
Längden av data och file ihop. Om du inte specifierar det här kommer
den att räknas ut av Spinner.
mime typen som det som du skickar tillbaka har.
Om du inte defenierar en kommer "text/plain" att användas.
raw
Om du sätter den här variabeln kommer _inget_ förutom det som
du skriver att skickas till klienten. Du måste alltså generera alla
headrar och liknande själv.
Om du sätter den här variablen kommer inget att skickas till klienten
av spinner, och du förväntas hantera all socket kommunikation och
upprensning själv. Används av proxy modulerna.
HTTP svarskoden som ska användas. 200 är normalt, alla koder finns
defenierade i /usr/www/spinner/server/include/variables.h (den mest
feldöpta filen i hela Spinner..)
Om du har tittat ett tag på spinners konfigurationsinterface så har du säkert sett att nästan alla moduler har en massa variabler som man kan sätta.
Hur gör man då om man vill ha några sådana i sin fina modul, för det är ju bra att användaren själv kan konfigurera?
Jo, man använder funktionen 'defvar' i create, och sedan 'query' om man vill veta vad en av variablerna har för värde.
name är namnet på variabeln som du sedan kan använda internt i ditt program för att få reda på dess värde (query("name"), eller QUERY(name))
value är skönskvärdet för variabeln.
long_name är det namn som användaren ser i konfigurationsinterfacet.
type är vilken typ variabeln har:
*TYPE_FILE En fil. Ingen egentlig skillnad mot en sträng just
nu, men ledtexten till användaren skiljer sig.
*TYPE_LOCATION En position i det virtuella filsystemet. Hanteras
precis som TYPE_STRING, men ledtexten till användaren
skiljer sig.
*TYPE_INT Ett heltal.
*TYPE_DIR Ett directoy. Användaren kan bara skriva in directoryn
som finns, och de slutar _alltid_ på '/', du behöver
alltså inte bry dig om det i din modul.
*TYPE_FLOAT Ett flyttal.
*TYPE_TEXT_FIELD Ett fält med text i, kan innehålla flera rader.
*TYPE_FLAG Antingen sant eller falskt, dvs, man kan använda
if(query("var_name")).
*TYPE_COLOR En färg, som ett heltal mellan 0 och 2^24, 8 bittar
för R G och B, respektive. Användes i
konfigurationsinterfacet en gång i tiden.
*TYPE_PASSWORD Ett lösenord. Krypteras automatiskt när användaren
sätter variabeln.
*TYPE_STRING_LIST En array med strängar, dvs ({ "foo", "bar",
"gazonk" }) t.ex.
*TYPE_MULTIPLE_STRING En av många strängar. Strängarna väljs ur den
array man skickar med i 'choises' fältet.
*TYPE_INT_LIST En array med heltal.
*TYPE_MULTIPLE_INT Som TYPE_MULTIPLE_STRING, men heltal istället.
*TYPE_DIR_LIST En array med directoryn.
*TYPE_FILE_LIST En array med filer, hanteras som TYPE_STRING_LIST just
nu, men ledtexten till användaren skiljer sig.
Ett litet exempel kanske kan hjälpa..
void create() { defvar("BG", 1, "Configuration interface background", TYPE_FLAG, "Should the background be set by the configuration interface?"); defvar("NumAccept", 1, "Number of accepts to attempt", TYPE_MULTIPLE_INT, "The maximum number of accepts to attempt for each read callback " "from the main socket. <p> Increasing this will make the server" " faster for users making many simultaneous connections to it, or" " if you have a very busy server.", ({ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 })); defvar("ConfigurationPort", 22202, "Configuration port", TYPE_INT, "the portnumber of the configuration interface. Anything will do, " "but you will have to be able to remember it to configure " "the server."); }
Den här kodsnutten defenierar tre variabler (de kommer egentligen från spinners centrala delar, och inte från en modul, men det fungerar precis lika dant).
Först en flagga, vars skönskvärde är på, sedan ett heltal som väljs från en lista av tal (2^n, 0 <= n <= 10), och sist ett heltal.
Så nu är det bara att sätta igång med din modul!