Att skriva Spinner moduler

Att skriva Spinner Moduler

eller

Konsten att förvirra


Vad är Spinner?

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.

Vad finns det då för typer av moduler?

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.


Hur skriver man då en 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 har jag en spinner. Vad gör jag sedan?

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.

Modultyp define att använda Authentifieringsmodul och användardatabas, kan bara finnas en aktiv i taget MODULE_AUTH Directoryparsningsmodul, kan bara finnas en aktiv i taget MODULE_DIRECTORIES Extension modul (före location) MODULE_EXTENSION Extension modul (efter location) MODULE_FILE_EXTENSION "First try" moduler MODULE_FIRST "Last resort" moduler MODULE_LAST Location modul, tillhandahåller filer MODULE_LOCATION Huvudparser, kan bara finnas en aktiv i taget MODULE_MAIN_PARSER Parser, hanterar en eller flera "tags" MODULE_PARSER Typ modul, hanterar extension -> content-type mapping. MODULE_TYPES URL modul, kan göra interna omriktingar av förfrågningar MODULE_URL

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: "
      "&lt;foo&gt;&lt;/foo&gt; and &lt;bar&gt;", 
      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:

Modultyp initialiseringsrutiner callbackrutiner Alla typer create, start status, info, check_variable, query_name MODULE_AUTH - auth, userinfo, userlist, user_from_uid MODULE_DIRECTORIES - parse_directory MODULE_EXTENSION query_extensions handle_extension MODULE_FILE_EXTENSION query_file_extensions handle_file_extension MODULE_FIRST - first_try MODULE_LAST - last_resort MODULE_LOCATION query_location find_file, find_dir, stat_file, real_file, MODULE_MAIN_PARSER - add_parse_module, remove_parse_module MODULE_PARSER query_tag_callers, query_container_callers Ges av initialiseringsrutinerna MODULE_TYPES - type_from_extension MODULE_URL - remap_url

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?

Hela modulen


/* 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: " "&lt;foo&gt;&lt;/foo&gt; and &lt;bar&gt;", 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.

request_id - objektet med all info (tm)

Nedan följer en mycket kortfattad beskrivning av de flesta variablerna i objektet i fråga:

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:

  if(request_id->supports->tables)
     return make_table();
  else
     return make_pre();

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.


Att returnera data

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.

mapping http_string_answer(string data, string|void type);

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":file_object,
  "data":"string",
  "len":int,
  "type":"main/sub",
  "raw":0|1,  
  "leave_me":0|1,
  "error":int,
])

file
Ett filobjekt som ska skickas till klienten. Skickas efter eventuell "data".

data
En sträng som ska skickas innan "file".

len
Längden av data och file ihop. Om du inte specifierar det här kommer den att räknas ut av Spinner.

type
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.

leave_me
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.

error
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..)


Spinner variabler

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.

int defvar(string name, mixed value, string long_name, int type, string documentation_string[, array choices]);

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_STRING En stäng. (t.ex. "foo bar")

*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!