-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: start of modrinth commands, basic search only for now
- Loading branch information
Showing
10 changed files
with
593 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.