Code and documentation copyright 1998 by Norman Ramsey. All rights reserved.
To compile this, you'll need Lua.
<*>= #include <string.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "pdbconv.h" #include "pdbrep.h" #include "pdblua.h" <macros> <functions>
These macros help pack and unpack Lua.
<macros>= (<-U) #define UNFLAG(X) fprintf(out, " %s = %s,\n", #X, db->info.X ? "1" : "nil"); #define UNINT(X) fprintf(out, " %s = %d,\n", #X, db->info.X); #define UNLINT(X) fprintf(out, " %s = %ld,\n", #X, db->info.X); #define UNSTRING(X) fprintf(out, " %s = %s,\n", #X, QUOTE(db->info.X)); #define UNDATE(X) (fprintf(out, " %s = ", #X), \ unpack_date(out, db->info.X ## Date), \ fprintf(out, ",\n")); #define PFLAG(X) info->X = bitval("Database." #X); #define PINT(X) info->X = intval("Database." #X); #define PINTOPT(X,N) info->X = intopt("Database." #X, N); #define PLINT(X) PINT(X); #define PLINTOPT(X, N) PINTOPT(X,N); #define PSTRING(X) setstring(info->X, "Database." #X, sizeof(info->X)-1); /* note size doesn't include terminating null */ #define PDATE(X) info->X ## Date = pack_date(objectval("Database.dates." #X));
DefinesPDATE
,PFLAG
,PINT
,PINTOPT
,PLINT
,PLINTOPT
,PSTRING
,UNDATE
,UNFLAG
,UNINT
,UNLINT
,UNSTRING
(links are to index).
<functions>= (<-U) [D->] static PDBConverter get_converter(struct pdb *db); /* uses only type, creator */ static void dbLua(FILE *out, struct pdb *db); static void unpack(FILE *in, FILE *out) { struct pdb db = read_db(in); dbLua(out, &db); } static void dbLua(FILE *out, struct pdb *db) { PDBConverter convert = get_converter(db); int i; lua_beginblock(); fprintf(out, "-- This %s created by Norman Ramsey's pdb converter\n", convert->application ? convert->application : "generic view of a PDB file"); fprintf(out, "-- To use the output, get http://www.tecgraf.puc-rio.br/lua\n"); fprintf(out, "-- For more information, see http://www.cs.tufts.edu/~nr/pilot\n"); fprintf(out, "\n\n"); fprintf(out, "-- The value `Database' represents the database\n"); fprintf(out, "Database = {\n"); fprintf(out, " -- the first four fields are required\n"); UNSTRING(name) UNSTRING(type) UNSTRING(creator) UNINT(version) fprintf(out, "\n -- no flags are required (default to nil)\n"); UNFLAG(resource) UNFLAG(readonly) UNFLAG(dirtyAppInfo) UNFLAG(backup) UNFLAG(installOver) UNFLAG(reset) { static char *docs [] = { "Date format:", " unixtime is seconds since 1/1/1970.", " If unixtime is present, all other fields are ignored.", " Otherwise, as many fields as are present are used to construct", " a date, with missing fields defaulting to minimum values.", " The value `today' (without quotes!) may be used instead", " of a table; it is bound to a table with suitable unixtime", 0 }; putc('\n', out); write_docs(out, 4, docs); } fprintf(out, " dates = {\n"); UNDATE(create) UNDATE(modify) UNDATE(backup) fprintf(out, " },\n\n"); fprintf(out, " -- these next three fields default to 0 if omitted\n"); UNLINT(uidSeed) fprintf(out, " -- nextRecordListID should always be 0\n"); UNLINT(nextRecordListID) fprintf(out, " -- modnum is incremented at every database change\n"); UNLINT(modnum) fprintf(out, " -- the following value exists just so we can do exact copies;\n"); fprintf(out, " -- it's fine to leave it out, and it defaults to zero\n"); fprintf(out, " filler = %d,\n", db->filler); fprintf(out, "\n\n -- Everything from here down is application-dependent\n"); if (db->info.appInfo.chars && convert->appInfo.docs) write_docs(out, 4, convert->appInfo.docs); fprintf(out, " appInfo = "); if (db->info.appInfo.chars) convert->appInfo.unpack(out, db, db->info.appInfo); else fprintf(out, "nil"); fprintf(out, ",\n"); if (db->info.sortInfo.chars && convert->sortInfo.docs) write_docs(out, 2, convert->sortInfo.docs); fprintf(out, " sortInfo = "); if (db->info.sortInfo.chars) convert->sortInfo.unpack(out, db, db->info.sortInfo); else fprintf(out, "nil"); fprintf(out, ",\n"); { static char *docs [] = { "Records:", " records must be an array, and each item a table.", " All fields except data can be omitted and default to 0 (or nil).", " delete = deleted busy = locked by an app", " secret = need passwd dirty = set at modify (cleared when?)", 0 }; write_docs(out, 2, docs); if (convert->record.docs) { fprintf(out, " -- The interpretation of the data field is as follows:\n"); write_docs(out, 6, convert->record.docs); } } #define UNRFLAG(X) fprintf(out, "%s = %s, ", #X, db->records[i].X ? "1" : "nil"); fprintf(out, " records = {\n"); for(i=0; i<db->info.numrecs; i++) { lua_beginblock(); fprintf(out, " { category = %d, uid = %d,\n ", db->records[i].category, db->records[i].uid); UNRFLAG(secret) UNRFLAG(busy) UNRFLAG(dirty) UNRFLAG(delete) fprintf(out, "\n data = "); convert->record.unpack(out, db, i); fprintf(out, "\n }%s\n", comma(i + 1 < db->info.numrecs)); lua_endblock(); } fprintf(out, " }\n"); fprintf(out, "}\n\n"); lua_endblock(); if (convert->trailers) { char **p; for (p = convert->trailers; *p; p++) fprintf(out, "%s\n", *p); } }
DefinesdbLua
,unpack
,UNRFLAG
(links are to index).
<functions>+= (<-U) [<-D->] static void pack(char *filename, FILE *output) { int i; struct pdb db; struct DBInfo *info = &db.info; long startpos = ftell(output); memset(&db, 0, sizeof(db)); /* trailing 0s for strings */ <set up the Lua data space and read in the database> lua_beginblock(); <pack the Lua database into data structures, then write it> lua_endblock(); }
Definespack
(links are to index).
We set up a date of today
.
<set up the Lua data space and read in the database>= (<-U) [D->] { static char buf[80]; sprintf(buf, "today = { unixtime = '%lu' }", time(NULL)); if (lua_dostring(buf)) assert(0); }
We need this function to be able to count the number of records.
<bindlistlength
andistable
into the Lua space>= (U->) if(lua_dostring( "$debug\n" "function listlength(list, file, line)\n" " local i, n, v\n" " if type(list) ~= 'table' then \n" " error(file .. ':' .. tostring(line) .. ' -- value of type '" " .. type(list) .. ' is not a list') \n" " end\n" " i = nil\n" " n = 0\n" " repeat\n" " i, v = next(list, i)\n" " if (i ~= nil) then\n" " n = n + 1\n" " end\n" " until (i == nil)\n" " return n\n" "end\n" "\n" "function istable(x)\n" " return type(x) == 'table'\n" "end\n") ) assert(0);
<set up the Lua data space and read in the database>+= (<-U) [<-D] if (lua_dofile(filename)) { fprintf(stderr, "Could not read %s\n", filename ? filename : "<stdin>"); exit(1); }
<pack the Lua database into data structures, then write it>= (<-U) [D->] PSTRING(name) PSTRING(type) PSTRING(creator) PINT(version) PFLAG(resource) PFLAG(readonly) PFLAG(dirtyAppInfo) PFLAG(backup) PFLAG(installOver) PFLAG(reset) PDATE(create) PDATE(modify) PDATE(backup) PLINTOPT(uidSeed, 0) PLINTOPT(nextRecordListID, 0) PLINTOPT(modnum, 0) info->numrecs = intval("listlength(Database.records)"); db.filler = intopt("Database.filler", 0);
Definesbackup
,create
,name
,resource
,version
(links are to index).
Next, compute the sizes of the applicatino info and sort areas, and advance the offset to the start of those areas.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] { int offset = 78; /* size of database header */ PDBConverter convert = get_converter(&db); int appsize = convert->appInfo.pack(NULL, 1); int sortsize = convert->sortInfo.pack(NULL, 0); int appoff, sortoff; offset += info->numrecs * 8; /* sizeof record list */ offset += 2; /* size of filler */
Now we can computer the offsets.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] if (appsize) { appoff = offset; offset += appsize; } else appoff = 0; if (sortsize) { sortoff = offset; offset += sortsize; } else sortoff = 0;
Given those offsets, write the database header part.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] if (write_dbinfo(output, info, appoff, sortoff) != 78) assert(((void)"header length botch", 0));
Next, write the record list.
offset
now points to the beginning of the first record.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] db.records = malloc(info->numrecs * sizeof(*db.records)); assert(db.records); if (info->numrecs < 1) { fprintf(stderr, "Cannot write database with %d records\n", info->numrecs); exit(1); } for(i=0; i < info->numrecs; i++) { lua_beginblock(); lua_pushnumber((double)(i+1)); lua_setglobal("i"); db.records[i].category = intopt("Database.records[i].category", 0); db.records[i].uid = intopt("Database.records[i].uid", 0); #define PRFLAG(F) db.records[i].F = bitval("Database.records[i]." #F); PRFLAG(secret) PRFLAG(busy) PRFLAG(dirty) PRFLAG(delete) write_record_header(output, &db.records[i], offset); db.records[i].data = text(NULL, offset); /* flagrant misuse */ offset += convert->record.pack(NULL, i); lua_endblock(); }
DefinesPRFLAG
(links are to index).
Now we have written the header and record list, and it's time to write the filler.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] put_short(output, intopt("Database.filler", 0));
Next, write the app info and sort area.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D->] if (appoff) { assert(ftell(output) - startpos == appoff); convert->appInfo.pack(output, 1); } if (sortoff) { assert(ftell(output) - startpos == sortoff); convert->sortInfo.pack(output, 1); }
At this point, we should be ready to write the records, and then we are done.
<pack the Lua database into data structures, then write it>+= (<-U) [<-D] for (i = 0; i < info->numrecs; i++) { assert(ftell(output) - startpos == db.records[i].data.len); lua_beginblock(); convert->record.pack(output, i); lua_endblock(); } }
<functions>+= (<-U) [<-D->] void usage(char *prog) { fprintf(stderr, "Usage: %s [-generic] pack [input [output.pdb]] -- convert ASCII to .pdb\n", prog); fprintf(stderr, " %s [-generic] unpack [input.pdb [output]] -- convert .pdb to ASCII\n", prog); fprintf(stderr, " Omitted files default to stdin/stdout\n"); exit(1); }
Definesusage
(links are to index).
This is an error-checking fopen
.
<functions>+= (<-U) [<-D->] static FILE *openfile(char *name, char *mode) { FILE *fp = fopen(name, mode); if (fp) return fp; fprintf(stderr, "Unable to open file %s in mode \"%s\"\n", name, mode); exit(1); return NULL; /* prevent warning */ }
Definesopenfile
(links are to index).
<functions>+= (<-U) [<-D->] extern struct PDBConv handyShopper, JFile, mobileDB, ToDo, expense; static int generic = 0; static PDBConverter converters[] = { &handyShopper, &JFile, &mobileDB, &ToDo, &expense, 0 }; static PDBConverter get_converter(struct pdb *db) { PDBConverter *convert; for (convert = converters; !generic && *convert; convert++) if (!strcmp((*convert)->type, db->info.type) && !strcmp((*convert)->creator, db->info.creator) ) return *convert; return generic_convert; }
Definesconverters
,generic
,get_converter
(links are to index).
<functions>+= (<-U) [<-D] int main(int argc, char *argv[]) { char *in, *out; extern void strlib_open(void); extern void iolib_open(void); generic = argc > 1 && !strcmp(argv[1], "-generic"); atexit(showall); strlib_open(); iolib_open(); <bindlistlength
andistable
into the Lua space> if (generic) { argv[1] = argv[0]; argv++; argc--; } if (argc < 2 || argc > 4) usage(argv[0]); in = argc < 3 ? NULL : strcmp(argv[2], "-") ? argv[2] : NULL; out = argc < 4 ? NULL : strcmp(argv[3], "-") ? argv[3] : NULL; if (!strcmp(argv[1], "pack")) pack(in, out ? openfile(out, "wb") : stdout); else if (!strcmp(argv[1], "unpack")) unpack(in ? openfile(in, "rb") : stdin, out ? openfile(out, "w") : stdout); else usage(argv[0]); return 0; }
Definesmain
(links are to index).
listlength
and istable
into the Lua space>: D1, U2