[27518] trunk/base/src

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 6 13:12:22 PDT 2007


Revision: 27518
          http://trac.macosforge.org/projects/macports/changeset/27518
Author:   sfiera at macports.org
Date:     2007-08-06 13:12:22 -0700 (Mon, 06 Aug 2007)

Log Message:
-----------
Committing registry2.0 to local branch

Added Paths:
-----------
    trunk/base/src/registry2.0/
    trunk/base/src/registry2.0/Makefile
    trunk/base/src/registry2.0/centry.c
    trunk/base/src/registry2.0/centry.h
    trunk/base/src/registry2.0/creg.c
    trunk/base/src/registry2.0/creg.h
    trunk/base/src/registry2.0/entry.c
    trunk/base/src/registry2.0/entry.h
    trunk/base/src/registry2.0/entryobj.c
    trunk/base/src/registry2.0/entryobj.h
    trunk/base/src/registry2.0/graph.c
    trunk/base/src/registry2.0/graph.h
    trunk/base/src/registry2.0/graphobj.c
    trunk/base/src/registry2.0/graphobj.h
    trunk/base/src/registry2.0/item.c
    trunk/base/src/registry2.0/item.h
    trunk/base/src/registry2.0/itemobj.c
    trunk/base/src/registry2.0/itemobj.h
    trunk/base/src/registry2.0/registry.c
    trunk/base/src/registry2.0/registry.h
    trunk/base/src/registry2.0/sql.c
    trunk/base/src/registry2.0/sql.h
    trunk/base/src/registry2.0/tests/
    trunk/base/src/registry2.0/tests/common.tcl
    trunk/base/src/registry2.0/tests/entry.tcl
    trunk/base/src/registry2.0/tests/item.tcl
    trunk/base/src/registry2.0/util.c
    trunk/base/src/registry2.0/util.h

Added: trunk/base/src/registry2.0/Makefile
===================================================================
--- trunk/base/src/registry2.0/Makefile	                        (rev 0)
+++ trunk/base/src/registry2.0/Makefile	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,15 @@
+OBJS=       registry.o util.o sql.o \
+			creg.o centry.o \
+			entry.o entryobj.o
+			#graph.o graphobj.o
+SHLIB_NAME= registry${SHLIB_SUFFIX}
+INSTALLDIR= ${DESTDIR}${datadir}/macports/Tcl/registry2.0
+export MACOSX_DEPLOYMENT_TARGET=10.3
+
+include ../../Mk/macports.autoconf.mk
+include ../../Mk/macports.tea.mk
+
+.PHONY: test
+
+test:: ${SHLIB_NAME}
+	${TCLSH} tests/entry.tcl ${SHLIB_NAME}

Added: trunk/base/src/registry2.0/centry.c
===================================================================
--- trunk/base/src/registry2.0/centry.c	                        (rev 0)
+++ trunk/base/src/registry2.0/centry.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,700 @@
+/*
+ * centry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <sqlite3.h>
+
+#include "centry.h"
+#include "creg.h"
+
+/**
+ * Concatenates `src` to string `dst`.
+ *
+ * Simple concatenation. Only guaranteed to work with strings that have been
+ * allocated with `malloc`. Amortizes cost of expanding string buffer for O(N)
+ * concatenation and such. Uses `memcpy` in favor of `strcpy` in hopes it will
+ * perform a bit better. If passing in a static string to dst, make sure
+ * dst_space starts at dst_len. Also make sure dst_space is never 0 (so don't
+ * use "" as the starter string, allocate some space);
+ */
+void reg_strcat(char** dst, int* dst_len, int* dst_space, char* src) {
+    int src_len = strlen(src);
+    if (*dst_len + src_len >= *dst_space) {
+        char* old_dst = *dst;
+        char* new_dst = malloc(*dst_space * 2 * sizeof(char));
+        *dst_space *= 2;
+        memcpy(new_dst, old_dst, *dst_len);
+        *dst = new_dst;
+        free(old_dst);
+    }
+    memcpy(&((*dst)[*dst_len]), src, src_len+1);
+    *dst_len += src_len;
+}
+
+/**
+ * Appends `src` to the list `dst`.
+ *
+ * It's like `reg_strcat`, except `src` represents an element and not a sequence
+ * of `char`s.
+ */
+static void reg_listcat(void*** dst, int* dst_len, int* dst_space, void* src) {
+    if (*dst_len == *dst_space) {
+        void** old_dst = *dst;
+        void** new_dst = malloc(*dst_space * 2 * sizeof(void*));
+        *dst_space *= 2;
+        memcpy(new_dst, old_dst, *dst_len);
+        *dst = new_dst;
+        free(old_dst);
+    }
+    (*dst)[*dst_len] = src;
+    (*dst_len)++;
+}
+
+/**
+ * Returns the operator to use for the given strategy.
+ */
+static char* reg_strategy_op(int strategy, reg_error* errPtr) {
+    switch (strategy) {
+        case 0:
+            return "=";
+        case 1:
+            return " GLOB ";
+        case 2:
+            return " REGEXP ";
+        default:
+            errPtr->code = "registry::invalid-strategy";
+            errPtr->description = "invalid matching strategy specified";
+            errPtr->free = NULL;
+            return NULL;
+    }
+}
+
+static int reg_stmt_to_entry(void* userdata, void** entry, void* stmt,
+        reg_error* errPtr UNUSED) {
+    if (sqlite3_column_type(stmt, 1) == SQLITE_NULL) {
+        reg_entry* e = malloc(sizeof(reg_entry));
+        e->db = (sqlite3*)userdata;
+        e->id = sqlite3_column_int64(stmt, 0);
+        e->saved = 0;
+        e->proc = NULL;
+        *entry = e;
+    } else {
+        *entry = *(reg_entry**)sqlite3_column_blob(stmt, 1);
+    }
+    return 1;
+}
+
+static int reg_save_addresses(sqlite3* db, reg_entry** entries,
+        int entry_count, reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    int i;
+    char* query = "INSERT INTO entries (id, address) VALUES (?, ?)";
+    /* avoid unnecessarily making queries */
+    for (i=0; i<entry_count; i++) {
+        if (!entries[i]->saved) {
+            break;
+        }
+    }
+    if (i == entry_count) {
+        return 1;
+    }
+    /* BEGIN */
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL)
+                == SQLITE_OK) {
+        for (i=0; i<entry_count; i++) {
+            if (entries[i]->saved) {
+                continue;
+            }
+            if ((sqlite3_bind_int64(stmt, 1, entries[i]->id) == SQLITE_OK)
+                    && (sqlite3_bind_blob(stmt, 2, &entries[i],
+                            sizeof(reg_entry*), SQLITE_TRANSIENT) == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                sqlite3_reset(stmt);
+            } else {
+                sqlite3_finalize(stmt);
+                reg_sqlite_error(db, errPtr, query);
+            }
+        }
+        sqlite3_finalize(stmt);
+        return 1;
+    } else {
+        sqlite3_finalize(stmt);
+        reg_sqlite_error(db, errPtr, query);
+    }
+    return 0;
+}
+
+/**
+ * registry::entry create portname version revision variants epoch ?name?
+ *
+ * Unlike the old registry::new_entry, revision, variants, and epoch are all
+ * required. That's OK because there's only one place this function is called,
+ * and it's called with all of them there.
+ */
+reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+        char* revision, char* variants, char* epoch, reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "INSERT INTO registry.ports "
+        "(name, version, revision, variants, epoch) VALUES (?, ?, ?, ?, ?)";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 3, revision, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 4, variants, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 5, epoch, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_step(stmt) == SQLITE_DONE)) {
+        char* query = "INSERT INTO entries (id, address) VALUES (?, ?)";
+        reg_entry* entry = malloc(sizeof(reg_entry));
+        entry->id = sqlite3_last_insert_rowid(db);
+        entry->db = db;
+        entry->proc = NULL;
+        sqlite3_finalize(stmt);
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL)
+                == SQLITE_OK)
+                && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+                && (sqlite3_bind_blob(stmt, 2, &entry, sizeof(reg_entry*),
+                        SQLITE_TRANSIENT) == SQLITE_OK)
+                && (sqlite3_step(stmt) == SQLITE_DONE)) {
+            return entry;
+        } else {
+            reg_sqlite_error(db, errPtr, query);
+        }
+        free(entry);
+        return NULL;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return NULL;
+    }
+}
+
+reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+        char* revision, char* variants, char* epoch, reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "SELECT registry.ports.id, entries.address "
+        "FROM registry.ports LEFT OUTER JOIN entries USING (id) "
+        "WHERE name=? AND version=? AND revision=? AND variants=? AND epoch=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 3, revision, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 4, variants, -1, SQLITE_STATIC)
+                == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 5, epoch, -1, SQLITE_STATIC)
+                == SQLITE_OK)) {
+        int r = sqlite3_step(stmt);
+        reg_entry* entry;
+        switch (r) {
+            case SQLITE_ROW:
+                if (reg_stmt_to_entry(db, (void**)&entry, stmt, errPtr)) {
+                    sqlite3_finalize(stmt);
+                    if (reg_save_addresses(db, &entry, 1, errPtr)) {
+                        return entry;
+                    }
+                }
+            case SQLITE_DONE:
+                errPtr->code = "registry::not-found";
+                errPtr->description = "no matching port found";
+                errPtr->free = NULL;
+                break;
+            default:
+                reg_sqlite_error(db, errPtr, query);
+                break;
+        }
+        sqlite3_finalize(stmt);
+        return NULL;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return NULL;
+    }
+}
+
+/**
+ * deletes an entry; still needs to be freed
+ */
+int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "DELETE FROM registry.ports WHERE id=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+            && (sqlite3_step(stmt) == SQLITE_DONE)) {
+        if (sqlite3_changes(db) > 0) {
+            sqlite3_finalize(stmt);
+            query = "DELETE FROM entries WHERE id=?";
+            if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                    && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                sqlite3_finalize(stmt);
+                return 1;
+            }
+        } else {
+            errPtr->code = "registry::invalid-entry";
+            errPtr->description = "an invalid entry was passed";
+            errPtr->free = NULL;
+        }
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+    }
+    sqlite3_finalize(stmt);
+    return 0;
+}
+
+/*
+ * Frees the entries in `entries`.
+ */
+void reg_entry_free(sqlite3* db UNUSED, reg_entry* entry) {
+    sqlite3_stmt* stmt;
+    if (entry->proc != NULL) {
+        free(entry->proc);
+    }
+    free(entry);
+    sqlite3_prepare(entry->db, "DELETE FROM entries WHERE address=?", -1, &stmt,
+            NULL);
+    sqlite3_bind_blob(stmt, 1, &entry, sizeof(reg_entry*), SQLITE_TRANSIENT);
+    sqlite3_step(stmt);
+}
+
+static int reg_all_objects(sqlite3* db, char* query, int query_len,
+        void*** objects, cast_function* fn, free_function* del,
+        reg_error* errPtr) {
+    int r;
+    reg_entry* entry;
+    void** results = malloc(10*sizeof(void*));
+    int result_count = 0;
+    int result_space = 10;
+    sqlite3_stmt* stmt;
+    if (sqlite3_prepare(db, query, query_len, &stmt, NULL) == SQLITE_OK) {
+        do {
+            int i;
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    if (fn(db, (void**)&entry, stmt, errPtr)) {
+                        reg_listcat(&results, &result_count, &result_space,
+                                entry);
+                        continue;
+                    } else {
+                        int i;
+                        sqlite3_finalize(stmt);
+                        for (i=0; i<result_count; i++) {
+                            del(NULL, results[i]);
+                        }
+                        free(results);
+                        return -1;
+                    }
+                case SQLITE_DONE:
+                    break;
+                default:
+                    for (i=0; i<result_count; i++) {
+                        del(NULL, results[i]);
+                    }
+                    free(results);
+                    sqlite3_finalize(stmt);
+                    reg_sqlite_error(db, errPtr, query);
+                    return -1;
+            }
+        } while (r != SQLITE_DONE);
+        *objects = results;
+        return result_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        free(results);
+        return -1;
+    }
+}
+
+/*
+ * Searches the registry for ports for which each key's value is equal to the
+ * given value. To find all ports, pass 0 key-value pairs.
+ *
+ * Vulnerable to SQL-injection attacks in the `keys` field. Pass it valid keys,
+ * please.
+ */
+int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+        int strategy, reg_entry*** entries, reg_error* errPtr) {
+    int i;
+    char* kwd = " WHERE ";
+    char* query;
+    int query_len = 32;
+    int query_space = 32;
+    int result;
+    /* get the strategy */
+    char* op = reg_strategy_op(strategy, errPtr);
+    if (op == NULL) {
+        return -1;
+    }
+    /* build the query */
+    query = strdup("SELECT registry.ports.id, entries.address "
+            "FROM registry.ports LEFT OUTER JOIN entries USING (id)");
+    for (i=0; i<key_count; i+=1) {
+        char* cond = sqlite3_mprintf("%s%s%s'%q'", kwd, keys[i], op, vals[i]);
+        reg_strcat(&query, &query_len, &query_space, cond);
+        sqlite3_free(cond);
+        kwd = " AND ";
+    }
+    /* do the query */
+    result = reg_all_objects(db, query, query_len, (void***)entries,
+            reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+    if (result > 0) {
+        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+            free(entries);
+            return 0;
+        }
+    }
+    free(query);
+    return result;
+}
+
+/**
+ * TODO: fix this to return ports where state=active too
+ * TODO: add more arguments (epoch, revision, variants), maybe
+ */
+int reg_entry_installed(sqlite3* db, char* name, char* version, 
+        reg_entry*** entries, reg_error* errPtr) {
+    char* format;
+    char* query;
+    int result;
+    char* select = "SELECT registry.ports.id, entries.address FROM "
+        "registry.ports LEFT OUTER JOIN entries USING (id)";
+    if (name == NULL) {
+        format = "%s WHERE (state='installed' OR state='active')";
+    } else if (version == NULL) {
+        format = "%s WHERE (state='installed' OR state='active') AND name='%q'";
+    } else {
+        format = "%s WHERE (state='installed' OR state='active') AND name='%q' "
+            "AND version='%q'";
+    }
+    query = sqlite3_mprintf(format, select, name, version);
+    result = reg_all_objects(db, query, -1, (void***)entries,
+            reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+    if (result > 0) {
+        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+            free(entries);
+            return 0;
+        }
+    }
+    sqlite3_free(query);
+    return result;
+}
+
+/**
+ */
+int reg_entry_active(sqlite3* db, char* name, reg_entry*** entries,
+        reg_error* errPtr) {
+    char* format;
+    char* query;
+    int result;
+    char* select = "SELECT registry.ports.id, entries.address FROM "
+        "registry.ports LEFT OUTER JOIN entries USING (id)";
+    if (name == NULL) {
+        format = "%s WHERE state='active'";
+    } else {
+        format = "%s WHERE state='active' AND name='%q'";
+    }
+    query = sqlite3_mprintf(format, select, name);
+    result = reg_all_objects(db, query, -1, (void***)entries,
+            reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+    if (result > 0) {
+        if (!reg_save_addresses(db, *entries, result, errPtr)) {
+            free(entries);
+            return 0;
+        }
+    }
+    sqlite3_free(query);
+    return result;
+}
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    reg_entry* result;
+    char* query = "SELECT registry.files.id, entries.address "
+        "FROM registry.files LEFT OUTER JOIN entries USING (id) "
+        "WHERE path=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC)
+                == SQLITE_OK)) {
+        int r = sqlite3_step(stmt);
+        switch (r) {
+            case SQLITE_ROW:
+                if (reg_stmt_to_entry(db, (void**)&result, stmt, errPtr)) {
+                    sqlite3_finalize(stmt);
+                    if (reg_save_addresses(db, &result, 1, errPtr)) {
+                        *entry = result;
+                        return 1;
+                    }
+                }
+            case SQLITE_DONE:
+                sqlite3_finalize(stmt);
+                *entry = NULL;
+                return 1;
+            default:
+                /* barf */
+                sqlite3_finalize(stmt);
+                return 0;
+        }
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return 0;
+    }
+}
+
+int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = sqlite3_mprintf("SELECT %q FROM registry.ports "
+            "WHERE id=%lld", key, entry->id);
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int r = sqlite3_step(stmt);
+        switch (r) {
+            case SQLITE_ROW:
+                *value = strdup(sqlite3_column_text(stmt, 0));
+                sqlite3_finalize(stmt);
+                return 1;
+            case SQLITE_DONE:
+                errPtr->code = "registry::invalid-entry";
+                errPtr->description = "an invalid entry was passed";
+                errPtr->free = NULL;
+                sqlite3_finalize(stmt);
+                return 0;
+            default:
+                reg_sqlite_error(db, errPtr, query);
+                sqlite3_finalize(stmt);
+                return 0;
+        }
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        return 0;
+    }
+}
+
+int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = sqlite3_mprintf("UPDATE registry.ports SET %q = '%q' "
+            "WHERE id=%lld", key, value, entry->id);
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int r = sqlite3_step(stmt);
+        switch (r) {
+            case SQLITE_DONE:
+                sqlite3_finalize(stmt);
+                return 1;
+            default:
+                switch (sqlite3_reset(stmt)) {
+                    case SQLITE_CONSTRAINT:
+                        errPtr->code = "registry::constraint";
+                        errPtr->description = "a constraint was disobeyed";
+                        errPtr->free = NULL;
+                        sqlite3_finalize(stmt);
+                        return 0;
+                    default:
+                        reg_sqlite_error(db, errPtr, query);
+                        sqlite3_finalize(stmt);
+                        return 0;
+                }
+        }
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        return 0;
+    }
+}
+
+int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "INSERT INTO registry.files (id, path) VALUES (?, ?)";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+        int i;
+        for (i=0; i<file_count; i++) {
+            if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
+                    == SQLITE_OK) {
+                int r = sqlite3_step(stmt);
+                switch (r) {
+                    case SQLITE_DONE:
+                        sqlite3_reset(stmt);
+                        continue;
+                    case SQLITE_ERROR:
+                        switch (sqlite3_reset(stmt)) {
+                            case SQLITE_CONSTRAINT:
+                                errPtr->code = "registry::already-owned";
+                                errPtr->description = "mapped file is already "
+                                    "owned by another entry";
+                                errPtr->free = NULL;
+                                sqlite3_finalize(stmt);
+                                return i;
+                            default:
+                                reg_sqlite_error(db, errPtr, query);
+                                sqlite3_finalize(stmt);
+                                return i;
+                        }
+                }
+            } else {
+                reg_sqlite_error(db, errPtr, query);
+                sqlite3_finalize(stmt);
+                return i;
+            }
+        }
+        sqlite3_finalize(stmt);
+        return file_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return 0;
+    }
+}
+
+int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "DELETE FROM registry.files WHERE id=? AND path=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+        int i;
+        for (i=0; i<file_count; i++) {
+            if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
+                    == SQLITE_OK) {
+                int r = sqlite3_step(stmt);
+                switch (r) {
+                    case SQLITE_DONE:
+                        if (sqlite3_changes(db) == 0) {
+                            errPtr->code = "registry::not-owned";
+                            errPtr->description = "this entry does not own the "
+                                "given file";
+                            errPtr->free = NULL;
+                            sqlite3_finalize(stmt);
+                            return i;
+                        } else {
+                            sqlite3_reset(stmt);
+                            continue;
+                        }
+                    default:
+                        reg_sqlite_error(db, errPtr, query);
+                        sqlite3_finalize(stmt);
+                        return i;
+                }
+            } else {
+                reg_sqlite_error(db, errPtr, query);
+                sqlite3_finalize(stmt);
+                return i;
+            }
+        }
+        sqlite3_finalize(stmt);
+        return file_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return 0;
+    }
+}
+
+int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "SELECT path FROM registry.files WHERE id=? ORDER BY path";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+        char** result = malloc(10*sizeof(char*));
+        int result_count = 0;
+        int result_space = 10;
+        int r;
+        do {
+            char* element;
+            int i;
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    element = strdup(sqlite3_column_text(stmt, 0));
+                    reg_listcat((void*)&result, &result_count, &result_space,
+                            element);
+                    break;
+                case SQLITE_DONE:
+                    break;
+                default:
+                    for (i=0; i<result_count; i++) {
+                        free(result[i]);
+                    }
+                    free(result);
+                    reg_sqlite_error(db, errPtr, query);
+                    sqlite3_finalize(stmt);
+                    return -1;
+            }
+        } while (r != SQLITE_DONE);
+        sqlite3_finalize(stmt);
+        *files = result;
+        return result_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return -1;
+    }
+}
+
+int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr) {
+    reg_entry* entry;
+    void** results = malloc(10*sizeof(void*));
+    int result_count = 0;
+    int result_space = 10;
+    sqlite3_stmt* stmt;
+    char* query = "SELECT address FROM entries";
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int r;
+        do {
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    entry = *(reg_entry**)sqlite3_column_blob(stmt, 0);
+                    reg_listcat(&results, &result_count, &result_space, entry);
+                    break;
+                case SQLITE_DONE:
+                    break;
+                default:
+                    reg_sqlite_error(db, errPtr, query);
+                    free(results);
+                    return -1;
+            }
+        } while (r != SQLITE_DONE);
+    }
+    *entries = (reg_entry**)results;
+    return result_count;
+}

Added: trunk/base/src/registry2.0/centry.h
===================================================================
--- trunk/base/src/registry2.0/centry.h	                        (rev 0)
+++ trunk/base/src/registry2.0/centry.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,86 @@
+/*
+ * centry.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _CENTRY_H
+#define _CENTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+#include "creg.h"
+
+typedef struct {
+    sqlite_int64 id; /* rowid in database */
+    sqlite3* db; /* database */
+    int saved; /* have we recorded &entry in database? */
+    char* proc; /* name of Tcl proc, if using Tcl */
+} reg_entry;
+
+typedef int (cast_function)(void* userdata, void** dst, void* src,
+        reg_error* errPtr);
+typedef void (free_function)(void* userdata, void* item);
+
+reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+        char* revision, char* variants, char* epoch, reg_error* errPtr);
+
+reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+        char* revision, char* variants, char* epoch, reg_error* errPtr);
+
+int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr);
+
+void reg_entry_free(sqlite3* db, reg_entry* entry);
+
+int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+        int strategy, reg_entry*** entries, reg_error* errPtr);
+
+int reg_entry_installed(sqlite3* db, char* name, char* version, 
+        reg_entry*** entries, reg_error* errPtr);
+
+int reg_entry_active(sqlite3* db, char* name, reg_entry*** entries,
+        reg_error* errPtr);
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+        reg_error* errPtr);
+
+int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
+        reg_error* errPtr);
+int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
+        reg_error* errPtr);
+
+int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr);
+int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr);
+
+int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+        reg_error* errPtr);
+
+int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr);
+
+#endif /* _CENTRY_H */

Added: trunk/base/src/registry2.0/creg.c
===================================================================
--- trunk/base/src/registry2.0/creg.c	                        (rev 0)
+++ trunk/base/src/registry2.0/creg.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,165 @@
+/*
+ * reg.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sqlite3.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "graph.h"
+#include "item.h"
+#include "entry.h"
+#include "util.h"
+#include "sql.h"
+
+void reg_error_destruct(reg_error* errPtr) {
+    if (errPtr->free) {
+        errPtr->free(errPtr->description);
+    }
+}
+
+/**
+ * Sets `errPtr` according to the last error in `db`.
+ */
+void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query) {
+    errPtr->code = "registry::sqlite-error";
+    errPtr->free = (reg_error_destructor*)sqlite3_free;
+    if (query == NULL) {
+        errPtr->description = sqlite3_mprintf("sqlite error: %s",
+                sqlite3_errmsg(db));
+    } else {
+        errPtr->description = sqlite3_mprintf("sqlite error: %s while "
+                "executing query: %s", sqlite3_errmsg(db), query);
+    }
+}
+
+int reg_open(sqlite3** dbPtr, reg_error* errPtr) {
+    if (sqlite3_open(NULL, dbPtr) == SQLITE_OK) {
+        if (init_db(*dbPtr, errPtr)) {
+            return 1;
+        } else {
+            sqlite3_close(*dbPtr);
+            *dbPtr = NULL;
+        }
+    } else {
+        reg_sqlite_error(*dbPtr, errPtr, NULL);
+        sqlite3_close(*dbPtr);
+        *dbPtr = NULL;
+    }
+    return 0;
+}
+
+int reg_close(sqlite3* db, reg_error* errPtr) {
+    if (sqlite3_close((sqlite3*)db) == SQLITE_OK) {
+        return 1;
+    } else {
+        errPtr->code = "registry::not-closed";
+        errPtr->description = sqlite3_mprintf("error: registry db not closed "
+                "correctly (%s)\n", sqlite3_errmsg((sqlite3*)db));
+        errPtr->free = (reg_error_destructor*)sqlite3_free;
+        return 0;
+    }
+}
+
+int reg_attach(sqlite3* db, const char* path, reg_error* errPtr) {
+    struct stat sb;
+    int needsInit = 0; /* registry doesn't yet exist */
+    int canWrite = 1; /* can write to this location */
+    if (stat(path, &sb) != 0) {
+        if (errno == ENOENT) {
+            needsInit = 1;
+        } else {
+            canWrite = 0;
+        }
+    }
+    if (!needsInit || canWrite) {
+        sqlite3_stmt* stmt;
+        char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", path);
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                && (sqlite3_step(stmt) == SQLITE_DONE)) {
+            sqlite3_finalize(stmt);
+            if (!needsInit || (create_tables(db, errPtr))) {
+                return 1;
+            }
+        } else {
+            reg_sqlite_error(db, errPtr, query);
+            sqlite3_finalize(stmt);
+        }
+    } else {
+        errPtr->code = "registry::cannot-init";
+        errPtr->description = sqlite3_mprintf("port registry doesn't exist at \"%q\" and couldn't write to this location", path);
+        errPtr->free = (reg_error_destructor*)sqlite3_free;
+    }
+    return 0;
+}
+
+int reg_detach(sqlite3* db, reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    char* query = "DETACH DATABASE registry";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_step(stmt) == SQLITE_DONE)) {
+        sqlite3_finalize(stmt);
+        query = "SELECT address FROM entries";
+        if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+            int r;
+            reg_entry* entry;
+            do {
+                r = sqlite3_step(stmt);
+                switch (r) {
+                    case SQLITE_ROW:
+                        entry = *(reg_entry**)sqlite3_column_blob(stmt, 0);
+                        reg_entry_free(db, entry);
+                        break;
+                    case SQLITE_DONE:
+                        break;
+                    default:
+                        reg_sqlite_error(db, errPtr, query);
+                        return 0;
+                }
+            } while (r != SQLITE_DONE);
+        }
+        sqlite3_finalize(stmt);
+        query = "DELETE FROM entries";
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) != SQLITE_OK)
+                || (sqlite3_step(stmt) != SQLITE_DONE)) {
+            sqlite3_finalize(stmt);
+            return 0;
+        }
+        return 1;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return 0;
+    }
+}
+

Added: trunk/base/src/registry2.0/creg.h
===================================================================
--- trunk/base/src/registry2.0/creg.h	                        (rev 0)
+++ trunk/base/src/registry2.0/creg.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,55 @@
+/*
+ * creg.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _CREG_H
+#define _CREG_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+
+typedef void reg_error_destructor(const char* description);
+
+typedef struct {
+    char* code;
+    const char* description;
+    reg_error_destructor* free;
+} reg_error;
+
+void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query);
+
+void reg_error_destruct(reg_error* errPtr);
+
+int reg_open(sqlite3** dbPtr, reg_error* errPtr);
+int reg_close(sqlite3* db, reg_error* errPtr);
+
+int reg_attach(sqlite3* db, const char* path, reg_error* errPtr);
+int reg_detach(sqlite3* db, reg_error* errPtr);
+
+#endif /* _CREG_H */

Added: trunk/base/src/registry2.0/entry.c
===================================================================
--- trunk/base/src/registry2.0/entry.c	                        (rev 0)
+++ trunk/base/src/registry2.0/entry.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,431 @@
+/*
+ * entry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "entry.h"
+#include "entryobj.h"
+#include "registry.h"
+#include "util.h"
+
+static reg_entry* get_entry(Tcl_Interp* interp, char* name, reg_error* errPtr) {
+    return (reg_entry*)get_object(interp, name, "entry", entry_obj_cmd, errPtr);
+}
+
+static void delete_entry(ClientData clientData) {
+    reg_entry_free(NULL, (reg_entry*)clientData);
+}
+
+static int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
+        reg_error* errPtr) {
+    if (set_object(interp, name, entry, "entry", entry_obj_cmd, delete_entry,
+                errPtr)) {
+        int size = strlen(name) + 1;
+        entry->proc = malloc(size*sizeof(char));
+        memcpy(entry->proc, name, size);
+        return 1;
+    }
+    return 0;
+}
+
+static int obj_to_entry(Tcl_Interp* interp, reg_entry** entry, Tcl_Obj* obj,
+        reg_error* errPtr) {
+    reg_entry* result = get_entry(interp, Tcl_GetString(obj), errPtr);
+    if (result == NULL) {
+        return 0;
+    } else {
+        *entry = result;
+        return 1;
+    }
+}
+
+static int list_obj_to_entry(Tcl_Interp* interp, reg_entry*** entries,
+        const Tcl_Obj** objv, int objc, reg_error* errPtr) {
+    return recast(interp, (cast_function*)obj_to_entry, NULL, (void***)entries,
+            (void**)objv, objc, errPtr);
+}
+
+static int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
+        reg_error* errPtr) {
+    if (entry->proc == NULL) {
+        char* name = unique_name(interp, "registry::entry");
+        if (!set_entry(interp, name, entry, errPtr)) {
+            free(name);
+            return 0;
+        }
+        free(name);
+    }
+    *obj = Tcl_NewStringObj(entry->proc, -1);
+    return 1;
+}
+
+static int list_entry_to_obj(Tcl_Interp* interp, Tcl_Obj*** objs,
+        reg_entry** entries, int entry_count, reg_error* errPtr) {
+    return recast(interp, (cast_function*)entry_to_obj, NULL, (void***)objs,
+            (void**)entries, entry_count, errPtr);
+}
+
+
+/**
+ * registry::entry create portname version revision variants epoch
+ *
+ * Unlike the old registry::new_entry, revision, variants, and epoch are all
+ * required. That's OK because there's only one place this function is called,
+ * and it's called with all of them there.
+ */
+static int entry_create(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (objc != 7) {
+        Tcl_WrongNumArgs(interp, 2, objv, "name version revision variants "
+                "epoch");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* name = Tcl_GetString(objv[2]);
+        char* version = Tcl_GetString(objv[3]);
+        char* revision = Tcl_GetString(objv[4]);
+        char* variants = Tcl_GetString(objv[5]);
+        char* epoch = Tcl_GetString(objv[6]);
+        reg_error error;
+        reg_entry* entry = reg_entry_create(db, name, version, revision,
+                variants, epoch, &error);
+        if (entry != NULL) {
+            Tcl_Obj* result;
+            if (entry_to_obj(interp, &result, entry, &error)) {
+                Tcl_SetObjResult(interp, result);
+                return TCL_OK;
+            }
+            reg_entry_free(db, entry);
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+/**
+ * registry::entry delete entry
+ *
+ * Deletes an entry from the registry (then closes it).
+ */
+static int entry_delete(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "delete entry");
+        return TCL_ERROR;
+    } if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        reg_entry* entry;
+        reg_error error;
+        if (obj_to_entry(interp, &entry, objv[2], &error)) {
+            if (reg_entry_delete(db, entry, &error)) {
+                Tcl_DeleteCommand(interp, Tcl_GetString(objv[2]));
+                return TCL_OK;
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+/**
+ * registry::entry open portname version revision variants epoch ?name?
+ *
+ *
+ */
+static int entry_open(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (objc != 7) {
+        Tcl_WrongNumArgs(interp, 1, objv, "open portname version revision "
+                "variants epoch");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* name = Tcl_GetString(objv[2]);
+        char* version = Tcl_GetString(objv[3]);
+        char* revision = Tcl_GetString(objv[4]);
+        char* variants = Tcl_GetString(objv[5]);
+        char* epoch = Tcl_GetString(objv[6]);
+        reg_error error;
+        reg_entry* entry = reg_entry_open(db, name, version, revision, variants,
+                epoch, &error);
+        if (entry != NULL) {
+            Tcl_Obj* result;
+            if (entry_to_obj(interp, &result, entry, &error)) {
+                Tcl_SetObjResult(interp, result);
+                return TCL_OK;
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+    return TCL_ERROR;
+}
+
+/*
+ * registry::entry close entry
+ *
+ * Closes an entry. It will remain in the registry until next time.
+ */
+static int entry_close(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "delete entry");
+        return TCL_ERROR;
+    } else {
+        reg_error error;
+        char* proc = Tcl_GetString(objv[2]);
+        reg_entry* entry = get_entry(interp, proc, &error);
+        if (entry == NULL) {
+            return registry_failed(interp, &error);
+        } else {
+            Tcl_DeleteCommand(interp, proc);
+            return TCL_OK;
+        }
+    }
+}
+
+/*
+ * registry::entry search ?key value ...?
+ *
+ * Searches the registry for ports for which each key's value is equal to the
+ * given value. To find all ports, call `entry search` with no key-value pairs.
+ *
+ * TODO: allow selection of -exact, -glob, and -regexp matching.
+ */
+static int entry_search(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    int i;
+    sqlite3* db = registry_db(interp, 1);
+    if (objc % 2 == 1) {
+        Tcl_WrongNumArgs(interp, 2, objv, "search ?key value ...?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** keys;
+        char** vals;
+        int key_count = objc/2 - 1;
+        reg_entry** entries;
+        reg_error error;
+        int entry_count;
+        /* ensure that valid search keys were used */
+        for (i=2; i<objc; i+=2) {
+            int index;
+            if (Tcl_GetIndexFromObj(interp, objv[i], entry_props, "search key",
+                        0, &index) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        keys = malloc(key_count * sizeof(char*));
+        vals = malloc(key_count * sizeof(char*));
+        for (i=0; i<key_count; i+=1) {
+            keys[i] = Tcl_GetString(objv[2*i+2]);
+            vals[i] = Tcl_GetString(objv[2*i+3]);
+        }
+        entry_count = reg_entry_search(db, keys, vals, key_count, 0, &entries,
+                &error);
+        if (entry_count >= 0) {
+            Tcl_Obj* resultObj;
+            Tcl_Obj** objs;
+            list_entry_to_obj(interp, &objs, entries, entry_count,
+                        &error);
+            resultObj = Tcl_NewListObj(entry_count, objs);
+            Tcl_SetObjResult(interp, resultObj);
+            free(entries);
+            return TCL_OK;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+/**
+ * registry::entry exists name
+ *
+ * Note that this is <i>not</i> the same as entry_exists from registry1.0. This
+ * simply checks if the given string is a valid entry object in the current
+ * interp. No query to the database will be made.
+ */
+static int entry_exists(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    reg_error error;
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "name");
+        return TCL_ERROR;
+    }
+    if (get_entry(interp, Tcl_GetString(objv[2]), &error) == NULL) {
+        reg_error_destruct(&error);
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+    } else {
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+    }
+    return TCL_OK;
+}
+
+/**
+ * registry::entry installed ?name? ?version?
+ *
+ * Returns a list of all installed ports. If `name` is specified, only returns
+ * ports with that name, and if `version` is specified, only with that version.
+ * Remember, the variants can still be different.
+ *
+ * TODO: add more arguments (epoch, revision, variants), maybe
+ */
+static int entry_installed(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]){
+    sqlite3* db = registry_db(interp, 1);
+    if (objc > 4) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?name? ?version?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* name = (objc >= 3) ? Tcl_GetString(objv[2]) : NULL;
+        char* version = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
+        reg_entry** entries;
+        reg_error error;
+        int entry_count = reg_entry_installed(db, name, version, &entries,
+                &error);
+        if (entry_count >= 0) {
+            Tcl_Obj* resultObj;
+            Tcl_Obj** objs;
+            list_entry_to_obj(interp, &objs, entries, entry_count,
+                        &error);
+            resultObj = Tcl_NewListObj(entry_count, objs);
+            Tcl_SetObjResult(interp, resultObj);
+            free(entries);
+            return TCL_OK;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+/**
+ * registry::entry active ?name?
+ *
+ * Returns a list of all active ports. If `name` is specified, only returns the
+ * active port named, still in a list.
+ */
+static int entry_active(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]){
+    sqlite3* db = registry_db(interp, 1);
+    if (objc > 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?name?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* name = (objc == 3) ? Tcl_GetString(objv[2]) : NULL;
+        reg_entry** entries;
+        reg_error error;
+        int entry_count = reg_entry_active(db, name, &entries,
+                &error);
+        if (entry_count >= 0) {
+            Tcl_Obj* resultObj;
+            Tcl_Obj** objs;
+            list_entry_to_obj(interp, &objs, entries, entry_count,
+                        &error);
+            resultObj = Tcl_NewListObj(entry_count, objs);
+            Tcl_SetObjResult(interp, resultObj);
+            free(entries);
+            return TCL_OK;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+
+/**
+ */
+static int entry_owner(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "path");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* path = Tcl_GetString(objv[2]);
+        reg_entry* entry;
+        reg_error error;
+        if (reg_entry_owner(db, path, &entry, &error)) {
+            if (entry == NULL) {
+                return TCL_OK;
+            } else {
+                Tcl_Obj* result;
+                if (entry_to_obj(interp, &result, entry, &error)) {
+                    Tcl_SetObjResult(interp, result);
+                    return TCL_OK;
+                }
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} entry_cmd_type;
+
+static entry_cmd_type entry_cmds[] = {
+    /* Global commands */
+    { "create", entry_create },
+    { "delete", entry_delete },
+    { "open", entry_open },
+    { "close", entry_close },
+    { "search", entry_search },
+    { "exists", entry_exists },
+    { "installed", entry_installed },
+    { "active", entry_active },
+    { "owner", entry_owner },
+    { NULL, NULL }
+};
+
+/**
+ * registry::entry cmd ?arg ...?
+ *
+ * Commands manipulating port entries in the registry. This could be called
+ * `registry::port`, but that could be misleading, because `registry::item`
+ * represents ports too, but not those in the registry.
+ */
+int entry_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], entry_cmds,
+                sizeof(entry_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+        entry_cmd_type* cmd = &entry_cmds[cmd_index];
+        return cmd->function(interp, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/entry.h
===================================================================
--- trunk/base/src/registry2.0/entry.h	                        (rev 0)
+++ trunk/base/src/registry2.0/entry.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * entry.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ENTRY_H
+#define _ENTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int entry_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ENTRY_H */

Added: trunk/base/src/registry2.0/entryobj.c
===================================================================
--- trunk/base/src/registry2.0/entryobj.c	                        (rev 0)
+++ trunk/base/src/registry2.0/entryobj.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,224 @@
+/*
+ * entryobj.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "entryobj.h"
+#include "registry.h"
+#include "util.h"
+
+const char* entry_props[] = {
+    "name",
+    "portfile",
+    "url",
+    "location",
+    "epoch",
+    "version",
+    "revision",
+    "variants",
+    "date",
+    "state",
+    NULL
+};
+
+/* ${entry} prop ?value? */
+static int entry_obj_prop(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int index;
+    sqlite3* db = registry_db(interp, 1);
+    if (objc > 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?value?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    }
+    if (objc == 2) {
+        /* ${entry} prop; return the current value */
+        if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+                == TCL_OK) {
+            char* key = Tcl_GetString(objv[1]);
+            char* value;
+            reg_error error;
+            if (reg_entry_propget(db, entry, key, &value, &error)) {
+                Tcl_Obj* result = Tcl_NewStringObj(value, -1);
+                Tcl_SetObjResult(interp, result);
+                return TCL_OK;
+            }
+            return registry_failed(interp, &error);
+        }
+        return TCL_ERROR;
+    } else {
+        /* ${entry} prop name value; set a new value */
+        if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+                == TCL_OK) {
+            char* key = Tcl_GetString(objv[1]);
+            char* value = Tcl_GetString(objv[2]);
+            reg_error error;
+            if (reg_entry_propset(db, entry, key, value, &error)) {
+                return TCL_OK;
+            }
+            return registry_failed(interp, &error);
+        }
+        return TCL_ERROR;
+    }
+}
+
+/*
+ * ${entry} map ?file ...?
+ *
+ * Maps the listed files to the port represented by ${entry}. This will throw an
+ * error if a file is mapped to an already-existing file, but not a very
+ * descriptive one.
+ *
+ * TODO: more descriptive error on duplicated file
+ */
+static int entry_obj_map(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** files;
+        reg_error error;
+        if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
+            if (reg_entry_map(db, entry, files, objc-2, &error) == objc-2) {
+                return TCL_OK;
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+/*
+ * ${entry} unmap ?file ...?
+ *
+ * Unmaps the listed files from the given port. Will throw an error if a file
+ * that is not mapped to the port is attempted to be unmapped.
+ */
+static int entry_obj_unmap(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** files;
+        reg_error error;
+        if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
+            if (reg_entry_unmap(db, entry, files, objc-2, &error) == objc-2) {
+                return TCL_OK;
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+static int entry_obj_files(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "files");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** files;
+        reg_error error;
+        int file_count = reg_entry_files(db, entry, &files, &error);
+        if (file_count >= 0) {
+            int i;
+            Tcl_Obj** objs;
+            if (list_string_to_obj(&objs, files, file_count, &error)) {
+                for (i=0; i<file_count; i++) {
+                    free(files[i]);
+                }
+                free(files);
+                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+                Tcl_SetObjResult(interp, result);
+                return TCL_OK;
+            }
+            for (i=0; i<file_count; i++) {
+                free(files[i]);
+            }
+            free(files);
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, reg_entry* entry, int objc,
+            Tcl_Obj* CONST objv[]);
+} entry_obj_cmd_type;
+
+static entry_obj_cmd_type entry_cmds[] = {
+    { "name", entry_obj_prop },
+    { "portfile", entry_obj_prop },
+    { "url", entry_obj_prop },
+    { "location", entry_obj_prop },
+    { "epoch", entry_obj_prop },
+    { "version", entry_obj_prop },
+    { "revision", entry_obj_prop },
+    { "variants", entry_obj_prop },
+    { "date", entry_obj_prop },
+    { "state", entry_obj_prop },
+    { "map", entry_obj_map },
+    { "unmap", entry_obj_unmap },
+    { "files", entry_obj_files },
+    { NULL, NULL }
+};
+
+/* ${entry} cmd ?arg ...? */
+/* This function implements the command that will be called when an entry
+ * created by `registry::entry` is used as a procedure. Since all data is kept
+ * in a temporary sqlite3 database that is created for the current interpreter,
+ * none of the sqlite3 functions used have any error checking. That should be a
+ * safe assumption, since nothing outside of registry:: should ever have the
+ * chance to touch it.
+ */
+int entry_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], entry_cmds,
+                sizeof(entry_obj_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+        entry_obj_cmd_type* cmd = &entry_cmds[cmd_index];
+        return cmd->function(interp, (reg_entry*)clientData, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/entryobj.h
===================================================================
--- trunk/base/src/registry2.0/entryobj.h	                        (rev 0)
+++ trunk/base/src/registry2.0/entryobj.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,48 @@
+/*
+ * entryobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ENTRY_OBJ_CMD_H
+#define _ENTRY_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+typedef struct {
+    sqlite_int64 rowid;
+    sqlite3* db;
+} entry_t;
+
+extern const char* entry_props[];
+
+int entry_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ENTRY_OBJ_CMD_H */

Added: trunk/base/src/registry2.0/graph.c
===================================================================
--- trunk/base/src/registry2.0/graph.c	                        (rev 0)
+++ trunk/base/src/registry2.0/graph.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,198 @@
+/*
+ * graph.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graph.h"
+#include "graphobj.h"
+#include "registry.h"
+#include "util.h"
+
+void DeleteGraph(graph* g) {
+    sqlite3_stmt* stmt;
+    if ((sqlite3_prepare(g->db, "DETACH DATABASE registry", -1, &stmt, NULL)
+                != SQLITE_OK)
+            || (sqlite3_step(stmt) != SQLITE_DONE)) {
+        fprintf(stderr, "error: registry db not detached correctly (%s)\n",
+                sqlite3_errmsg(g->db));
+    }
+    free(g);
+}
+
+graph* GetGraph(Tcl_Interp* interp, char* name) {
+    return GetCtx(interp, name, "graph", GraphObjCmd);
+}
+
+int SetGraph(Tcl_Interp* interp, char* name, graph* g) {
+    return SetCtx(interp, name, g, "graph", GraphObjCmd,
+            (Tcl_CmdDeleteProc*)DeleteGraph);
+}
+
+/* graph create dbfile ?name? */
+int GraphCreateCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    if (objc > 4 || objc < 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "dbfile ?name?");
+        return TCL_ERROR;
+    } else {
+        sqlite3* db = RegistryDB(interp);
+        sqlite3_stmt* stmt;
+        int needsInit = 0;
+        int len;
+        char* file = Tcl_GetStringFromObj(objv[2], &len);
+        char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", file);
+
+        if (Tcl_FSAccess(objv[2], F_OK) != 0) {
+            needsInit = 1;
+            printf("initializing\n");
+        }
+
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                && (sqlite3_step(stmt) == SQLITE_DONE)) {
+            sqlite3_finalize(stmt);
+            if (!needsInit
+                    || ((sqlite3_prepare(db, "CREATE TABLE registry.ports "
+                                "(name, portfile, url, location, epoch, "
+                                "version, revision, variants, state)", -1,
+                                &stmt, NULL)
+                            == SQLITE_OK)
+                        && (sqlite3_step(stmt) == SQLITE_DONE))) {
+                graph* g = malloc(sizeof(graph));
+                g->db = db;
+                sqlite3_free(query);
+                if (objc == 4) {
+                    /* graph create dbfile name */
+                    if (SetGraph(interp, Tcl_GetString(objv[3]), g) == TCL_OK) {
+                        Tcl_SetObjResult(interp, objv[3]);
+                        return TCL_OK;
+                    }
+                } else {
+                    /* graph create dbfile; generate a name */
+                    char* name = UniqueName(interp, "registry::graph");
+                    if (SetGraph(interp, name, g) == TCL_OK) {
+                        Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+                        Tcl_SetObjResult(interp, res);
+                        free(name);
+                        return TCL_OK;
+                    }
+                    free(name);
+                }
+                free(g);
+            }
+        } else {
+            set_sqlite_result(interp, db, query);
+            sqlite3_free(query);
+        }
+        sqlite3_finalize(stmt);
+        return TCL_ERROR;
+    }
+}
+
+/* graph delete ?name ...? */
+int GraphDeleteCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    int i;
+    for (i=2; i<objc; i++) {
+        graph* g;
+        char* proc = Tcl_GetString(objv[i]);
+        g = GetGraph(interp, proc);
+        if (g == NULL) {
+            return TCL_ERROR;
+        } else {
+            Tcl_DeleteCommand(interp, proc);
+        }
+    }
+    return TCL_OK;
+}
+
+/* graph exists name */
+int GraphExistsCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "name");
+        return TCL_ERROR;
+    }
+    if (GetGraph(interp, Tcl_GetString(objv[2])) == NULL) {
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+    } else {
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+    }
+    return TCL_OK;
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} GraphCmdType;
+
+static GraphCmdType graph_cmds[] = {
+    /* commands usable only by `graph` itself */
+    { "create", GraphCreateCmd },
+    { "delete", GraphDeleteCmd },
+    { "exists", GraphExistsCmd },
+    /* commands usable by `graph` or an instance thereof */
+    /* { "install", GraphInstallCmd }, */
+    /* { "uninstall", GraphUninstallCmd }, */
+    /* { "activate", GraphActivateCmd }, */
+    /* { "deactivate", GraphDeactivateCmd }, */
+    /* { "upgrade", GraphUpgradeCmd }, */
+    /* { "changed", GraphChangedCmd }, */
+    /* { "warnings", GraphWarningsCmd }, */
+    /* { "errors", GraphErrorsCmd }, */
+    /* { "commit", GraphCommitCmd }, */
+    /* { "rollback", GraphRollbackCmd }, */
+    /* { "active", GraphActiveCmd }, */
+    /* { "installed", GraphInstalledCmd }, */
+    /* { "location", GraphLocationCmd }, */
+    /* { "map", GraphMapCmd }, */
+    /* { "unmap", GraphUnmapCmd }, */
+    /* { "contents", GraphContentsCmd }, */
+    /* { "provides", GraphProvidesCmd }, */
+    { NULL, NULL }
+};
+
+/* graph cmd ?arg ...? */
+int GraphCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], graph_cmds,
+                sizeof(GraphCmdType), "cmd", 0, &cmd_index) == TCL_OK) {
+        GraphCmdType* cmd = &graph_cmds[cmd_index];
+        return cmd->function(interp, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/graph.h
===================================================================
--- trunk/base/src/registry2.0/graph.h	                        (rev 0)
+++ trunk/base/src/registry2.0/graph.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * graph.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRAPH_CMD_H
+#define _GRAPH_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int GraphCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _GRAPH_CMD_H */

Added: trunk/base/src/registry2.0/graphobj.c
===================================================================
--- trunk/base/src/registry2.0/graphobj.c	                        (rev 0)
+++ trunk/base/src/registry2.0/graphobj.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,147 @@
+/*
+ * graphobj.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graphobj.h"
+#include "util.h"
+
+/* ${graph} install registry::item */
+int GraphObjInstallCmd(Tcl_Interp* interp, graph* g, int objc,
+        Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "install registry::item");
+        return TCL_ERROR;
+    } else {
+        printf("installing %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+    }
+    return TCL_OK;
+}
+
+/* ${graph} uninstall registry::item */
+int GraphObjUninstallCmd(Tcl_Interp* interp, graph* g, int objc,
+        Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "uninstall registry::item");
+        return TCL_ERROR;
+    } else {
+        printf("uninstalling %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+    }
+    return TCL_OK;
+}
+
+/* ${graph} activate registry::item */
+int GraphObjActivateCmd(Tcl_Interp* interp, graph* g, int objc,
+        Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "activate registry::item");
+        return TCL_ERROR;
+    } else {
+        printf("activating %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+    }
+    return TCL_OK;
+}
+
+/* ${graph} deactivate registry::item */
+int GraphObjDeactivateCmd(Tcl_Interp* interp, graph* g, int objc,
+        Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "deactivate registry::item");
+        return TCL_ERROR;
+    } else {
+        printf("deactivating %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+    }
+    return TCL_OK;
+}
+
+enum {
+    BUBBLE_UP = 1,
+    BUBBLE_DOWN = 2
+};
+
+/* ${graph} upgrade ?-bubble-up? ?-bubble-down? ?--? porturl */
+int GraphObjUpgradeCmd(Tcl_Interp* interp, graph* g, int objc,
+        Tcl_Obj* CONST objv[]) {
+    option_spec options[] = {
+        { "--", END_FLAGS },
+        { "-bubble-up", BUBBLE_UP },
+        { "-bubble-down", BUBBLE_DOWN },
+        { NULL, 0 }
+    };
+    int flags;
+    int start=2;
+    if (ParseFlags(interp, objc, objv, &start, options, &flags) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (objc - start != 1) {
+        Tcl_WrongNumArgs(interp, 1, objv, "upgrade ?-bubble-up? "
+                "?--bubble-down? ?--? registry::item");
+        return TCL_ERROR;
+    } else {
+        printf("upgrading %s to %p (flags %x)\n", Tcl_GetString(objv[start]),
+                (void*)g, flags);
+    }
+    return TCL_OK;
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp*, graph*, int, Tcl_Obj* CONST objv[]);
+} GraphObjCmdType;
+
+static GraphObjCmdType graph_obj_cmds[] = {
+    { "install", GraphObjInstallCmd },
+    { "uninstall", GraphObjUninstallCmd },
+    { "activate", GraphObjActivateCmd },
+    { "deactivate", GraphObjDeactivateCmd },
+    { "upgrade", GraphObjUpgradeCmd },
+    { NULL, NULL }
+};
+
+/* ${graph} cmd ?arg ...? */
+int GraphObjCmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], graph_obj_cmds,
+                sizeof(GraphObjCmdType), "cmd", 0, &cmd_index) == TCL_OK) {
+        GraphObjCmdType* cmd = &graph_obj_cmds[cmd_index];
+        return cmd->function(interp, clientData, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/graphobj.h
===================================================================
--- trunk/base/src/registry2.0/graphobj.h	                        (rev 0)
+++ trunk/base/src/registry2.0/graphobj.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,44 @@
+/*
+ * graphobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRAPH_OBJ_CMD_H
+#define _GRAPH_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+typedef struct {
+    sqlite3* db;
+} graph;
+
+int GraphObjCmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _GRAPH_OBJ_CMD_H */

Added: trunk/base/src/registry2.0/item.c
===================================================================
--- trunk/base/src/registry2.0/item.c	                        (rev 0)
+++ trunk/base/src/registry2.0/item.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,264 @@
+/*
+ * item.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "item.h"
+#include "itemobj.h"
+#include "util.h"
+#include "registry.h"
+
+static void delete_item(ClientData clientData) {
+    sqlite_int64 rowid = ((item_t*)clientData)->rowid;
+    sqlite3* db = ((item_t*)clientData)->db;
+    sqlite3_stmt* stmt;
+    sqlite3_prepare(db, "DELETE FROM items WHERE rowid=?", -1, &stmt, NULL);
+    sqlite3_bind_int(stmt, rowid, 1);
+    sqlite3_step(stmt);
+    sqlite3_finalize(stmt);
+    free(clientData);
+}
+
+static item_t* get_item(Tcl_Interp* interp, char* name) {
+    return (item_t*)get_object(interp, name, "item", item_obj_cmd);
+}
+
+static int set_item(Tcl_Interp* interp, char* name, sqlite_int64 rowid) {
+    sqlite3* db = registry_db(interp, 0);
+    item_t* new_item = malloc(sizeof(item_t));
+    new_item->rowid = rowid;
+    new_item->db = db;
+    if (set_object(interp, name, new_item, "item", item_obj_cmd, delete_item)
+                == TCL_OK) {
+        sqlite3_stmt* stmt;
+        /* record the proc name in case we need to return it in a search */
+        if ((sqlite3_prepare(db, "UPDATE items SET proc=? WHERE rowid=?", -1,
+                    &stmt, NULL) == SQLITE_OK)
+                && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+                    == SQLITE_OK)
+                && (sqlite3_bind_int64(stmt, 2, rowid) == SQLITE_OK)
+                && (sqlite3_step(stmt) == SQLITE_DONE)) {
+            sqlite3_finalize(stmt);
+            return TCL_OK;
+        }
+        Tcl_DeleteCommand(interp, name);
+        sqlite3_finalize(stmt);
+    }
+    free(new_item);
+    return TCL_ERROR;
+}
+
+/* item create ?name? */
+static int item_create(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite_int64 item;
+    sqlite3* db = registry_db(interp, 0);
+    if (objc > 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?name?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    }
+    sqlite3_exec(db, "INSERT INTO items (refcount) VALUES (1)", NULL, NULL,
+            NULL);
+    item = sqlite3_last_insert_rowid(db);
+    if (objc == 3) {
+        /* item create name */
+        char* name = Tcl_GetString(objv[2]);
+        if (set_item(interp, name, item) == TCL_OK) {
+            Tcl_SetObjResult(interp, objv[2]);
+            return TCL_OK;
+        }
+    } else {
+        /* item create */
+        char* name = unique_name(interp, "registry::item");
+        if (set_item(interp, name, item) == TCL_OK) {
+            Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+            Tcl_SetObjResult(interp, res);
+            free(name);
+            return TCL_OK;
+        }
+        free(name);
+    }
+    return TCL_ERROR;
+}
+
+/* item release ?name ...? */
+static int item_release(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    int i;
+    for (i=2; i<objc; i++) {
+        char* proc = Tcl_GetString(objv[i]);
+        item_t* item = get_item(interp, proc);
+        if (item == NULL) {
+            return TCL_ERROR;
+        } else {
+            /* decref */
+        }
+    }
+    return TCL_OK;
+}
+
+static const char* searchKeys[] = {
+    "name",
+    "url",
+    "path",
+    "worker",
+    "options",
+    "variants",
+    NULL
+};
+
+/**
+ * item search ?{key value} ...?
+ *
+ * TODO: rip this out and adapt `entry search`
+ */
+static int item_search(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    int i, r;
+    sqlite3* db = registry_db(interp, 0);
+    sqlite3_stmt* stmt;
+    Tcl_Obj* result;
+    /* 40 + 20 per clause is safe */
+    char* query = (char*)malloc((20*objc)*sizeof(char));
+    char* insert;
+    if (db == NULL) {
+        return TCL_ERROR;
+    }
+    strcpy(query, "SELECT proc FROM items");
+    insert = query + strlen("SELECT proc FROM items");
+    for (i=2; i<objc; i++) {
+        int len;
+        int index;
+        char* key;
+        Tcl_Obj* keyObj;
+        /* ensure each search clause is a 2-element list */
+        if (Tcl_ListObjLength(interp, objv[i], &len) != TCL_OK || len != 2) {
+            free(query);
+            Tcl_AppendResult(interp, "search clause \"", Tcl_GetString(objv[i]),
+                    "\" is not a list with 2 elements", NULL);
+            return TCL_ERROR;
+        }
+        /* this should't fail if Tcl_ListObjLength didn't */
+        Tcl_ListObjIndex(interp, objv[i], 0, &keyObj);
+        /* ensure that a valid search key was used */
+        if (Tcl_GetIndexFromObj(interp, keyObj, searchKeys, "search key", 0,
+                &index) != TCL_OK) {
+            free(query);
+            return TCL_ERROR;
+        }
+        key = Tcl_GetString(keyObj);
+        if (i == 2) {
+            sprintf(insert, " WHERE %s=?", key);
+            insert += 9 + strlen(key);
+        } else {
+            sprintf(insert, " AND %s=?", key);
+            insert += 7 + strlen(key);
+        }
+    }
+    r = sqlite3_prepare(db, query, -1, &stmt, NULL);
+    free(query);
+    for (i=2; i<objc; i++) {
+        char* val;
+        Tcl_Obj* valObj;
+        Tcl_ListObjIndex(interp, objv[i], 1, &valObj);
+        val = Tcl_GetString(valObj);
+        sqlite3_bind_text(stmt, i-1, val, -1, SQLITE_STATIC);
+    }
+    result = Tcl_NewListObj(0, NULL);
+    r = sqlite3_step(stmt);
+    while (r == SQLITE_ROW) {
+        /* avoid signedness warning */
+        const char* proc = sqlite3_column_text(stmt, 0);
+        int len = sqlite3_column_bytes(stmt, 0);
+        Tcl_Obj* procObj = Tcl_NewStringObj(proc, len);
+        Tcl_ListObjAppendElement(interp, result, procObj);
+        r = sqlite3_step(stmt);
+    }
+    Tcl_SetObjResult(interp, result);
+    return TCL_OK;
+}
+
+/* item exists name */
+static int item_exists(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "name");
+        return TCL_ERROR;
+    }
+    if (get_item(interp, Tcl_GetString(objv[2])) == NULL) {
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+    } else {
+        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+    }
+    return TCL_OK;
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} item_cmd_type;
+
+static item_cmd_type item_cmds[] = {
+    /* Global commands */
+    { "create", item_create },
+    { "search", item_search },
+    { "exists", item_exists },
+    /* Instance commands */
+    /*
+    { "retain", item_retain },
+    { "release", item_release },
+    { "name", item_name },
+    { "url", item_url },
+    { "path", item_path },
+    { "worker", item_worker },
+    { "options", item_options },
+    { "variants", item_variants },
+    */
+    { NULL, NULL }
+};
+
+/* item cmd ?arg ...? */
+int item_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], item_cmds,
+                sizeof(item_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+        item_cmd_type* cmd = &item_cmds[cmd_index];
+        return cmd->function(interp, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/item.h
===================================================================
--- trunk/base/src/registry2.0/item.h	                        (rev 0)
+++ trunk/base/src/registry2.0/item.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * item.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ITEM_CMD_H
+#define _ITEM_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int item_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ITEM_CMD_H */

Added: trunk/base/src/registry2.0/itemobj.c
===================================================================
--- trunk/base/src/registry2.0/itemobj.c	                        (rev 0)
+++ trunk/base/src/registry2.0/itemobj.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,184 @@
+/*
+ * item.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "itemobj.h"
+#include "registry.h"
+#include "util.h"
+
+/* ${item} retain */
+/* Increments the refcount of the item. Calls to retain should be balanced by
+ * calls to release. The refcount starts at 1 and needn't be retained by the
+ * creator.
+ */
+static int item_obj_retain(Tcl_Interp* interp, item_t* item, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3_stmt* stmt;
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 2, objv, "");
+        return TCL_ERROR;
+    }
+    sqlite3_prepare(item->db, "UPDATE items SET refcount = refcount+1 WHERE "
+            "rowid=?", -1, &stmt, NULL);
+    sqlite3_bind_int64(stmt, 1, item->rowid);
+    sqlite3_step(stmt);
+    sqlite3_finalize(stmt);
+    Tcl_SetObjResult(interp, objv[0]);
+    return TCL_OK;
+}
+
+/* ${item} release */
+/* Decrements the refcount of the item. If this is called after all retains have
+ * been balanced with releases, the object will be freed.
+ */
+static int item_obj_release(Tcl_Interp* interp, item_t* item, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3_stmt* stmt;
+    int refcount;
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 2, objv, "");
+        return TCL_ERROR;
+    }
+    sqlite3_prepare(item->db, "UPDATE items SET refcount = refcount-1 "
+            "WHERE rowid=?", -1, &stmt, NULL);
+    sqlite3_bind_int64(stmt, 1, item->rowid);
+    sqlite3_step(stmt);
+    sqlite3_finalize(stmt);
+    sqlite3_prepare(item->db, "SELECT refcount FROM items WHERE rowid=?", -1,
+            &stmt, NULL);
+    sqlite3_bind_int64(stmt, 1, item->rowid);
+    sqlite3_step(stmt);
+    refcount = sqlite3_column_int(stmt, 0);
+    sqlite3_finalize(stmt);
+    if (refcount <= 0) {
+        Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+    }
+    return TCL_OK;
+}
+
+/* ${item} key name ?value? */
+static int item_obj_key(Tcl_Interp* interp, item_t* item, int objc,
+        Tcl_Obj* CONST objv[]) {
+    static const char* keys[] = {
+        "name", "url", "path", "worker", "options", "variants",
+        NULL
+    };
+    if (objc == 3) {
+        /* ${item} key name; return the current value */
+        int index;
+        if (Tcl_GetIndexFromObj(interp, objv[2], keys, "key", 0, &index)
+                != TCL_OK) {
+            /* objv[2] is not a valid key */
+            return TCL_ERROR;
+        } else {
+            sqlite3_stmt* stmt;
+            char query[64];
+            char* key = Tcl_GetString(objv[2]);
+            int len;
+            const char* result;
+            Tcl_Obj* resultObj;
+            sprintf(query, "SELECT %s FROM items WHERE rowid=?", key);
+            sqlite3_prepare(item->db, query, -1, &stmt, NULL);
+            sqlite3_bind_int64(stmt, 1, item->rowid);
+            sqlite3_step(stmt);
+            /* eliminate compiler warning about signedness */
+            result = sqlite3_column_text(stmt, 0);
+            len = sqlite3_column_bytes(stmt, 0);
+            resultObj = Tcl_NewStringObj(result, len);
+            Tcl_SetObjResult(interp, resultObj);
+            sqlite3_finalize(stmt);
+        }
+    } else if (objc == 4) {
+        /* ${item} key name value; set a new value */
+        int index;
+        if (Tcl_GetIndexFromObj(interp, objv[2], keys, "key", 0, &index)
+                != TCL_OK) {
+            /* objv[2] is not a valid key */
+            return TCL_ERROR;
+        } else {
+            sqlite3_stmt* stmt;
+            char query[64];
+            char* key = Tcl_GetString(objv[2]);
+            char* value = Tcl_GetString(objv[3]);
+            sprintf(query, "UPDATE items SET %s=? WHERE rowid=?", key);
+            sqlite3_prepare(item->db, query, -1, &stmt, NULL);
+            sqlite3_bind_text(stmt, 1, value, -1, SQLITE_STATIC);
+            sqlite3_bind_int64(stmt, 2, item->rowid);
+            sqlite3_step(stmt);
+            sqlite3_finalize(stmt);
+        }
+    } else {
+        Tcl_WrongNumArgs(interp, 1, objv, "key name ?value?");
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, item_t* item, int objc,
+            Tcl_Obj* CONST objv[]);
+} item_obj_cmd_type;
+
+static item_obj_cmd_type item_cmds[] = {
+    { "retain", item_obj_retain },
+    { "release", item_obj_release },
+    { "key", item_obj_key },
+    { NULL, NULL }
+};
+
+/* ${item} cmd ?arg ...? */
+/* This function implements the command that will be called when an item created
+ * by `registry::item` is used as a procedure. Since all data is kept in a
+ * temporary sqlite3 database that is created for the current interpreter, none
+ * of the sqlite3 functions used have any error checking. That should be a safe
+ * assumption, since nothing outside of registry:: should ever have the chance
+ * to touch it.
+ */
+int item_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int cmd_index;
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+        return TCL_ERROR;
+    }
+    if (Tcl_GetIndexFromObjStruct(interp, objv[1], item_cmds,
+                sizeof(item_obj_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+        item_obj_cmd_type* cmd = &item_cmds[cmd_index];
+        return cmd->function(interp, (item_t*)clientData, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: trunk/base/src/registry2.0/itemobj.h
===================================================================
--- trunk/base/src/registry2.0/itemobj.h	                        (rev 0)
+++ trunk/base/src/registry2.0/itemobj.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,46 @@
+/*
+ * itemobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ITEM_OBJ_CMD_H
+#define _ITEM_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+typedef struct {
+    sqlite_int64 rowid;
+    sqlite3* db;
+} item_t;
+
+int item_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ITEM_OBJ_CMD_H */

Added: trunk/base/src/registry2.0/registry.c
===================================================================
--- trunk/base/src/registry2.0/registry.c	                        (rev 0)
+++ trunk/base/src/registry2.0/registry.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,188 @@
+/*
+ * registry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graph.h"
+#include "item.h"
+#include "entry.h"
+#include "util.h"
+#include "sql.h"
+
+int registry_failed(Tcl_Interp* interp, reg_error* errPtr) {
+    Tcl_Obj* result = Tcl_NewStringObj(errPtr->description, -1);
+    Tcl_SetObjResult(interp, result);
+    Tcl_SetErrorCode(interp, errPtr->code, NULL);
+    reg_error_destruct(errPtr);
+    return TCL_ERROR;
+}
+
+int registry_tcl_detach(Tcl_Interp* interp, sqlite3* db, reg_error* errPtr) {
+    reg_entry** entries;
+    int entry_count = reg_all_entries(db, &entries, errPtr);
+    if (entry_count >= 0) {
+        int i;
+        for (i=0; i<entry_count; i++) {
+            Tcl_DeleteCommand(interp, entries[i]->proc);
+        }
+        if (reg_detach(db, errPtr)) {
+            Tcl_SetAssocData(interp, "registry::attached", NULL, (void*)0);
+            return 1;
+        }
+    }
+    return registry_failed(interp, errPtr);
+}
+
+/**
+ * Deletes the sqlite3 DB associated with interp.
+ *
+ * This function will close an interp's associated DB, although there doesn't
+ * seem to be a way of verifying that it happened properly. This will be a
+ * problem if we get lazy and forget to finalize a sqlite3_stmt somewhere, so
+ * this function will be noisy and complain if we do.
+ *
+ * Then it will leak memory :(
+ */
+static void delete_db(ClientData db, Tcl_Interp* interp) {
+    reg_error error;
+    if (Tcl_GetAssocData(interp, "registry::attached", NULL)) {
+        if (!registry_tcl_detach(interp, (sqlite3*)db, &error)) {
+            fprintf(stderr, error.description);
+            reg_error_destruct(&error);
+        }
+    }
+    if (!reg_close((sqlite3*)db, &error)) {
+        fprintf(stderr, error.description);
+        reg_error_destruct(&error);
+    }
+}
+
+/**
+ * Returns the sqlite3 DB associated with interp.
+ *
+ * The registry keeps its state in a sqlite3 database that is keyed to the
+ * current interpreter context. Different interps will have different instances
+ * of the connection, although I don't know if the Apple-provided sqlite3 lib
+ * was compiled with thread-safety, so I can't be certain that it's safe to use
+ * the registry from multiple threads. I'm pretty sure it's unsafe to alias a
+ * registry function into a different thread.
+ *
+ * If `attached` is set to true, then this function will additionally check if
+ * a real registry database has been attached. If not, then it will return NULL.
+ *
+ * This function sets its own Tcl result.
+ */
+sqlite3* registry_db(Tcl_Interp* interp, int attached) {
+    sqlite3* db = Tcl_GetAssocData(interp, "registry::db", NULL);
+    if (db == NULL) {
+        reg_error error;
+        if (reg_open(&db, &error)) {
+            Tcl_SetAssocData(interp, "registry::db", delete_db, db);
+        } else {
+            registry_failed(interp, &error);
+            return NULL;
+        }
+    }
+    if (attached) {
+        if (!Tcl_GetAssocData(interp, "registry::attached", NULL)) {
+            Tcl_SetErrorCode(interp, "registry::not-open", NULL);
+            Tcl_SetResult(interp, "registry is not open", TCL_STATIC);
+            db = NULL;
+        }
+    }
+    return db;
+}
+
+static int registry_open(ClientData clientData UNUSED, Tcl_Interp* interp,
+        int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "db-file");
+        return TCL_ERROR;
+    } else {
+        char* path = Tcl_GetString(objv[1]);
+        sqlite3* db = registry_db(interp, 0);
+        reg_error error;
+        if (reg_attach(db, path, &error)) {
+            Tcl_SetAssocData(interp, "registry::attached", NULL,
+                    (void*)1);
+            return TCL_OK;
+        } else {
+            return registry_failed(interp, &error);
+        }
+    }
+}
+
+static int registry_close(ClientData clientData UNUSED, Tcl_Interp* interp,
+        int objc, Tcl_Obj* CONST objv[]) {
+    if (objc != 1) {
+        Tcl_WrongNumArgs(interp, 1, objv, NULL);
+        return TCL_ERROR;
+    } else {
+        sqlite3* db = registry_db(interp, 1);
+        if (db == NULL) {
+            return TCL_ERROR;
+        } else {
+            reg_error error;
+            if (registry_tcl_detach(interp, db, &error)) {
+                return TCL_OK;
+            }
+            return registry_failed(interp, &error);
+        }
+    }
+    return TCL_ERROR;
+}
+
+/**
+ * Initializer for the registry lib.
+ *
+ * This function is called automatically by Tcl upon loading of registry.dylib.
+ * It creates the global commands made available in the registry namespace.
+ */
+int Registry_Init(Tcl_Interp* interp) {
+    if (Tcl_InitStubs(interp, "8.3", 0) == NULL) {
+        return TCL_ERROR;
+    }
+    Tcl_CreateObjCommand(interp, "registry::open", registry_open, NULL,
+            NULL);
+    Tcl_CreateObjCommand(interp, "registry::close", registry_close, NULL,
+            NULL);
+    /* Tcl_CreateObjCommand(interp, "registry::graph", GraphCmd, NULL, NULL); */
+    /* Tcl_CreateObjCommand(interp, "registry::item", item_cmd, NULL, NULL); */
+    Tcl_CreateObjCommand(interp, "registry::entry", entry_cmd, NULL, NULL);
+    if (Tcl_PkgProvide(interp, "registry", "2.0") != TCL_OK) {
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}

Added: trunk/base/src/registry2.0/registry.h
===================================================================
--- trunk/base/src/registry2.0/registry.h	                        (rev 0)
+++ trunk/base/src/registry2.0/registry.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,42 @@
+/*
+ * itemobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _REGISTRY_H
+#define _REGISTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+#include <centry.h>
+
+sqlite3* registry_db(Tcl_Interp* interp, int attached);
+int registry_failed(Tcl_Interp* interp, reg_error* errPtr);
+
+#endif /* _REGISTRY_H */

Added: trunk/base/src/registry2.0/sql.c
===================================================================
--- trunk/base/src/registry2.0/sql.c	                        (rev 0)
+++ trunk/base/src/registry2.0/sql.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,300 @@
+/*
+ * sql.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "creg.h"
+#include "util.h"
+
+/**
+ * Executes a null-terminated list of queries.
+ *
+ * Pass it a list of queries, it'll execute them. This is mainly intended for
+ * initialization, when you have a number of standard queries to execute.
+ */
+int do_queries(sqlite3* db, char** queries, reg_error* errPtr) {
+    char** query;
+    for (query = queries; *query != NULL; query++) {
+        sqlite3_stmt* stmt;
+        if ((sqlite3_prepare(db, *query, -1, &stmt, NULL) != SQLITE_OK)
+                || (sqlite3_step(stmt) != SQLITE_DONE)) {
+            reg_sqlite_error(db, errPtr, *query);
+            sqlite3_finalize(stmt);
+            return 0;
+        }
+        sqlite3_finalize(stmt);
+    }
+    return 1;
+}
+
+/**
+ * REGEXP function for sqlite3.
+ *
+ * Takes two arguments; the first is the value and the second the pattern. If
+ * the pattern is invalid, errors out. Otherwise, returns true if the value
+ * matches the pattern and false otherwise.
+ *
+ * This function is available in sqlite3 as the REGEXP operator.
+ */
+static void sql_regexp(sqlite3_context* context, int argc UNUSED,
+        sqlite3_value** argv) {
+    const char* value = sqlite3_value_text(argv[0]);
+    const char* pattern = sqlite3_value_text(argv[1]);
+    switch (Tcl_RegExpMatch(NULL, value, pattern)) {
+        case 0:
+            sqlite3_result_int(context, 0);
+            break;
+        case 1:
+            sqlite3_result_int(context, 1);
+            break;
+        case -1:
+            sqlite3_result_error(context, "invalid pattern", -1);
+            break;
+    }
+}
+
+/**
+ * NOW function for sqlite3.
+ *
+ * Takes no arguments. Returns the unix timestamp of now.
+ */
+static void sql_now(sqlite3_context* context, int argc UNUSED,
+        sqlite3_value** argv UNUSED) {
+    sqlite3_result_int(context, time(NULL));
+}
+
+static int rpm_vercomp (const char *versionA, int lengthA, const char *versionB,
+        int lengthB) {
+    const char *endA, *endB;
+	const char *ptrA, *ptrB;
+	const char *eptrA, *eptrB;
+
+    if (lengthA < 0)
+        lengthA = strlen(versionA);
+    if (lengthB < 0)
+        lengthB = strlen(versionB);
+
+	/* if versions equal, return zero */
+	if(lengthA == lengthB && !strncmp(versionA, versionB, lengthA))
+		return 0;
+
+	ptrA = versionA;
+	ptrB = versionB;
+    endA = versionA + lengthA;
+    endB = versionB + lengthB;
+	while (ptrA != endA && ptrB != endB) {
+		/* skip all non-alphanumeric characters */
+		while (ptrA != endB && !isalnum(*ptrA))
+			ptrA++;
+		while (ptrB != endB && !isalnum(*ptrB))
+			ptrB++;
+
+		eptrA = ptrA;
+		eptrB = ptrB;
+
+		/* Somewhat arbitrary rules as per RPM's implementation.
+		 * This code could be more clever, but we're aiming
+		 * for clarity instead. */
+
+		/* If versionB's segment is not a digit segment, but
+		 * versionA's segment IS a digit segment, return 1.
+		 * (Added for redhat compatibility. See redhat bugzilla
+		 * #50977 for details) */
+		if (!isdigit(*ptrB)) {
+			if (isdigit(*ptrA))
+				return 1;
+		}
+
+		/* Otherwise, if the segments are of different types,
+		 * return -1 */
+
+		if ((isdigit(*ptrA) && isalpha(*ptrB)) || (isalpha(*ptrA) && isdigit(*ptrB)))
+			return -1;
+
+		/* Find the first segment composed of entirely alphabetical
+		 * or numeric members */
+		if (isalpha(*ptrA)) {
+			while (eptrA != endA && isalpha(*eptrA))
+				eptrA++;
+
+			while (eptrB != endB && isalpha(*eptrB))
+				eptrB++;
+		} else {
+			int countA = 0, countB = 0;
+			while (eptrA != endA && isdigit(*eptrA)) {
+				countA++;
+				eptrA++;
+			}
+			while (eptrB != endB && isdigit(*eptrB)) {
+				countB++;
+				eptrB++;
+			}
+
+			/* skip leading '0' characters */
+			while (ptrA != eptrA && *ptrA == '0') {
+				ptrA++;
+				countA--;
+			}
+			while (ptrB != eptrB && *ptrB == '0') {
+				ptrB++;
+				countB--;
+			}
+
+			/* If A is longer than B, return 1 */
+			if (countA > countB)
+				return 1;
+
+			/* If B is longer than A, return -1 */
+			if (countB > countA)
+				return -1;
+		}
+		/* Compare strings lexicographically */
+		while (ptrA != eptrA && ptrB != eptrB && *ptrA == *ptrB) {
+				ptrA++;
+				ptrB++;
+		}
+		if (ptrA != eptrA && ptrB != eptrB)
+			return *ptrA - *ptrB;
+
+		ptrA = eptrA;
+		ptrB = eptrB;
+	}
+
+	/* If both pointers are null, all alphanumeric
+	 * characters were identical and only seperating
+	 * characters differed. According to RPM, these
+	 * version strings are equal */
+	if (ptrA == endA && ptrB == endB)
+		return 0;
+
+	/* If A has unchecked characters, return 1
+	 * Otherwise, if B has remaining unchecked characters,
+	 * return -1 */
+	if (ptrA != endA)
+		return 1;
+	else
+		return -1;
+}
+
+/**
+ * VERSION collation for sqlite3.
+ *
+ * This function collates text according to pextlib's rpm-vercomp function. This
+ * allows direct comparison and sorting of version columns, such as port.version
+ * and port.revision.
+ *
+ * TODO: share rpm-vercomp properly with pextlib. Currently it's copy-pasted in.
+ */
+static int sql_version(void* userdata UNUSED, int alen, const void* a, int blen,
+        const void* b) {
+    return rpm_vercomp((const char*)a, alen, (const char*)b, blen);
+}
+
+/**
+ * Creates tables in the registry.
+ *
+ * This function is called upon an uninitialized database to create the tables
+ * needed to record state between invocations of `port`.
+ */
+int create_tables(sqlite3* db, reg_error* errPtr) {
+    static char* queries[] = {
+        "BEGIN",
+
+        /* metadata table */
+        "CREATE TABLE registry.metadata (key UNIQUE, value)",
+        "INSERT INTO registry.metadata (key, value) VALUES ('version', 1.000)",
+        "INSERT INTO registry.metadata (key, value) VALUES ('created', NOW())",
+
+        /* ports table */
+        "CREATE TABLE registry.ports ("
+            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+            "name, portfile, url, location, epoch, version COLLATE VERSION, "
+            "revision COLLATE VERSION, variants, state, date, "
+            "UNIQUE (name, epoch, version, revision, variants), "
+            "UNIQUE (url, epoch, version, revision, variants)"
+            ")",
+        "CREATE INDEX registry.port_name ON ports "
+            "(name, epoch, version, revision, variants)",
+        "CREATE INDEX registry.port_url ON ports "
+            "(url, epoch, version, revision, variants)",
+        "CREATE INDEX registry.port_state ON ports (state)",
+
+        /* file map */
+        "CREATE TABLE registry.files (id, path UNIQUE, mtime)",
+        "CREATE INDEX registry.file_port ON files (id)",
+
+        "END",
+        NULL
+    };
+    return do_queries(db, queries, errPtr);
+}
+
+/**
+ * Initializes database connection.
+ *
+ * This function creates all the temporary tables used by the registry. It also
+ * registers the user functions and collations declared in "sql.h", making them
+ * available.
+ */
+int init_db(sqlite3* db, reg_error* errPtr) {
+    static char* queries[] = {
+        "BEGIN",
+
+        /* items cache */
+        "CREATE TEMPORARY TABLE items (refcount, proc UNIQUE, name, url, path, "
+            "worker, options, variants)",
+
+        /* indexes list */
+        "CREATE TEMPORARY TABLE indexes (file, name, attached)",
+
+        /* entry addresses */
+        "CREATE TEMPORARY TABLE entries (id, address)",
+
+        "END",
+        NULL
+    };
+
+    /* I'm not error-checking these. I don't think I need to. */
+    sqlite3_create_function(db, "REGEXP", 2, SQLITE_UTF8, NULL, sql_regexp,
+            NULL, NULL);
+    sqlite3_create_function(db, "NOW", 0, SQLITE_ANY, NULL, sql_now, NULL,
+            NULL);
+
+    sqlite3_create_collation(db, "VERSION", SQLITE_UTF8, NULL, sql_version);
+
+    return do_queries(db, queries, errPtr);
+}
+

Added: trunk/base/src/registry2.0/sql.h
===================================================================
--- trunk/base/src/registry2.0/sql.h	                        (rev 0)
+++ trunk/base/src/registry2.0/sql.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,42 @@
+/*
+ * sql.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _SQL_H
+#define _SQL_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+
+#include "creg.h"
+
+int create_tables(sqlite3* db, reg_error* errPtr);
+int init_db(sqlite3* db, reg_error* errPtr);
+
+#endif /* _SQL_H */

Added: trunk/base/src/registry2.0/tests/common.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/common.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/tests/common.tcl	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,51 @@
+# Common functions for test cases
+
+proc test {condition} {
+    uplevel 1 "\
+        puts -nonewline {checking if $condition... }
+        if {\[catch {
+                if {$condition} { \n\
+                    puts yes
+                } else { \n\
+                    puts no \n\
+                    exit 1 \n\
+                } \n\
+            } msg\]} { \n\
+                puts \"caught error: \$msg\" \n\
+                exit 1 \n\
+            }"
+}
+
+proc test_equal {statement value} {
+    uplevel 1 "\
+        puts -nonewline {checking if $statement == \"$value\"... }
+        if {\[catch {
+                set actual $statement
+                if {\$actual == \[subst {$value}\]} { \n\
+                    puts yes
+                } else { \n\
+                    puts \"no (was \$actual)\" \n\
+                    exit 1 \n\
+                } \n\
+            } msg\]} { \n\
+                puts \"caught error: \$msg\" \n\
+                exit 1 \n\
+            }"
+}
+
+proc test_throws {statement error} {
+    uplevel 1 "\
+        puts -nonewline {checking if $statement throws $error... }
+        if {\[catch {$statement} error\]} { \n\
+            if {\$::errorCode == {$error}} {
+                puts yes
+            } else {
+                puts \"no (threw \$::errorCode instead)\" \n\
+                exit 1 \n\
+            } \n\
+        } else { \n\
+            puts {no (did not throw)} \n\
+            exit 1 \n\
+        }"
+}
+

Added: trunk/base/src/registry2.0/tests/entry.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/entry.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/tests/entry.tcl	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,99 @@
+# Test file for registry::item
+# Syntax:
+# tclsh item.tcl <Pextlib name>
+
+proc main {pextlibname} {
+    load $pextlibname
+
+	file delete -force test.db
+
+    # can't use registry before it's opened
+    test_throws {registry::entry search} registry::not-open
+    registry::open test.db
+
+    # create some (somewhat contrived) ports to play with
+    set vim1 [registry::entry create vim 7.1.000 0 {multibyte +} 0]
+    set vim2 [registry::entry create vim 7.1.002 0 {} 0]
+    set vim3 [registry::entry create vim 7.1.002 0 {multibyte +} 0]
+    set zlib [registry::entry create zlib 1.2.3 1 {} 0]
+    set pcre [registry::entry create pcre 7.1 1 {utf8 +} 0]
+
+    # check that their properties can be set
+    $vim1 state installed
+    $vim2 state installed
+    $vim3 state active
+    $zlib state active
+    $pcre state installed
+
+    # check that their properties can be retrieved
+    test_equal {[$vim1 name]} vim
+    test_equal {[$vim2 epoch]} 0
+    test_equal {[$vim3 version]} 7.1.002
+    test_equal {[$zlib revision]} 1
+    test_equal {[$pcre variants]} {utf8 +}
+    
+    set installed [registry::entry installed]
+    set active [registry::entry active]
+
+    # check that installed and active give correct results
+    # have to sort these because their orders aren't defined
+    test_equal {[lsort $installed]} {[lsort "$vim1 $vim2 $vim3 $zlib $pcre"]}
+    test_equal {[lsort $active]} {[lsort "$vim3 $zlib"]}
+
+    # try mapping files and checking their owners
+    $vim3 map
+    $vim3 map /opt/local/bin/vim
+    $vim3 map /opt/local/bin/vimdiff /opt/local/bin/vimtutor
+    test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {$vim3}
+    test_equal {[registry::entry owner /opt/local/bin/emacs]} {}
+
+    test_equal {[$vim3 files]} {/opt/local/bin/vim /opt/local/bin/vimdiff /opt/local/bin/vimtutor}
+    test_equal {[$zlib files]} {}
+
+    # try unmapping and remapping
+    $vim3 unmap /opt/local/bin/vim
+    test_equal {[registry::entry owner /opt/local/bin/vim]} {}
+    $vim3 map /opt/local/bin/vim
+    test_equal {[registry::entry owner /opt/local/bin/vim]} {$vim3}
+
+    # make sure you can't map an already-owned file or unmap one you don't
+    test_throws {$zlib map /opt/local/bin/vim} registry::already-owned
+    test_throws {$zlib unmap /opt/local/bin/vim} registry::not-owned
+    test_throws {$zlib unmap /opt/local/bin/emacs} registry::not-owned
+
+    # delete pcre
+    test_equal {[registry::entry installed pcre]} {$pcre}
+    registry::entry delete $pcre
+    test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+    test {![registry::entry exists $pcre]}
+
+    # close vim1
+    test {[registry::entry exists $vim1]}
+    registry::entry close $vim1
+    test {![registry::entry exists $vim1]}
+
+    # close the registry; make sure the registry isn't usable after being closed
+    # and ensure state persists between open sessions
+    registry::close
+    test_throws {registry::entry search} registry::not-open
+    test {![registry::entry exists $vim3]}
+    registry::open test.db
+
+    # check that the same vim is active from before
+    set vim3 [registry::entry active vim]
+    test_equal {[$vim3 version]} 7.1.002
+
+    # find the zlib we inserted before
+    set zlib [registry::entry open zlib 1.2.3 1 {} 0]
+    test {[registry::entry exists $zlib]}
+
+    # check that pcre is gone
+    test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+
+    registry::close
+
+	file delete -force test.db
+}
+
+source tests/common.tcl
+main $argv

Added: trunk/base/src/registry2.0/tests/item.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/item.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/tests/item.tcl	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,51 @@
+# Test file for registry::item
+# Syntax:
+# tclsh item.tcl <Pextlib name>
+
+proc main {pextlibname} {
+    load $pextlibname
+
+    set aesc [registry::item create]
+    set wynn [registry::item create]
+    set eth [registry::item create]
+    set thorn [registry::item create]
+
+    test {[registry::item exists $aesc]}
+    test {![registry::item exists kumquat]}
+    test {![registry::item exists string]}
+
+    $aesc key name aesc
+    $wynn key name wynn
+    $eth key name eth
+    $thorn key name thorn
+
+    test_equal {[$aesc key name]} "aesc"
+    test_equal {[$thorn key name]} "thorn"
+
+    $aesc key variants {}
+    $wynn key variants {}
+    $eth key variants {{big +} {small -}}
+    $thorn key variants {{big +} {small -}}
+
+	test_equal {[registry::item search {name aesc}]} "$aesc"
+    test_equal {[registry::item search {variants {}}]} "$aesc $wynn"
+    test_equal {[registry::item search {variants {{big +}}}]} ""
+    test_equal {[registry::item search {variants {{big +} {small -}}}]} "$eth $thorn"
+	test_equal {[registry::item search {name wynn} {variants {}}]} "$wynn"
+
+	$aesc release
+	$wynn retain
+	$wynn release
+
+	test {![registry::item exists $aesc]}
+	test {[registry::item exists $wynn]}
+
+	$wynn release
+
+	test {![registry::item exists $wynn]}
+
+	file delete -force test.db
+}
+
+source tests/common.tcl
+main $argv

Added: trunk/base/src/registry2.0/util.c
===================================================================
--- trunk/base/src/registry2.0/util.c	                        (rev 0)
+++ trunk/base/src/registry2.0/util.c	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,270 @@
+/*
+ * util.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+
+#include "util.h"
+
+/**
+ * Generates a unique proc name starting with prefix.
+ *
+ * This function loops through the integers trying to find a name
+ * "<prefix><int>" such that no command with that name exists within the given
+ * Tcl interp context. This behavior is similar to that of the builtin
+ * `interp create` command, and is intended to generate names for created
+ * objects of a similar nature.
+ *
+ * TODO: add a int* parameter so that functions which need large numbers of
+ * unique names can keep track of the lower bound between calls,thereby turning
+ * N^2 to N. It'll be alchemy for the 21st century.
+ */
+char* unique_name(Tcl_Interp* interp, char* prefix) {
+    char* result = malloc(strlen(prefix) + TCL_INTEGER_SPACE + 1);
+    Tcl_CmdInfo info;
+    int i;
+    for (i=0; ; i++) {
+        sprintf(result, "%s%d", prefix, i);
+        if (Tcl_GetCommandInfo(interp, result, &info) == 0) {
+            break;
+        }
+    }
+    return result;
+}
+
+/**
+ * Parses flags given to a Tcl command.
+ *
+ * Starting at `objv[start]`, this function will loop through the remaining
+ * arguments until a non-flag argument is found, or an END_FLAGS flag is found,
+ * or an invalid flag is found. In the first two cases, TCL_OK will be returned
+ * and `start` will be moved to the first non-flag argument; in the third,
+ * TCL_ERROR will be returned.
+ *
+ * It is recommended that all callers of this function include the entry
+ * `{ "--", END_FLAGS }` in the NULL-terminated list `options`. For other,
+ * non-zero flag values in `options`, flags will be bitwise or'ed by that value.
+ *
+ * Note that `alpha -beta gamma -delta epsilon` will be recognized as three
+ * arguments following one flag. This could be changed but would make things
+ * much more difficult.
+ *
+ * TODO: support flags of the form ?-flag value?. No functions currently have a
+ * use for this yet, so it's not a priority, but it should be there for
+ * completeness.
+ */
+int parse_flags(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[], int* start,
+        option_spec options[], int* flags) {
+    int i;
+    int index;
+    *flags = 0;
+    for (i=*start; i<objc; i++) {
+        if (Tcl_GetString(objv[i])[0] != '-') {
+            break;
+        }
+        if (Tcl_GetIndexFromObjStruct(interp, objv[i], options,
+                    sizeof(option_spec), "option", 0, &index) == TCL_OK) {
+            if (options[index].flag == END_FLAGS) {
+                i++;
+                break;
+            } else {
+                *flags |= options[index].flag;
+            }
+        } else {
+            return TCL_ERROR;
+        }
+    }
+    *start = i;
+    return TCL_OK;
+}
+
+/**
+ * Retrieves the object whose proc is named by `name`.
+ *
+ * A common design pattern is to have an object be a proc whose clientData
+ * points to the object and whose function points to an object function. This
+ * function retrieves such an object.
+ *
+ * `proc` is used to verify that a proc names an instance of the object. If not,
+ * `type` is used to construct an appropriate error message before it returns
+ * NULL.
+ */
+void* get_object(Tcl_Interp* interp, char* name, char* type,
+        Tcl_ObjCmdProc* proc, reg_error* errPtr) {
+    Tcl_CmdInfo info;
+    if (Tcl_GetCommandInfo(interp, name, &info) && info.objProc == proc){
+        return info.objClientData;
+    } else {
+        errPtr->code = "registry::not-found";
+        errPtr->description = sqlite3_mprintf("could not find %s \"%s\"", type,
+                name);
+        errPtr->free = (reg_error_destructor*)sqlite3_free;
+        return NULL;
+    }
+}
+
+/**
+ * Sets the object whose proc is named by `name`.
+ *
+ * See the documentation for `get_object`. This function registers such an
+ * object, and additionally requires the `deleteProc` argument, which will be
+ * used to free the object.
+ *
+ * TODO: cause the error used here not to leak memory. This probably needs to be
+ *       addressed as a generic "reg_error_free" routine
+ */
+int set_object(Tcl_Interp* interp, char* name, void* value, char* type,
+        Tcl_ObjCmdProc* proc, Tcl_CmdDeleteProc* deleteProc, reg_error* errPtr){
+    Tcl_CmdInfo info;
+    if (Tcl_GetCommandInfo(interp, name, &info) && info.objProc == proc) {
+        errPtr->code = "registry::duplicate-object";
+        errPtr->description = sqlite3_mprintf("%s named \"%s\" already exists, "
+                "cannot create", type, name);
+        errPtr->free = (reg_error_destructor*)sqlite3_free;
+        return 0;
+    }
+    Tcl_CreateObjCommand(interp, name, proc, value, deleteProc);
+    return 1;
+}
+
+/**
+ * Reports a sqlite3 error to Tcl.
+ *
+ * Queries the database for the most recent error message and sets it as the
+ * result of the given interpreter. If a query is optionally passed, also
+ * records what it was.
+ */
+void set_sqlite_result(Tcl_Interp* interp, sqlite3* db, const char* query) {
+    Tcl_ResetResult(interp);
+    Tcl_SetErrorCode(interp, "registry::sqlite-error", NULL);
+    if (query == NULL) {
+        Tcl_AppendResult(interp, "sqlite error: ", sqlite3_errmsg(db), NULL);
+    } else {
+        Tcl_AppendResult(interp, "sqlite error executing \"", query, "\": ",
+                sqlite3_errmsg(db), NULL);
+    }
+}
+
+/**
+ * Sets the result of the interpreter to all objects returned by a query.
+ *
+ * This function executes `query` on `db` It expects that the query will return
+ * records of a single column, `rowid`. It will then use `prefix` to construct
+ * unique names for these records, and call `setter` to construct their proc
+ * objects. The result of `interp` will be set to a list of all such objects.
+ *
+ * If TCL_OK is returned, then a list is in the result. If TCL_ERROR is, then an
+ * error is there.
+ */
+int all_objects(Tcl_Interp* interp, sqlite3* db, char* query, char* prefix,
+        set_object_function* setter) {
+    sqlite3_stmt* stmt;
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        Tcl_Obj* result = Tcl_NewListObj(0, NULL);
+        Tcl_SetObjResult(interp, result);
+        while (sqlite3_step(stmt) == SQLITE_ROW) {
+            sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
+            char* name = unique_name(interp, prefix);
+            if (setter(interp, name, rowid) == TCL_OK) {
+                Tcl_Obj* element = Tcl_NewStringObj(name, -1);
+                Tcl_ListObjAppendElement(interp, result, element);
+                free(name);
+            } else {
+                free(name);
+                return TCL_ERROR;
+            }
+        }
+        return TCL_OK;
+    } else {
+        sqlite3_free(query);
+        set_sqlite_result(interp, db, query);
+        return TCL_ERROR;
+    }
+    return TCL_ERROR;
+}
+
+int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
+        void** inv, int inc, reg_error* errPtr) {
+    void** result = malloc(inc*sizeof(void*));
+    int i;
+    for (i=0; i<inc; i++) {
+        if (!fn(userdata, &result[i], inv[i], errPtr)) {
+            if (del != NULL) {
+                for ( ; i>=0; i--) {
+                    del(userdata, result[i]);
+                }
+            }
+            free(result);
+            return 0;
+        }
+    }
+    *outv = result;
+    return 1;
+}
+
+static int obj_to_string(void* userdata UNUSED, char** string, Tcl_Obj* obj,
+        reg_error* errPtr UNUSED) {
+    int length;
+    char* value = Tcl_GetStringFromObj(obj, &length);
+    *string = malloc((length+1)*sizeof(char));
+    memcpy(*string, value, length+1);
+    return 1;
+}
+
+void free_string(void* userdata UNUSED, char* string) {
+    free(string);
+}
+
+int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+        reg_error* errPtr) {
+    return recast(NULL, (cast_function*)obj_to_string,
+            (free_function*)free_string, (void***)strings, (void**)objv, objc,
+            errPtr);
+}
+
+static int string_to_obj(void* userdata UNUSED, Tcl_Obj** obj, char* string,
+        reg_error* errPtr UNUSED) {
+    *obj = Tcl_NewStringObj(string, -1);
+    return 1;
+}
+
+static void free_obj(void* userdata UNUSED, Tcl_Obj* obj) {
+    Tcl_DecrRefCount(obj);
+}
+
+int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+        reg_error* errPtr) {
+    return recast(NULL, (cast_function*)string_to_obj,
+            (free_function*)free_obj, (void***)objv, (void**)strings, objc,
+            errPtr);
+}

Added: trunk/base/src/registry2.0/util.h
===================================================================
--- trunk/base/src/registry2.0/util.h	                        (rev 0)
+++ trunk/base/src/registry2.0/util.h	2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,74 @@
+/*
+ * util.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "centry.h"
+
+typedef struct {
+    char* option;
+    int flag;
+} option_spec;
+
+#define END_FLAGS 0
+
+char* unique_name(Tcl_Interp* interp, char* prefix);
+
+int parse_flags(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[], int* start,
+        option_spec options[], int* flags);
+
+void* get_object(Tcl_Interp* interp, char* name, char* type,
+        Tcl_ObjCmdProc* proc, reg_error* errPtr);
+int set_object(Tcl_Interp* interp, char* name, void* value, char* type,
+        Tcl_ObjCmdProc* proc, Tcl_CmdDeleteProc* deleteProc, reg_error* errPtr);
+
+void set_sqlite_result(Tcl_Interp* interp, sqlite3* db, const char* query);
+
+typedef int set_object_function(Tcl_Interp* interp, char* name,
+        sqlite_int64 rowid);
+int all_objects(Tcl_Interp* interp, sqlite3* db, char* query, char* prefix,
+        set_object_function* setter);
+
+int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
+        void** inv, int inc, reg_error* errPtr);
+
+void free_strings(void* userdata UNUSED, char** strings, int count);
+
+int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+        reg_error* errPtr);
+int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+        reg_error* errPtr);
+
+#endif /* _UTIL_H */

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070806/0281b34a/attachment.html


More information about the macports-changes mailing list