package main import ( "database/sql" "fmt" "log" "github.com/goccy/go-graphviz/cgraph" _ "github.com/mattn/go-sqlite3" ) func NewMapper(fn string) (AardMapper, error) { var am AardMapper db, err := sql.Open("sqlite3", fn) if err != nil { return am, fmt.Errorf("error opening mapper sqlite3 database: %s\n", err) } am.DB = db return am, nil } type AardMapper struct { DB *sql.DB RoomCache map[string]Room AreaCache map[string]Area } func (am AardMapper) Areas() (map[string]Area, error) { areas := make(map[string]Area) rows, err := am.DB.Query("select uid, name, texture, color, flags from areas;") defer rows.Close() if err != nil { return nil, fmt.Errorf("error selecting areas: %w", err) } for rows.Next() { a := Area{} err := rows.Scan(&a.Uid, &a.Name, &a.Texture, &a.Color, &a.Flags) if err != nil { return nil, fmt.Errorf("error scanning Areas: %d", err) } areas[a.Uid.String] = a } return areas, nil } func (am AardMapper) Area(uid string) (Area, error) { a, ok := am.AreaCache[uid] if ok { return a, nil } a = Area{} row := am.DB.QueryRow("select uid, name, flags, color, texture from areas where uid = :uid") if err := row.Scan(&a.Uid, &a.Name, &a.Flags, &a.Color, &a.Texture); err != nil { if err == sql.ErrNoRows { return a, fmt.Errorf("no area found: %w", err) } } return a, nil } func (am AardMapper) Rooms() ([]Room, error) { var uidCount int uidCountRow := am.DB.QueryRow("select count(uid) from rooms;") uidCountRow.Scan(&uidCount) log.Println("uidCount:", uidCount) rows, err := am.DB.Query("select uid from rooms;") if err != nil { return nil, fmt.Errorf("error querying room UIDs: %w", err) } var uids = make([]string, uidCount) i := 0 for rows.Next() { var uid string if err := rows.Scan(&uid); err != nil { return nil, fmt.Errorf("error querying room %q from uid: %w", uid, err) } uids[i] = uid } rooms := make([]Room, uidCount) for idx, uid := range uids { room, err := am.Room(uid) if err != nil { return rooms, fmt.Errorf("error fetching room: %s", err) } rooms[idx] = room } return rooms, nil } func (am AardMapper) Room(uid string) (Room, error) { r := Room{} row := am.DB.QueryRow("select uid,name,building,info,notes,flags,area,norecall,noportal from rooms where uid = ?", uid) if err := row.Scan(&r.Uid, &r.Name, &r.Building, &r.Info, &r.Notes, &r.Flags, &r.AreaName, &r.Norecall, &r.Noportal); err != nil { if err == sql.ErrNoRows { return r, fmt.Errorf("no room found: %w", err) } log.Println("room found:", r.Uid.String) } return r, nil } func (am AardMapper) Exits(uid string) ([]RoomExit, error) { exits := make([]RoomExit, 0) rows, err := am.DB.Query("select r.uid,r.name,r.building,r.info,r.notes,r.flags,r.area, r.norecall,r.noportal,e.dir,e.fromuid,e.touid,e.level from rooms r INNER JOIN exits e ON r.uid = e.fromuid where uid=:uid;", uid) // rows, err := am.DB.Query("select e.dir,e.fromuid,e.touid,e.level from rooms r INNER JOIN exits e ON r.uid = e.fromuid;") defer rows.Close() if err != nil { return nil, fmt.Errorf("error selecting areas: %w", err) } for rows.Next() { e := RoomExit{} // err := rows.Scan(&e.Dir, &e.Fromuid, &e.Touid, &e.Level) err := rows.Scan(&e.Uid, &e.Name, &e.Building, &e.Info, &e.Notes, &e.Flags, &e.AreaName, &e.Norecall, &e.Noportal, &e.Dir, &e.Fromuid, &e.Touid, &e.Level) if err != nil { return nil, fmt.Errorf("error scanning Areas: %d", err) } exits = append(exits, e) } return exits, nil } func (am AardMapper) Walk(uid string, g *cgraph.Graph) error { originRoom, err := am.Room(uid) if err != nil { return fmt.Errorf("error querying origin room for walk %q: %w", uid, err) } area, err := am.Area(originRoom.AreaName.String) if err != nil { return fmt.Errorf("error querying origin area for walk %q: %w", uid, err) } sg := g.SubGraph("cluster_"+area.Name.String, 1) sg.SetLabel(area.Name.String) originNode, err := sg.CreateNode(originRoom.Uid.String) if err != nil { return fmt.Errorf("error creating origin node for room %s: %w", originRoom.Name.String, err) } sg.SetLabel(originRoom.Name.String) exits, err := am.Exits(originRoom.Uid.String) if err != nil { return fmt.Errorf("error fetching exits for origin room %s: %w", originRoom.Name.String, err) } for _, exit := range exits { destRoom, err := am.Room(exit.Touid.String) if err != nil { return fmt.Errorf("error querying destination room %s: %w", uid, err) } destNode, err := sg.CreateNode(destRoom.Uid.String) destNode.SetLabel(destRoom.Name.String) if err != nil { return fmt.Errorf("error creating destination node for room %s: %w", originRoom.Name.String, err) } // maybe i'll save recursion for later // am.Walk(exit.Touid.String, g) // gotta prevent cycles. check if edge already exists and return? sg.CreateEdge(originRoom.Uid.String+">"+originRoom.Uid.String, originNode, destNode) } return nil }