Skip to content

Commit

Permalink
feat: start of modrinth commands, basic search only for now
Browse files Browse the repository at this point in the history
  • Loading branch information
mworzala committed Oct 3, 2024
1 parent bd6b721 commit 6b64e9f
Show file tree
Hide file tree
Showing 10 changed files with 593 additions and 32 deletions.
17 changes: 17 additions & 0 deletions cmd/mc/modrinth/modrinth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package modrinth

import (
"github.com/mworzala/mc/internal/pkg/cli"
"github.com/spf13/cobra"
)

func NewModrinthCmd(app *cli.App) *cobra.Command {
cmd := &cobra.Command{
Use: "modrinth",
Short: "Query the modrinth API directly",
}

cmd.AddCommand(newSearchCmd(app))

return cmd
}
101 changes: 101 additions & 0 deletions cmd/mc/modrinth/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package modrinth

import (
"context"
"os"
"os/signal"
"strings"

"github.com/mworzala/mc/internal/pkg/cli"
appModel "github.com/mworzala/mc/internal/pkg/cli/model"
"github.com/mworzala/mc/internal/pkg/modrinth"
"github.com/mworzala/mc/internal/pkg/modrinth/facet"
"github.com/spf13/cobra"
)

type searchOpts struct {
app *cli.App

// Project type
mod bool
modPack bool
resourcePack bool
shader bool

// Sort
//todo
}

func newSearchCmd(app *cli.App) *cobra.Command {
var o searchOpts

cmd := &cobra.Command{
Use: "search",
Short: "Search for projects on modrinth",
Args: func(cmd *cobra.Command, args []string) error {
o.app = app
return o.validateArgs(cmd, args)
},
RunE: func(_ *cobra.Command, args []string) error {
o.app = app
return o.execute(args)
},
}

cmd.Flags().BoolVar(&o.mod, "mod", false, "Show only mods")
cmd.Flags().BoolVar(&o.modPack, "modpack", false, "Show only modpacks")
cmd.Flags().BoolVar(&o.resourcePack, "resourcepack", false, "Show only resource packs")
cmd.Flags().BoolVar(&o.resourcePack, "rp", false, "Show only resource packs")
cmd.Flags().BoolVar(&o.shader, "shader", false, "Show only shaders")

cmd.Flags().FlagUsages()

return cmd
}

func (o *searchOpts) validateArgs(cmd *cobra.Command, args []string) (err error) {
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}

// todo
return nil
}

func (o *searchOpts) execute(args []string) error {
// Validation function has done arg validation and option population

client := modrinth.NewClient(o.app.Build.Version)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

query := strings.Join(args, " ")
var facets facet.And
if o.mod || o.modPack || o.resourcePack || o.shader {
var filter facet.Or
if o.mod {
filter = append(filter, facet.Eq{facet.ProjectType, "mod"})
}
if o.modPack {
filter = append(filter, facet.Eq{facet.ProjectType, "modpack"})
}
if o.resourcePack {
filter = append(filter, facet.Eq{facet.ProjectType, "resourcepack"})
}
if o.shader {
filter = append(filter, facet.Eq{facet.ProjectType, "shader"})
}
facets = append(facets, filter)
}

res, err := client.Search(ctx, modrinth.SearchRequest{
Query: query,
Facets: facets,
})
if err != nil {
return err
}

presentable := appModel.ModrinthSearchResult(*res)
return o.app.Present(&presentable)
}
2 changes: 2 additions & 0 deletions cmd/mc/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mc

import (
"github.com/MakeNowJust/heredoc"
"github.com/mworzala/mc/cmd/mc/modrinth"

"github.com/mworzala/mc/cmd/mc/profile"

Expand Down Expand Up @@ -37,6 +38,7 @@ func NewRootCmd(app *cli.App) *cobra.Command {
cmd.AddCommand(profile.NewProfileCmd(app))
cmd.AddCommand(newLaunchCmd(app))
cmd.AddCommand(newInstallCmd(app))
cmd.AddCommand(modrinth.NewModrinthCmd(app))
cmd.AddCommand(newVersionCmd(app))
cmd.AddCommand(newDebugCmd(app))

Expand Down
24 changes: 24 additions & 0 deletions internal/pkg/cli/model/modrinth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package model

import (
"fmt"

"github.com/gosuri/uitable"
"github.com/mworzala/mc/internal/pkg/modrinth"
"github.com/mworzala/mc/internal/pkg/util"
)

type ModrinthSearchResult modrinth.SearchResponse

func (result *ModrinthSearchResult) String() string {
table := uitable.New()
table.AddRow("ID", "TYPE", "NAME", "DOWNLOADS")
for _, project := range result.Hits {
table.AddRow(project.ProjectID, project.ProjectType, project.Title, util.FormatCount(project.Downloads))
}
res := table.String()
if result.TotalHits-len(result.Hits) > 0 {
res += fmt.Sprintf("\n...and %d more", result.TotalHits-len(result.Hits))
}
return res
}
215 changes: 215 additions & 0 deletions internal/pkg/modrinth/facet/facet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package facet

import (
"fmt"
"strconv"
"time"
)

func ToString(facet Root) (string, error) {
if facet == nil {
return "", nil
}
return facet.asString(false, false)
}

type And []OrFilter

type Or []Filter

type Eq struct {
Type Type
Value interface{}
}

type NEq struct {
Type Type
Value interface{}
}

type Gt struct {
Type Type
Value interface{}
}

type GtEq struct {
Type Type
Value interface{}
}

type Lt struct {
Type Type
Value interface{}
}

type LtEq struct {
Type Type
Value interface{}
}

type Type string

const (
ProjectType Type = "project_type"
Categories Type = "categories"
Versions Type = "versions"
ClientSide Type = "client_side"
ServerSide Type = "server_side"
OpenSource Type = "open_source"
Title Type = "title"
Author Type = "author"
Follows Type = "follows"
ProjectID Type = "project_id"
License Type = "license"
Downloads Type = "downloads"
Color Type = "color"
CreatedTimestamp Type = "created_timestamp"
ModifiedTimestamp Type = "modified_timestamp"
)

type (
anyFilter interface {
asString(hasAnd, hasOr bool) (string, error)
}
Root interface {
anyFilter
facetRoot()
}
OrFilter interface {
anyFilter
orFilter()
}
Filter interface {
anyFilter
facet()
}
)

func (f And) asString(_, _ bool) (string, error) {
if len(f) == 0 {
return "", nil
}

out := "["
for i, child := range f {
childStr, err := child.asString(true, false)
if err != nil {
return "", err
}
out += childStr
if i != len(f)-1 {
out += ","
}
}

return out + "]", nil
}

func (f Or) asString(hasAnd, _ bool) (string, error) {
if len(f) == 0 {
return "", nil
}

out := "["
for i, child := range f {
childStr, err := child.asString(true, true)
if err != nil {
return "", err
}
out += childStr
if i != len(f)-1 {
out += ","
}
}
out += "]"

if !hasAnd {
out = "[" + out + "]"
}

return out, nil
}

func (f Eq) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, "=", f.Type, f.Value)
}
func (f NEq) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, "!=", f.Type, f.Value)
}
func (f Gt) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, ">", f.Type, f.Value)
}
func (f GtEq) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, ">=", f.Type, f.Value)
}
func (f Lt) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, "<", f.Type, f.Value)
}
func (f LtEq) asString(hasAnd, hasOr bool) (string, error) {
return serializeOperation(hasAnd, hasOr, "<=", f.Type, f.Value)
}

func (And) facetRoot() {}
func (Or) facetRoot() {}
func (Eq) facetRoot() {}
func (NEq) facetRoot() {}
func (Gt) facetRoot() {}
func (GtEq) facetRoot() {}
func (Lt) facetRoot() {}
func (LtEq) facetRoot() {}

func (Or) orFilter() {}
func (Eq) orFilter() {}
func (NEq) orFilter() {}
func (Gt) orFilter() {}
func (GtEq) orFilter() {}
func (Lt) orFilter() {}
func (LtEq) orFilter() {}

func (Or) facet() {}
func (Eq) facet() {}
func (NEq) facet() {}
func (Gt) facet() {}
func (GtEq) facet() {}
func (Lt) facet() {}
func (LtEq) facet() {}

func serializeOperation(hasAnd, hasOr bool, op string, _type Type, value interface{}) (string, error) {
valueStr, err := serializeValue(value)
if err != nil {
return "", err
}

out := `"` + string(_type) + op + valueStr + `"`

if !hasAnd {
out = "[" + out + "]"
}
if !hasOr {
out = "[" + out + "]"
}
return out, nil
}

func serializeValue(value interface{}) (string, error) {
switch v := value.(type) {
case bool:
if v {
return "true", nil
} else {
return "false", nil
}
case string:
return v, nil
case int:
return strconv.Itoa(v), nil
case int64:
return strconv.FormatInt(v, 10), nil
case float64:
return strconv.FormatFloat(v, 'g', -1, 64), nil
case time.Time:
return v.Format(time.RFC3339), nil
default:
return "", fmt.Errorf("unexpected facet value type %T", v)
}
}
Loading

0 comments on commit 6b64e9f

Please sign in to comment.