diff --git a/.gitignore b/.gitignore index 7f4f37ab..2ea9ee15 100644 --- a/.gitignore +++ b/.gitignore @@ -25,15 +25,13 @@ brzaguza.* .vscode/* -src/engines/yandex/site/* -src/engines/etools/site/* -src/engines/swisscows/site/* -src/engines/metager/site/* -src/engines/presearch/site/* +src/engines/*/site/* +!src/engines/_engines_test logdump/*.html log/*.log # go generate *_stringer.go -*_enumer.go \ No newline at end of file +*_enumer.go +*_searcher.go \ No newline at end of file diff --git a/generate/searcher/searcher.go b/generate/searcher/searcher.go new file mode 100644 index 00000000..05d8e34f --- /dev/null +++ b/generate/searcher/searcher.go @@ -0,0 +1,319 @@ +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "go/types" + "log" + "os" + "path" + "strings" + + "golang.org/x/tools/go/packages" +) + +var ( + typeName = flag.String("type", "", "type name; must be set") + output = flag.String("output", "", "output file name; default srcdir/_searcher.go") + trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") + linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") + buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") + packageName = flag.String("packagename", "", "name of the package for generated code; default current package") + enginesImport = flag.String("enginesimport", "github.com/tminaorg/brzaguza/src/engines", "source of the engines import, which is prefixed to imports for consts; default github.com/tminaorg/brzaguza/src/engines") +) + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of searcher:\n") + fmt.Fprintf(os.Stderr, "\tsearcher [flags] -type T [directory]\n") + fmt.Fprintf(os.Stderr, "\tsearcher [flags] -type T files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() +} + +func main() { + log.SetFlags(0) + log.SetPrefix("searcher: ") + flag.Usage = Usage + flag.Parse() + if len(*typeName) == 0 { + flag.Usage() + os.Exit(2) + } + /* ---------------------------------- + //! Should be comma seperated list of type names, currently is only the first type name + ---------------------------------- */ + types := strings.Split(*typeName, ",") + var tags []string + if len(*buildTags) > 0 { + tags = strings.Split(*buildTags, ",") + } + + // We accept either one directory or a list of files. Which do we have? + args := flag.Args() + if len(args) == 0 { + // Default: process whole package in current directory. + args = []string{"."} + } + + // Parse the package once. + var dir string + g := Generator{ + trimPrefix: *trimprefix, + lineComment: *linecomment, + } + + if len(args) == 1 && isDirectoryFatal(args[0]) { + dir = args[0] + } else { + if len(tags) != 0 { + log.Fatal("-tags option applies only to directories, not when files are specified") + } + dir = path.Dir(args[0]) + } + + g.parsePackage(args, tags) + + // Print the header and package clause. + g.Printf("// Code generated by \"searcher %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) + g.Printf("\n") + var pkgName string + if *packageName == "" { + pkgName = g.pkg.name + } else { + pkgName = *packageName + } + g.Printf("package %s", pkgName) + g.Printf("\n") + g.Printf("import \"%s\"\n", *enginesImport) // Used by all methods. + + // Run generate for each type. + for _, typeName := range types { + g.generate(typeName) + } + + // Format the output. + src := g.format() + + // Write to file. + outputName := *output + if outputName == "" { + baseName := fmt.Sprintf("%s_searcher.go", types[0]) + outputName = path.Join(dir, strings.ToLower(baseName)) + } + err := os.WriteFile(outputName, src, 0644) + if err != nil { + log.Fatalf("writing output: %s", err) + } +} + +func (g *Generator) Printf(format string, args ...interface{}) { + fmt.Fprintf(&g.buf, format, args...) +} + +// parsePackage analyzes the single package constructed from the patterns and tags. +// parsePackage exits if there is an error. +func (g *Generator) parsePackage(patterns []string, tags []string) { + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax, + Tests: false, + BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, + Logf: g.logf, + } + pkgs, err := packages.Load(cfg, patterns...) + if err != nil { + log.Fatal(err) + } + if len(pkgs) != 1 { + log.Fatalf("error: %d packages matching %v", len(pkgs), strings.Join(patterns, " ")) + } + g.addPackage(pkgs[0]) +} + +// addPackage adds a type checked Package and its syntax files to the generator. +func (g *Generator) addPackage(pkg *packages.Package) { + g.pkg = &Package{ + name: pkg.Name, + defs: pkg.TypesInfo.Defs, + files: make([]*File, len(pkg.Syntax)), + } + + for i, file := range pkg.Syntax { + g.pkg.files[i] = &File{ + file: file, + pkg: g.pkg, + trimPrefix: g.trimPrefix, + lineComment: g.lineComment, + } + } +} + +// generate produces imports and the NewEngineStarter method for the named type. +func (g *Generator) generate(typeName string) { + values := make(Values, 0, 100) + for _, file := range g.pkg.files { + // Set the state for this run of the walker. + file.typeName = typeName + file.values = nil + if file.file != nil { + ast.Inspect(file.file, file.genDecl) + values = append(values, file.values...) + } + } + + if len(values) == 0 { + log.Fatalf("no values defined for type %s", typeName) + } + + // Generate code for importing engines + for _, v := range values { + if validConst(v) { + g.Printf("import \"%s/%s\"\n", *enginesImport, strings.ToLower(v.originalName)) + } + } + + // Generate code that will fail if the constants change value. + g.Printf("func _() {\n") + g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n") + g.Printf("\t// Re-run the searcher command to generate them again.\n") + g.Printf("\tvar x [1]struct{}\n") + for _, v := range values { + origName := v.originalName + if *packageName != "" { + origName = fmt.Sprintf("%s.%s", g.pkg.name, v.originalName) + } + g.Printf("\t_ = x[%s - (%s)]\n", origName, v.str) + } + g.Printf("}\n") + + g.buildOneRun(values, typeName) +} + +// format returns the gofmt-ed contents of the Generator's buffer. +func (g *Generator) format() []byte { + src, err := format.Source(g.buf.Bytes()) + if err != nil { + // Should never happen, but can arise when developing this code. + // The user can compile the output to see the error. + log.Printf("warning: internal error: invalid Go generated: %s", err) + log.Printf("warning: compile the package to analyze the error") + return g.buf.Bytes() + } + return src +} + +func (v *Value) String() string { + return v.str +} + +// genDecl processes one declaration clause. +func (f *File) genDecl(node ast.Node) bool { + decl, ok := node.(*ast.GenDecl) + if !ok || decl.Tok != token.CONST { + // We only care about const declarations. + return true + } + // The name of the type of the constants we are declaring. + // Can change if this is a multi-element declaration. + typ := "" + // Loop over the elements of the declaration. Each element is a ValueSpec: + // a list of names possibly followed by a type, possibly followed by values. + // If the type and value are both missing, we carry down the type (and value, + // but the "go/types" package takes care of that). + for _, spec := range decl.Specs { + vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. + if vspec.Type == nil && len(vspec.Values) > 0 { + // "X = 1". With no type but a value. If the constant is untyped, + // skip this vspec and reset the remembered type. + typ = "" + + // If this is a simple type conversion, remember the type. + // We don't mind if this is actually a call; a qualified call won't + // be matched (that will be SelectorExpr, not Ident), and only unusual + // situations will result in a function call that appears to be + // a type conversion. + ce, ok := vspec.Values[0].(*ast.CallExpr) + if !ok { + continue + } + id, ok := ce.Fun.(*ast.Ident) + if !ok { + continue + } + typ = id.Name + } + if vspec.Type != nil { + // "X T". We have a type. Remember it. + ident, ok := vspec.Type.(*ast.Ident) + if !ok { + continue + } + typ = ident.Name + } + if typ != f.typeName { + // This is not the type we're looking for. + continue + } + // We now have a list of names (from one line of source code) all being + // declared with the desired type. + // Grab their names and actual values and store them in f.values. + for _, name := range vspec.Names { + if name.Name == "_" { + continue + } + // This dance lets the type checker find the values for us. It's a + // bit tricky: look up the object declared by the name, find its + // types.Const, and extract its value. + obj, ok := f.pkg.defs[name] + if !ok { + log.Fatalf("no value for constant %s", name) + } + info := obj.Type().Underlying().(*types.Basic).Info() + if info&types.IsInteger == 0 { + log.Fatalf("can't handle non-integer constant type %s", typ) + } + value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. + if value.Kind() != constant.Int { + log.Fatalf("can't happen: constant is not an integer %s", name) + } + i64, isInt := constant.Int64Val(value) + u64, isUint := constant.Uint64Val(value) + if !isInt && !isUint { + log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) + } + if !isInt { + u64 = uint64(i64) + } + v := Value{ + originalName: name.Name, + value: u64, + signed: info&types.IsUnsigned == 0, + str: value.String(), + } + if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { + v.name = strings.TrimSpace(c.Text()) + } else { + v.name = strings.TrimPrefix(v.originalName, f.trimPrefix) + } + f.values = append(f.values, v) + } + } + return false +} + +// buildOneRun generates the variables and NewEngineStarter func for a single run of contiguous values. +func (g *Generator) buildOneRun(values Values, typeName string) { + g.Printf("\n") + // The generated code is simple enough to write as a Printf format. + g.Printf("\nfunc NewEngineStarter() []EngineSearch {\n\tmm := make([]EngineSearch, %d)", len(values)) + for _, v := range values { + if validConst(v) { + g.Printf("\n\tmm[%s.%s] = %s.Search", g.pkg.name, v.name, strings.ToLower(v.name)) + } + } + g.Printf("\n\treturn mm\n}") +} diff --git a/generate/searcher/structs.go b/generate/searcher/structs.go new file mode 100644 index 00000000..6c131b6c --- /dev/null +++ b/generate/searcher/structs.go @@ -0,0 +1,53 @@ +package main + +import ( + "bytes" + "go/ast" + "go/types" +) + +// Value represents a declared constant. +type Value struct { + originalName string // The name of the constant. + name string // The name with trimmed prefix. + // The value is stored as a bit pattern alone. The boolean tells us + // whether to interpret it as an int64 or a uint64; the only place + // this matters is when sorting. + // Much of the time the str field is all we need; it is printed + // by Value.String. + value uint64 // Will be converted to int64 when needed. + signed bool // Whether the constant is a signed type. + str string // The string representation given by the "go/constant" package. +} + +type Values []Value + +// Generator holds the state of the analysis. Primarily used to buffer +// the output for format.Source. +type Generator struct { + buf bytes.Buffer // Accumulated output. + pkg *Package // Package we are scanning. + + trimPrefix string + lineComment bool + + logf func(format string, args ...interface{}) // test logging hook; nil when not testing +} + +// File holds a single parsed file and associated data. +type File struct { + pkg *Package // Package to which this file belongs. + file *ast.File // Parsed AST. + // These fields are reset for each type being generated. + typeName string // Name of the constant type. + values Values // Accumulator for constant values of that type. + + trimPrefix string + lineComment bool +} + +type Package struct { + name string + defs map[*ast.Ident]types.Object + files []*File +} diff --git a/generate/searcher/util.go b/generate/searcher/util.go new file mode 100644 index 00000000..450bb35c --- /dev/null +++ b/generate/searcher/util.go @@ -0,0 +1,29 @@ +package main + +import ( + "log" + "os" + "strings" +) + +func validConst(v Value) bool { + lowerName := strings.ToLower(v.name) + return lowerName != "undefined" && isDirectory(lowerName) +} + +// isDirectory reports whether the named file is a directory. +func isDirectory(path string) bool { + info, err := os.Stat(path) + if err != nil { + return false + } + return info.IsDir() +} + +func isDirectoryFatal(path string) bool { + info, err := os.Stat(path) + if err != nil { + log.Fatal(err) + } + return info.IsDir() +} diff --git a/go.mod b/go.mod index cec15582..6308a59d 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/knadh/koanf/v2 v2.0.1 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/robertkrimen/otto v0.2.1 + golang.org/x/tools v0.13.0 github.com/rs/zerolog v1.31.0 ) @@ -28,6 +29,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.15.3 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect @@ -43,6 +45,7 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.5.0 // indirect golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.12.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3c2cbc66..9c12cde3 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= @@ -190,6 +191,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -216,6 +219,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -257,8 +261,9 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/src/engines/name.go b/src/engines/name.go index cd8d976c..669df07f 100644 --- a/src/engines/name.go +++ b/src/engines/name.go @@ -5,6 +5,7 @@ import "strings" type Name uint8 //go:generate enumer -type=Name -json -text -yaml -sql +//go:generate go run github.com/tminaorg/brzaguza/generate/searcher -type=Name -packagename search -output ../search/engine_searcher.go const ( Undefined Name = iota Bing diff --git a/src/search/search.go b/src/search/search.go index 6cdc6aa4..0151a074 100644 --- a/src/search/search.go +++ b/src/search/search.go @@ -11,18 +11,6 @@ import ( "github.com/tminaorg/brzaguza/src/bucket/result" "github.com/tminaorg/brzaguza/src/config" "github.com/tminaorg/brzaguza/src/engines" - "github.com/tminaorg/brzaguza/src/engines/bing" - "github.com/tminaorg/brzaguza/src/engines/brave" - "github.com/tminaorg/brzaguza/src/engines/duckduckgo" - "github.com/tminaorg/brzaguza/src/engines/etools" - "github.com/tminaorg/brzaguza/src/engines/google" - "github.com/tminaorg/brzaguza/src/engines/mojeek" - "github.com/tminaorg/brzaguza/src/engines/presearch" - "github.com/tminaorg/brzaguza/src/engines/qwant" - "github.com/tminaorg/brzaguza/src/engines/startpage" - "github.com/tminaorg/brzaguza/src/engines/swisscows" - "github.com/tminaorg/brzaguza/src/engines/yahoo" - "github.com/tminaorg/brzaguza/src/engines/yep" "github.com/tminaorg/brzaguza/src/rank" ) @@ -53,99 +41,23 @@ func PerformSearch(query string, options engines.Options, config *config.Config) return results } +// engine_searcher, NewEngineStarter() use this. +type EngineSearch func(context.Context, string, *bucket.Relay, engines.Options, config.Settings) error + func runEngines(engineMap map[string]config.Engine, query string, worker *conc.WaitGroup, relay *bucket.Relay, options engines.Options) { log.Info().Msgf("Enabled engines: %v", config.EnabledEngines) + engineStarter := NewEngineStarter() for name, engine := range engineMap { - if engineName, err := engines.NameString(name); err == nil { - switch engineName { - case engines.Google: - worker.Go(func() { - err := google.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", google.Info.Domain) - } - }) - case engines.DuckDuckGo: - worker.Go(func() { - err := duckduckgo.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", duckduckgo.Info.Domain) - } - }) - case engines.Mojeek: - worker.Go(func() { - err := mojeek.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", mojeek.Info.Domain) - } - }) - case engines.Qwant: - worker.Go(func() { - err := qwant.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", qwant.Info.Domain) - } - }) - case engines.Etools: - worker.Go(func() { - err := etools.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", etools.Info.Domain) - } - }) - case engines.Swisscows: - worker.Go(func() { - err := swisscows.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", swisscows.Info.Domain) - } - }) - case engines.Brave: - worker.Go(func() { - err := brave.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", brave.Info.Domain) - } - }) - case engines.Bing: - worker.Go(func() { - err := bing.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", bing.Info.Domain) - } - }) - case engines.Startpage: - worker.Go(func() { - err := startpage.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", startpage.Info.Domain) - } - }) - case engines.Yep: - worker.Go(func() { - err := yep.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", yep.Info.Domain) - } - }) - case engines.Yahoo: - worker.Go(func() { - err := yahoo.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", yahoo.Info.Domain) - } - }) - case engines.Presearch: - worker.Go(func() { - err := presearch.Search(context.Background(), query, relay, options, engine.Settings) - if err != nil { - log.Error().Err(err).Msgf("Failed searching %v", presearch.Info.Domain) - } - }) - } - } else { - log.Panic().Err(err).Msg("failed converting string to engine name") + engineName, nameErr := engines.NameString(name) + if nameErr != nil { + log.Panic().Err(nameErr).Msg("failed converting string to engine name") + return + } + + err := engineStarter[engineName](context.Background(), query, relay, options, engine.Settings) + if err != nil { + log.Error().Err(err).Msgf("failed searching %v", engineName) } } }