commit
0d6b6e07a1
@ -0,0 +1,2 @@
|
||||
.bin
|
||||
.vscode
|
@ -0,0 +1,28 @@
|
||||
FROM golang:1.13.3-alpine AS build
|
||||
|
||||
WORKDIR /go/src/riffraff
|
||||
COPY . /go/src/riffraff
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
RUN make clean build
|
||||
|
||||
FROM alpine
|
||||
|
||||
ARG VERSION=dev
|
||||
|
||||
LABEL maintainer="Adam Veldhousen <adam@vdhsn.com>"
|
||||
LABEL version=${VERSION}
|
||||
|
||||
WORKDIR /usr/local/bin
|
||||
COPY --from=build /go/src/riffraff/.bin/riffraff /usr/local/bin/
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN adduser -u 1000 -D -s /bin/ash riffraff \
|
||||
&& chown -R riffraff:riffraff /usr/local/bin/riffraff
|
||||
|
||||
USER riffraff
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/riffraff", "-port", "8080", "-accesslog"]
|
@ -0,0 +1,15 @@
|
||||
Riff Raff
|
||||
Copyright (C) 2019 Adam Veldhousen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
@ -0,0 +1,39 @@
|
||||
# rifraf
|
||||
|
||||
A browser omni search bar enhancer. Side effects may include enhanced productivity, happiness and less google search tracking.
|
||||
|
||||
|
||||
## How to install
|
||||
|
||||
|
||||
- Install it:
|
||||
```
|
||||
go get git.vdhsn.com/adam/riffraff
|
||||
```
|
||||
|
||||
- Start it:
|
||||
```
|
||||
rifiraff [-p 80] [-accesslog]
|
||||
```
|
||||
|
||||
- Point your browser at it:
|
||||
|
||||
- Customize it:
|
||||
- add a shortcut: `add gitemoji https://www.webfx.com/tools/emoji-cheat-sheet`
|
||||
- add a search *note the `%s`*: `add so https://stackoverflow.com/search?q=%s` or `add go https://godoc.org/?q=%s`
|
||||
|
||||
- Use it:
|
||||
- use stackoverflow shortcut: `so what is the copy and swap idiom`
|
||||
- go to a shortcut: `gitemoji`
|
||||
|
||||
## FAQ
|
||||
|
||||
- Why rifraf?
|
||||
- It's Halloween, I finally watched Rocky Horror Picture Show, and I'm following the butler theme for search apps.
|
||||
|
||||
- How does this work?
|
||||
- This serves a page over HTTP that implements [Opensearch spec](https://developer.mozilla.org/en-US/docs/Web/OpenSearch).
|
||||
|
||||
## License
|
||||
|
||||
GPL
|
@ -0,0 +1,5 @@
|
||||
module github.com/adamveld12/riffraff
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/gobuffalo/packr v1.30.1
|
@ -0,0 +1,80 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -0,0 +1,51 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func NewServer(tp TemplateRenderer, accessLogging bool) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
ss := &CommandHandler{
|
||||
Mutex: &sync.Mutex{},
|
||||
Shortcuts: map[string]string{
|
||||
"*": DefaultSearchProvider,
|
||||
"help": "/",
|
||||
},
|
||||
}
|
||||
|
||||
mux.HandleFunc("/", tp.RenderHandler("index.html", ss, nil))
|
||||
mux.HandleFunc("/index.html", tp.RenderHandler("index.html", ss, nil))
|
||||
mux.HandleFunc("/search_plugin.xml", tp.RenderHandler("search_plugin.xml", ss, http.Header{
|
||||
"Content-Type": []string{"application/opensearchdescription+xml"},
|
||||
}))
|
||||
|
||||
handlerFunc := searchHandler(ss, accessLogging)
|
||||
mux.HandleFunc("/search", handlerFunc)
|
||||
mux.HandleFunc("/search_to_home", handlerFunc)
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func searchHandler(scs *CommandHandler, logAccess bool) http.HandlerFunc {
|
||||
accessLogger := log.New(os.Stdout, "[access] ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
v := req.URL.Query()
|
||||
commandString := v.Get("q")
|
||||
action, err := scs.Handle(commandString)
|
||||
if err != nil {
|
||||
accessLogger.Printf("'%s' -> got error: '%s'", commandString, err.Error())
|
||||
http.Redirect(res, req, fmt.Sprintf("https://duckduckgo.com?q=%s", commandString), http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
accessLogger.Printf("'%s' %s -> 302 %s", commandString, action.Action, action.Location)
|
||||
http.Redirect(res, req, action.Location, http.StatusFound)
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSearchProvider = "https://duckduckgo.com/%s"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnrecognizedCommand = errors.New("unrecognized command")
|
||||
ErrNotEnoughArguments = errors.New("not enough arguments")
|
||||
)
|
||||
|
||||
type CommandHandler struct {
|
||||
*sync.Mutex
|
||||
Shortcuts map[string]string
|
||||
}
|
||||
|
||||
func (c *CommandHandler) Handle(input string) (Command, error) {
|
||||
rawArgs := strings.Fields(input)
|
||||
fragmentCount := len(rawArgs)
|
||||
farg := "*"
|
||||
parameters := input
|
||||
|
||||
var shortcut string
|
||||
|
||||
if fragmentCount > 0 {
|
||||
farg = rawArgs[0]
|
||||
|
||||
if fragmentCount > 1 {
|
||||
shortcut = rawArgs[1]
|
||||
}
|
||||
|
||||
var updateShortcutParams string
|
||||
if fragmentCount > 2 {
|
||||
updateShortcutParams = strings.Join(rawArgs[2:], " ")
|
||||
}
|
||||
|
||||
if cmd, err := c.updateShortcut(farg, shortcut, updateShortcutParams); err != ErrUnrecognizedCommand {
|
||||
return cmd, err
|
||||
}
|
||||
}
|
||||
|
||||
return c.getShortcut(farg, parameters), nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) updateShortcut(action, shortcut, location string) (Command, error) {
|
||||
command := Command{
|
||||
Action: action,
|
||||
Name: shortcut,
|
||||
Location: location,
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "add":
|
||||
if location == "" {
|
||||
return Command{}, ErrNotEnoughArguments
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
c.Shortcuts[shortcut] = command.Location
|
||||
c.Unlock()
|
||||
|
||||
case "remove":
|
||||
if shortcut == "" {
|
||||
return Command{}, ErrNotEnoughArguments
|
||||
}
|
||||
|
||||
command.Location = c.Shortcuts[shortcut]
|
||||
|
||||
c.Lock()
|
||||
delete(c.Shortcuts, shortcut)
|
||||
c.Unlock()
|
||||
|
||||
default:
|
||||
return Command{}, ErrUnrecognizedCommand
|
||||
}
|
||||
|
||||
return command, nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) getShortcut(key string, parameter string) Command {
|
||||
location, ok := c.Shortcuts[key]
|
||||
if !ok {
|
||||
location = DefaultSearchProvider
|
||||
key = "*"
|
||||
}
|
||||
|
||||
if strings.Contains(location, "%s") {
|
||||
location = fmt.Sprintf(location, parameter)
|
||||
}
|
||||
|
||||
return Command{
|
||||
Action: "lookup",
|
||||
Name: key,
|
||||
Location: location,
|
||||
}
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Action string
|
||||
Name string
|
||||
Location string
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Handle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want Command
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty input should send to default search provider",
|
||||
input: "",
|
||||
want: Command{
|
||||
Action: "lookup",
|
||||
Name: "*",
|
||||
Location: fmt.Sprintf(DefaultSearchProvider, ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add shortcut: 'add gh https://github.com'",
|
||||
input: "add gh https://github.com",
|
||||
want: Command{
|
||||
Action: "add",
|
||||
Name: "gh",
|
||||
Location: "https://github.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add shortcut without location: 'add gh'",
|
||||
input: "add gh",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove shortcut: 'remove gh'",
|
||||
input: "remove gh",
|
||||
want: Command{
|
||||
Action: "remove",
|
||||
Name: "gh",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove command with incorrect number of args: 'remove'",
|
||||
input: "remove",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "forward search to search provider: 'golang unit testing frameworks'",
|
||||
input: "golang unit testing frameworks",
|
||||
want: Command{
|
||||
Action: "lookup",
|
||||
Name: "*",
|
||||
Location: fmt.Sprintf(DefaultSearchProvider, "golang unit testing frameworks"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "visit a shortcut: 'fb'",
|
||||
input: "fb",
|
||||
want: Command{
|
||||
Action: "lookup",
|
||||
Name: "fb",
|
||||
Location: "https://facebook.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cm := &CommandHandler{
|
||||
Shortcuts: map[string]string{
|
||||
"fb": "https://facebook.com",
|
||||
},
|
||||
}
|
||||
|
||||
got, err := cm.Handle(tt.input)
|
||||
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Errorf("wantErr: %v got '%v'", tt.wantErr, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseInput('%s') = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type TemplateRenderer struct {
|
||||
FS http.FileSystem
|
||||
}
|
||||
|
||||
type TemplateVariables struct {
|
||||
Host string
|
||||
Entries map[string]string
|
||||
}
|
||||
|
||||
func ToTemplateVars(req *http.Request, shortcuts map[string]string) TemplateVariables {
|
||||
scheme := req.URL.Scheme
|
||||
host := req.URL.Host
|
||||
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
if host = req.Header.Get("X-Forwarded-For"); host == "" {
|
||||
host = req.Host
|
||||
}
|
||||
}
|
||||
|
||||
fqdn := fmt.Sprintf("%s://%s", scheme, host)
|
||||
|
||||
return TemplateVariables{
|
||||
Host: fqdn,
|
||||
Entries: shortcuts,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr TemplateRenderer) Render(name string, templVars TemplateVariables, w io.Writer) error {
|
||||
templateFileName := fmt.Sprintf("%s.tpl", name)
|
||||
|
||||
f, err := tr.FS.Open(templateFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
templateBytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
templateStr := string(templateBytes)
|
||||
|
||||
templateObj, parseTemplateErr := template.New(templateFileName).Parse(templateStr)
|
||||
if parseTemplateErr != nil {
|
||||
return fmt.Errorf("could not parse template for '%s': %w", templateFileName, parseTemplateErr)
|
||||
}
|
||||
|
||||
log.Printf("%+v", templVars)
|
||||
|
||||
return templateObj.Execute(w, templVars)
|
||||
}
|
||||
|
||||
func (tr TemplateRenderer) RenderHandler(filename string, ss *CommandHandler, headers http.Header) http.HandlerFunc {
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
templVar := ToTemplateVars(req, ss.Shortcuts)
|
||||
h := res.Header()
|
||||
for k, v := range headers {
|
||||
h[k] = v
|
||||
}
|
||||
h.Set("Cache-Control", "max-age 0; no-cache; private")
|
||||
|
||||
if err := tr.Render(filename, templVar, res); err != nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "gh",
|
||||
"location": "https://github.com"
|
||||
}
|
||||
],
|
||||
"searches": [
|
||||
{
|
||||
"name": "so",
|
||||
"location": "https://stackoverflow.com/search?q=%s"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 214 B |
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link
|
||||
rel="search"
|
||||
type="application/opensearchdescription+xml"
|
||||
title="riff-raff"
|
||||
href="/search_plugin.xml"
|
||||
/>
|
||||
|
||||
<Url type="application/opensearchdescription+xml"
|
||||
rel="self"
|
||||
template="/search_plugin.xml" />
|
||||
</head>
|
||||
<body style="display: flex; width: 100%; align-items: center; flex-direction: column;">
|
||||
<div style="display: flex; align-content: center; flex-direction: column; min-width: 40%;">
|
||||
<h1>Hello Internet from {{ .Host }}</h1>
|
||||
<!-- Add search box form here for testing/adding shortcuts -->
|
||||
<form action="/search" method="GET" style="display:flex; margin-bottom: 10px; width: 100%;">
|
||||
<input type="text" name="q" value="" style="flex-grow: 1;"/>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<div style="background: lightgray; padding: 10px 5px; margin-bottom: 10px;">
|
||||
add <shortcut> <url>: adds a url as a shortcut<br/>
|
||||
remove <shortcut> : removes a url shortcut<br/>
|
||||
</div>
|
||||
<ul style="list-style-type: none; padding: 0; margin-top: 15px;">
|
||||
{{ range $key, $value := .Entries }}
|
||||
<li>
|
||||
- {{ $key }} → <a href="{{ $value }}">{{ $value }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,11 @@
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>Riff Raff</ShortName>
|
||||
<Description>slick omnibar search enhancer</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16" type="image/png">{{ .Host }}/favicon.png</Image>
|
||||
<Url type="text/html" template="{{ .Host }}/search">
|
||||
<Param name="q" value="{searchTerms}"/>
|
||||
</Url>
|
||||
<Url type="application/x-suggestions+json" template="{{ .Host }}/search"/>
|
||||
<moz:SearchForm>{{ .Host }}/search</moz:SearchForm>
|
||||
</OpenSearchDescription>
|
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/adamveld12/riffraff/internal"
|
||||
"github.com/gobuffalo/packr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 80, "port to listen on")
|
||||
enableAccessLogging := flag.Bool("accesslog", true, "Enable access logging")
|
||||
flag.Parse()
|
||||
|
||||
box := packr.NewBox("./internal/templates")
|
||||
tp := internal.TemplateRenderer{FS: box}
|
||||
|
||||
server := internal.NewServer(tp, *enableAccessLogging)
|
||||
|
||||
log.SetPrefix("[INFO] ")
|
||||
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", *port)
|
||||
log.Printf("Listening @ %s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, server))
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
APP := riffraff
|
||||
|
||||
dev: clean .bin/$(APP)-dev
|
||||
./.bin/$(APP)-dev -port 8080 -accesslog=true
|
||||
|
||||
package:
|
||||
docker build --build-arg VERSION=$${VERSION:-dev} -t vdhsn/$(APP):$${VERSION:-dev} .
|
||||
|
||||
package-run:
|
||||
docker run -it --rm --name riffraff -u 1000:1000 -p 8080:8080 vdhsn/$(APP):$${VERSION:-dev}
|
||||
|
||||
publish: package
|
||||
docker push vdhsn/$(APP):$${VERSION:-dev}
|
||||
|
||||
build: .bin/$(APP)
|
||||
|
||||
.bin:
|
||||
mkdir .bin
|
||||
|
||||
.bin/$(APP): packr .bin
|
||||
packr build -o .bin/$(APP) -v .
|
||||
|
||||
.bin/$(APP)-dev: .bin
|
||||
go build -o .bin/$(APP)-dev -v .
|
||||
|
||||
clean:
|
||||
rm -rf .bin
|
||||
|
||||
packr:
|
||||
go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
.PHONY: build clean dev package package-run publish packr
|
Loading…
Reference in new issue