From 8a3da4c5fe2c8e5b67edb92e6e115dd01940822c Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 2 May 2025 11:33:23 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20ic-merge=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Still needs some work, like saving to a new file, but works well so far. --- cmd/ic-merge/main.go | 34 ++++ go.mod | 9 + go.sum | 28 +++ infinitecraft/errors.go | 5 + infinitecraft/game.go | 10 ++ infinitecraft/item.go | 9 + infinitecraft/merger.go | 379 ++++++++++++++++++++++++++++++++++++++++ infinitecraft/new.go | 25 +++ 8 files changed, 499 insertions(+) create mode 100644 cmd/ic-merge/main.go create mode 100644 infinitecraft/errors.go create mode 100644 infinitecraft/game.go create mode 100644 infinitecraft/item.go create mode 100644 infinitecraft/merger.go create mode 100644 infinitecraft/new.go diff --git a/cmd/ic-merge/main.go b/cmd/ic-merge/main.go new file mode 100644 index 0000000..1be90ef --- /dev/null +++ b/cmd/ic-merge/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "encoding/json" + "os" + + c "codeberg.org/danjones000/utils/cli/context" + e "codeberg.org/danjones000/utils/cli/err" + ic "codeberg.org/danjones000/utils/infinitecraft" +) + +func main() { + ctx, done := c.SelfCancelingContext(context.Background()) + defer done() + + m, err := ic.NewMerger(os.Args[1:]) + e.HandleErr(err) + defer m.Close() + + err = m.ParseFiles() + e.HandleErr(err) + err = m.ReadData(ctx) + e.HandleErr(err) + + g, err := m.Merge(ctx) + e.HandleErr(err) + + jsout := json.NewEncoder(os.Stdout) + jsout.SetIndent("", "\t") + err = jsout.Encode(g) + e.HandleErr(err) + // */ +} diff --git a/go.mod b/go.mod index 7dca67b..f0711b6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( codeberg.org/danjones000/ezcache v0.5.2 github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbletea v1.1.1 + github.com/glebarez/go-sqlite v1.22.0 + github.com/jmoiron/sqlx v1.4.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 ) @@ -15,7 +17,9 @@ require ( github.com/charmbracelet/lipgloss v0.13.0 // indirect github.com/charmbracelet/x/ansi v0.2.3 // indirect github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/google/uuid v1.5.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -23,8 +27,13 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.3.8 // indirect + modernc.org/libc v1.37.6 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/sqlite v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index 8361db0..0a91682 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ codeberg.org/danjones000/ezcache v0.5.2 h1:msWRwbLj+HHCU3dnFbsdedCRuhyPQCOFbT9EgjwN9KM= codeberg.org/danjones000/ezcache v0.5.2/go.mod h1:wLTvTXfXxaEDUS9dOILqQmhAlB/9lUZCC2yB5CyGBfk= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= @@ -14,8 +16,22 @@ github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4h github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -24,6 +40,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -32,6 +50,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -51,3 +71,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/infinitecraft/errors.go b/infinitecraft/errors.go new file mode 100644 index 0000000..a61b6e3 --- /dev/null +++ b/infinitecraft/errors.go @@ -0,0 +1,5 @@ +package infinitecraft + +import "errors" + +var ErrNotParsed = errors.New("not yet parsed") diff --git a/infinitecraft/game.go b/infinitecraft/game.go new file mode 100644 index 0000000..6a4c729 --- /dev/null +++ b/infinitecraft/game.go @@ -0,0 +1,10 @@ +package infinitecraft + +type Game struct { + Name string `json:"name"` + Version string `json:"version"` + Created uint64 `json:"created"` + Updated uint64 `json:"updated"` + Instances []any `json:"instances" db:"-"` + Items []Item `json:"items" db:"-"` +} diff --git a/infinitecraft/item.go b/infinitecraft/item.go new file mode 100644 index 0000000..c805525 --- /dev/null +++ b/infinitecraft/item.go @@ -0,0 +1,9 @@ +package infinitecraft + +type Item struct { + Id int32 `json:"id"` + Text string `json:"text" db:"name"` + Emoji string `json:"emoji"` + Discovery bool `json:"discovery,omitempty"` + Recipes [][]int32 `json:"recipes,omitempty" db:"-"` +} diff --git a/infinitecraft/merger.go b/infinitecraft/merger.go new file mode 100644 index 0000000..dd70295 --- /dev/null +++ b/infinitecraft/merger.go @@ -0,0 +1,379 @@ +package infinitecraft + +import ( + "compress/gzip" + "context" + "database/sql" + "encoding/json" + "errors" + "os" + + _ "github.com/glebarez/go-sqlite" + "github.com/jmoiron/sqlx" +) + +type Merger struct { + files []*os.File + games []Game + parsed bool + db *sqlx.DB + gameMap map[int64]Game + itemMap map[int64]Item +} + +func (m *Merger) Games() map[int64]Game { + return m.gameMap +} + +func (m *Merger) Items() map[int64]Item { + return m.itemMap +} + +func (m *Merger) Close() error { + errs := make([]error, 0, len(m.files)+1) + for _, f := range m.files { + errs = append(errs, f.Close()) + } + errs = append(errs, m.db.Close()) + + return errors.Join(errs...) +} + +func (m *Merger) ParseFiles() error { + m.games = make([]Game, len(m.files)) + errs := make([]error, len(m.files)) + for idx, f := range m.files { + unz, err := gzip.NewReader(f) + if err != nil { + errs[idx] = err + continue + } + dec := json.NewDecoder(unz) + errs[idx] = dec.Decode(&(m.games[idx])) + } + err := errors.Join(errs...) + if err == nil { + m.parsed = true + } + return err +} + +const schema string = ` +CREATE TABLE IF NOT EXISTS games ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + version TEXT NOT NULL, + created INTEGER NOT NULL, + updated INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS items ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + emoji TEXT NOT NULL, + discovery BOOLEAN NOT NULL +); + +CREATE TABLE IF NOT EXISTS games_items ( + id INTEGER PRIMARY KEY, + game_id INTEGER NOT NULL, + item_id INTEGER NOT NULL, + orig_id INTEGER NOT NULL, + FOREIGN KEY(game_id) REFERENCES games(id), + FOREIGN KEY(item_id) REFERENCES items(id) +); + +CREATE TABLE IF NOT EXISTS recipes ( + id INTEGER PRIMARY KEY, + item_id INTEGER NOT NULL, + FOREIGN KEY(item_id) REFERENCES items(id) +); + +CREATE TABLE IF NOT EXISTS recipes_items ( + id INTEGER PRIMARY KEY, + recipe_id INTEGER NOT NULL, + parent_id INTEGER NOT NULL, + FOREIGN KEY(recipe_id) REFERENCES recipes(id), + FOREIGN KEY(parent_id) REFERENCES items(id) +); +` + +// select i.name as text, group_concat(par.name) as ingredients from items i join recipes r on r.item_id = i.id join recipes_items ri on ri.recipe_id = r.id join items par on ri.parent_id = par.id group by ri.recipe_id ; + +func (m *Merger) ReadData(ctx context.Context) (err error) { + //nolint:gocritic // temp stuff + // m.db, err = sqlx.Open("sqlite", "foo.db") + m.db, err = sqlx.Open("sqlite", ":memory:") + if err != nil { + return + } + _, err = m.db.ExecContext(ctx, schema) + if err != nil { + return + } + m.gameMap, err = m.insertGames(ctx) + if err != nil { + return + } + + m.itemMap = make(map[int64]Item) + for gameID, game := range m.gameMap { + var itM map[int64]Item + itM, err = m.insertItems(ctx, gameID, game.Items) + if err != nil { + return + } + for itemID, it := range itM { + m.itemMap[itemID] = it + err = m.insertRecipes(ctx, gameID, itemID, it.Recipes) + if err != nil { + return + } + } + } + + return +} + +func (m *Merger) getGameDetails(ctx context.Context, g *Game) error { + return m.db.GetContext(ctx, g, `SELECT name, version FROM games ORDER BY id ASC LIMIT 1`) +} + +func (m *Merger) getGameCreated(ctx context.Context, date *uint64) error { + return m.db.GetContext(ctx, date, `SELECT MIN(created) FROM games`) +} + +func (m *Merger) getGameUpdated(ctx context.Context, date *uint64) error { + return m.db.GetContext(ctx, date, `SELECT MAX(updated) FROM games`) +} + +func (m *Merger) getItems(ctx context.Context, items *[]Item) error { + return m.db.SelectContext(ctx, items, ` + SELECT + id-1 AS id, + name, + emoji, + discovery + FROM items + `) +} + +func (m *Merger) getRecipes(ctx context.Context, items []Item) (err error) { + var rows *sqlx.Rows + if rows, err = m.db.QueryxContext(ctx, ` + SELECT + r.item_id-1 AS itemid, + ri.recipe_id AS recipeid, + ri.parent_id-1 AS parentid + FROM recipes_items ri + JOIN recipes r ON ri.recipe_id = r.id + `); err != nil { + return + } + + defer rows.Close() + rm := make(map[int32]map[int32][]int32) + var found bool + var recRow struct { + ItemID int32 + RecipeID int32 + ParentID int32 + } + for rows.Next() { + if err = rows.StructScan(&recRow); err != nil { + return + } + if _, found = rm[recRow.ItemID]; !found { + rm[recRow.ItemID] = make(map[int32][]int32) + } + if _, found = rm[recRow.ItemID][recRow.RecipeID]; !found { + rm[recRow.ItemID][recRow.RecipeID] = make([]int32, 0, 2) + } + rm[recRow.ItemID][recRow.RecipeID] = append(rm[recRow.ItemID][recRow.RecipeID], recRow.ParentID) + } + var recs map[int32][]int32 + for idx := range items { + if recs, found = rm[items[idx].Id]; found { + items[idx].Recipes = make([][]int32, 0, len(recs)) + for _, rec := range recs { + items[idx].Recipes = append(items[idx].Recipes, rec) + } + } + } + return +} + +func (m *Merger) Merge(ctx context.Context) (g Game, err error) { + if err = m.getGameDetails(ctx, &g); err != nil { + return + } + if err = m.getGameCreated(ctx, &g.Created); err != nil { + return + } + if err = m.getGameUpdated(ctx, &g.Updated); err != nil { + return + } + if err = m.getItems(ctx, &g.Items); err != nil { + return + } + err = m.getRecipes(ctx, g.Items) + + return +} + +type GameItem struct { + Game string `db:"game"` + GameID int64 `db:"game_id"` + Text string `db:"name"` + Emoji string + Id int64 `db:"orig_id"` +} + +func (m *Merger) GameItems(ctx context.Context) (gits []GameItem, err error) { + err = m.db.SelectContext(ctx, &gits, ` + SELECT + g.name AS game, + g.id AS game_id, + it.name, + it.emoji, + it.id - 1 AS orig_id + FROM games_items gi + JOIN items it ON gi.item_id = it.id + JOIN games g ON gi.game_id = g.id + ORDER BY gi.id ASC + `) + return +} + +func (m *Merger) insertGames(ctx context.Context) (mp map[int64]Game, err error) { + mp = make(map[int64]Game, len(m.games)) + var res sql.Result + var stmt *sqlx.NamedStmt + stmt, err = m.db.PrepareNamedContext(ctx, ` + INSERT INTO games + (name, version, created, updated) + VALUES (:name, :version, :created, :updated) + `) + if err != nil { + return + } + defer stmt.Close() + for _, g := range m.games { + res, err = stmt.ExecContext(ctx, g) + if err != nil { + return + } + var id int64 + id, err = res.LastInsertId() + if err != nil { + return + } + mp[id] = g + } + return +} + +func (m *Merger) insertItems(ctx context.Context, gameID int64, items []Item) (itM map[int64]Item, err error) { + itM = make(map[int64]Item, len(items)) + var stmt *sqlx.NamedStmt + var fetchIDStmt *sqlx.Stmt + var relStmt *sqlx.Stmt + stmt, err = m.db.PrepareNamedContext(ctx, ` + INSERT OR IGNORE INTO items + (name, emoji, discovery) + VALUES (:name, :emoji, :discovery) + ON CONFLICT(name) DO UPDATE SET + emoji=excluded.emoji, + discovery=excluded.discovery + `) + + if err != nil { + return + } + defer stmt.Close() + fetchIDStmt, err = m.db.PreparexContext(ctx, `SELECT id FROM items WHERE name = ?`) + if err != nil { + return + } + defer fetchIDStmt.Close() + relStmt, err = m.db.PreparexContext(ctx, ` + INSERT INTO games_items + (game_id, item_id, orig_id) + VALUES (?, ?, ?) + `) + if err != nil { + return + } + defer relStmt.Close() + for _, it := range items { + _, err = stmt.ExecContext(ctx, it) + if err != nil { + return + } + var id int64 + err = fetchIDStmt.GetContext(ctx, &id, it.Text) + if err != nil { + return + } + _, err = relStmt.ExecContext(ctx, gameID, id, it.Id) + if err != nil { + return + } + itM[id] = it + } + return +} + +func (m *Merger) insertRecipes(ctx context.Context, gameID, itemID int64, recipes [][]int32) (err error) { + var res sql.Result + var insertStmt *sqlx.Stmt + var selStmt *sqlx.Stmt + var insertRecItemStmt *sqlx.Stmt + insertStmt, err = m.db.PreparexContext(ctx, ` + INSERT INTO recipes (item_id) VALUES (?) + `) + if err != nil { + return + } + defer insertStmt.Close() + insertRecItemStmt, err = m.db.PreparexContext(ctx, ` + INSERT INTO recipes_items + (recipe_id, parent_id) + VALUES (?, ?) + `) + if err != nil { + return + } + defer insertRecItemStmt.Close() + selStmt, err = m.db.PreparexContext(ctx, ` + SELECT item_id FROM games_items + WHERE game_id = ? AND orig_id = ? + `) + if err != nil { + return + } + defer selStmt.Close() + for _, recipe := range recipes { + var recipeID int64 + res, err = insertStmt.ExecContext(ctx, itemID) + if err != nil { + return + } + recipeID, err = res.LastInsertId() + if err != nil { + return + } + for _, origID := range recipe { + var parentID int64 + err = selStmt.GetContext(ctx, &parentID, gameID, origID) + if err != nil { + return + } + _, err = insertRecItemStmt.ExecContext(ctx, recipeID, parentID) + if err != nil { + return + } + } + } + return +} diff --git a/infinitecraft/new.go b/infinitecraft/new.go new file mode 100644 index 0000000..c2575ca --- /dev/null +++ b/infinitecraft/new.go @@ -0,0 +1,25 @@ +package infinitecraft + +import ( + "fmt" + "os" +) + +func NewMerger(args []string) (*Merger, error) { + m := Merger{} + var err error + if len(args) < 2 { + //nolint:err113 // Don't care + return nil, fmt.Errorf("expected at lease 2 arguments. Got %d", len(args)) + } + fs := make([]*os.File, len(args)) + for idx, pth := range args { + fs[idx], err = os.Open(pth) + if err != nil { + return nil, err + } + } + m.files = fs + + return &m, nil +}