Skip to content

Commit 1c085e6

Browse files
authored
Merge pull request #2 from x1unix/dev
1.1.0
2 parents 85fe47b + d8f68c5 commit 1c085e6

32 files changed

+855
-274
lines changed

cmd/playground/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ func start(packagesFile, addr, goRoot string, debug bool) error {
6262

6363
r := mux.NewRouter()
6464
langserver.New(packages).Mount(r.PathPrefix("/api").Subrouter())
65-
r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public")))
65+
r.PathPrefix("/").Handler(langserver.SpaFileServer("./public"))
6666

6767
zap.S().Infof("Listening on %q", addr)
6868

6969
var handler http.Handler
7070
if debug {
71-
zap.S().Warn("Debug mode enabled, CORS disabled")
71+
zap.S().Info("Debug mode enabled, CORS disabled")
7272
handler = langserver.NewCORSDisablerWrapper(r)
7373
} else {
7474
handler = r

docker.mk

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
DOCKERFILE ?= ./build/Dockerfile
22
IMG_NAME ?= x1unix/go-playground
3-
TAG ?= 1.0.0
43

54
.PHONY: docker
65
docker: docker-login docker-make-image
@@ -20,5 +19,8 @@ docker-login:
2019

2120
.PHONY: docker-make-image
2221
docker-make-image:
22+
@if [ -z "$(TAG)" ]; then\
23+
echo "required parameter TAG is undefined" && exit 1; \
24+
fi;
2325
@echo "- Building '$(IMG_NAME):latest' $(TAG)..."
2426
docker image build -t $(IMG_NAME):latest -t $(IMG_NAME):$(TAG) -f $(DOCKERFILE) .

pkg/analyzer/decl.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func formatFieldAndType(t ast.Expr, id *ast.Ident) string {
1010
return id.String() + " " + typeStr
1111
}
1212

13-
func formatFuncParams(params *ast.FieldList) (string, int) {
13+
func formatFieldsList(params *ast.FieldList, joinChar string) (string, int) {
1414
if params == nil {
1515
return "", 0
1616
}
@@ -30,7 +30,7 @@ func formatFuncParams(params *ast.FieldList) (string, int) {
3030
}
3131
}
3232

33-
return strings.Join(fieldTypePair, ", "), paramsLen
33+
return strings.Join(fieldTypePair, joinChar), paramsLen
3434
}
3535

3636
func valSpecToItem(isConst bool, v *ast.ValueSpec, withPrivate bool) []*CompletionItem {
@@ -61,26 +61,30 @@ func valSpecToItem(isConst bool, v *ast.ValueSpec, withPrivate bool) []*Completi
6161
return items
6262
}
6363

64-
func funcToItem(fn *ast.FuncDecl) *CompletionItem {
65-
ci := &CompletionItem{
66-
Label: fn.Name.String(),
67-
Kind: Function,
68-
Documentation: fn.Doc.Text(),
69-
}
70-
71-
params, _ := formatFuncParams(fn.Type.Params)
72-
ci.Detail = "func(" + params + ")"
73-
ci.InsertText = ci.Label + "(" + params + ")"
74-
75-
returns, retCount := formatFuncParams(fn.Type.Results)
64+
func funcToString(fn *ast.FuncType) string {
65+
params, _ := formatFieldsList(fn.Params, ", ")
66+
str := "func(" + params + ")"
67+
returns, retCount := formatFieldsList(fn.Results, ", ")
7668
switch retCount {
7769
case 0:
7870
break
7971
case 1:
80-
ci.Detail += " " + returns
72+
str += " " + returns
8173
default:
82-
ci.Detail += " (" + returns + ")"
74+
str += " (" + returns + ")"
75+
}
76+
77+
return str
78+
}
79+
80+
func funcToItem(fn *ast.FuncDecl) *CompletionItem {
81+
ci := &CompletionItem{
82+
Label: fn.Name.String(),
83+
Kind: Function,
84+
Documentation: fn.Doc.Text(),
8385
}
8486

87+
ci.Detail = funcToString(fn.Type)
88+
ci.InsertText = ci.Label + "()"
8589
return ci
8690
}

pkg/analyzer/expr.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ func expToString(exp ast.Expr) string {
2323
return v.Sel.String()
2424
case *ast.StarExpr:
2525
return "*" + expToString(v.X)
26+
case *ast.Ellipsis:
27+
return "..." + expToString(v.Elt)
28+
case *ast.MapType:
29+
keyT := expToString(v.Key)
30+
valT := expToString(v.Value)
31+
return "map[" + keyT + "]" + valT
32+
case *ast.ChanType:
33+
chanT := expToString(v.Value)
34+
return "chan " + chanT
35+
case *ast.InterfaceType:
36+
typ := "interface{"
37+
fields, fieldCount := formatFieldsList(v.Methods, "\n")
38+
if fieldCount > 0 {
39+
typ += "\n" + fields + "\n"
40+
}
41+
return typ + "}"
42+
case *ast.FuncType:
43+
return funcToString(v)
2644
default:
2745
log.Warnf("expToString: unknown expression - [%[1]T %[1]v]", exp)
2846
return "interface{}"

pkg/goplay/client.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7-
"go.uber.org/zap"
87
"io"
98
"net/http"
109
"net/url"
@@ -22,29 +21,47 @@ const (
2221

2322
var ErrSnippetTooLarge = fmt.Errorf("code snippet too large (max %d bytes)", maxSnippetSize)
2423

24+
func newRequest(ctx context.Context, method, queryPath string, body io.Reader) (*http.Request, error) {
25+
uri := goPlayURL + "/" + queryPath
26+
req, err := http.NewRequestWithContext(ctx, method, uri, body)
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
req.Header.Add("User-Agent", userAgent)
32+
return req, nil
33+
}
34+
35+
func getRequest(ctx context.Context, queryPath string) (*http.Response, error) {
36+
req, err := newRequest(ctx, http.MethodGet, queryPath, nil)
37+
if err != nil {
38+
return nil, nil
39+
}
40+
41+
return http.DefaultClient.Do(req)
42+
}
43+
2544
func doRequest(ctx context.Context, method, url, contentType string, body io.Reader) ([]byte, error) {
26-
url = goPlayURL + "/" + url
27-
zap.S().Debug("doRequest ", url)
28-
req, err := http.NewRequestWithContext(ctx, method, url, body)
45+
req, err := newRequest(ctx, method, url, body)
2946
if err != nil {
3047
return nil, err
3148
}
3249
req.Header.Add("Content-Type", contentType)
33-
req.Header.Add("User-Agent", userAgent)
3450
response, err := http.DefaultClient.Do(req)
3551
if err != nil {
3652
return nil, err
3753
}
3854

39-
var bodyBytes bytes.Buffer
40-
_, err = io.Copy(&bodyBytes, io.LimitReader(response.Body, maxSnippetSize+1))
55+
bodyBytes := &bytes.Buffer{}
56+
_, err = io.Copy(bodyBytes, io.LimitReader(response.Body, maxSnippetSize+1))
4157
defer response.Body.Close()
4258
if err != nil {
4359
return nil, err
4460
}
45-
if bodyBytes.Len() > maxSnippetSize {
46-
return nil, ErrSnippetTooLarge
61+
if err = ValidateContentLength(bodyBytes); err != nil {
62+
return nil, err
4763
}
64+
4865
return bodyBytes.Bytes(), nil
4966
}
5067

pkg/goplay/errors.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package goplay
2+
3+
import "errors"
4+
5+
var ErrSnippetNotFound = errors.New("snippet not found")
6+
7+
type CompileFailedError struct {
8+
msg string
9+
}
10+
11+
func (c CompileFailedError) Error() string {
12+
return c.msg
13+
}
14+
15+
func IsCompileError(err error) bool {
16+
_, ok := err.(CompileFailedError)
17+
return ok
18+
}

pkg/goplay/methods.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,60 @@ import (
55
"encoding/json"
66
"fmt"
77
"go.uber.org/zap"
8+
"io"
9+
"io/ioutil"
10+
"net/http"
811
"net/url"
912
)
1013

14+
type lener interface {
15+
Len() int
16+
}
17+
18+
func ValidateContentLength(r lener) error {
19+
if r.Len() > maxSnippetSize {
20+
return ErrSnippetTooLarge
21+
}
22+
23+
return nil
24+
}
25+
26+
func GetSnippet(ctx context.Context, snippetID string) (*Snippet, error) {
27+
fileName := snippetID + ".go"
28+
resp, err := getRequest(ctx, "p/"+fileName)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
defer resp.Body.Close()
34+
switch resp.StatusCode {
35+
case http.StatusOK:
36+
snippet, err := ioutil.ReadAll(resp.Body)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
return &Snippet{
42+
FileName: fileName,
43+
Contents: string(snippet),
44+
}, nil
45+
case http.StatusNotFound:
46+
return nil, ErrSnippetNotFound
47+
default:
48+
return nil, fmt.Errorf("error from Go Playground server - %d %s", resp.StatusCode, resp.Status)
49+
}
50+
}
51+
52+
func Share(ctx context.Context, src io.Reader) (string, error) {
53+
resp, err := doRequest(ctx, http.MethodPost, "share", "text/plain", src)
54+
if err != nil {
55+
return "", err
56+
}
57+
58+
shareID := string(resp)
59+
return shareID, nil
60+
}
61+
1162
func GoImports(ctx context.Context, src []byte) (*FmtResponse, error) {
1263
form := url.Values{}
1364
form.Add("imports", "true")

pkg/goplay/types.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import (
55
"time"
66
)
77

8+
// Snippet represents shared snippet
9+
type Snippet struct {
10+
FileName string
11+
Contents string
12+
}
13+
814
// FmtResponse is the response returned from
915
// upstream play.golang.org/fmt request
1016
type FmtResponse struct {
@@ -17,7 +23,7 @@ func (r *FmtResponse) HasError() error {
1723
return nil
1824
}
1925

20-
return errors.New(r.Error)
26+
return CompileFailedError{msg: r.Error}
2127
}
2228

2329
// CompileEvent represents individual

pkg/langserver/request.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ import (
1313
"github.com/x1unix/go-playground/pkg/analyzer"
1414
)
1515

16+
type SnippetResponse struct {
17+
FileName string `json:"fileName"`
18+
Code string `json:"code"`
19+
}
20+
21+
type ShareResponse struct {
22+
SnippetID string `json:"snippetID"`
23+
}
24+
1625
type SuggestionRequest struct {
1726
PackageName string `json:"packageName"`
1827
Value string `json:"value"`

pkg/langserver/server.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func (s *Service) Mount(r *mux.Router) {
2727
r.Path("/suggest").HandlerFunc(s.GetSuggestion)
2828
r.Path("/compile").Methods(http.MethodPost).HandlerFunc(s.Compile)
2929
r.Path("/format").Methods(http.MethodPost).HandlerFunc(s.FormatCode)
30+
r.Path("/share").Methods(http.MethodPost).HandlerFunc(s.Share)
31+
r.Path("/snippet/{id}").Methods(http.MethodGet).HandlerFunc(s.GetSnippet)
3032
}
3133

3234
func (s *Service) lookupBuiltin(val string) (*SuggestionsResponse, error) {
@@ -107,7 +109,7 @@ func (s *Service) goImportsCode(w http.ResponseWriter, r *http.Request) ([]byte,
107109
}
108110

109111
if err = resp.HasError(); err != nil {
110-
Errorf(http.StatusBadRequest, err.Error())
112+
Errorf(http.StatusBadRequest, err.Error()).Write(w)
111113
return nil, err, false
112114
}
113115

@@ -118,16 +120,64 @@ func (s *Service) goImportsCode(w http.ResponseWriter, r *http.Request) ([]byte,
118120
func (s *Service) FormatCode(w http.ResponseWriter, r *http.Request) {
119121
code, err, _ := s.goImportsCode(w, r)
120122
if err != nil {
123+
if goplay.IsCompileError(err) {
124+
return
125+
}
126+
121127
s.log.Error(err)
122128
return
123129
}
124130

125131
WriteJSON(w, CompilerResponse{Formatted: string(code)})
126132
}
127133

134+
func (s *Service) Share(w http.ResponseWriter, r *http.Request) {
135+
shareID, err := goplay.Share(r.Context(), r.Body)
136+
defer r.Body.Close()
137+
if err != nil {
138+
if err == goplay.ErrSnippetTooLarge {
139+
Errorf(http.StatusRequestEntityTooLarge, err.Error()).Write(w)
140+
return
141+
}
142+
143+
s.log.Error("failed to share code: ", err)
144+
NewErrorResponse(err).Write(w)
145+
}
146+
147+
WriteJSON(w, ShareResponse{SnippetID: shareID})
148+
}
149+
150+
func (s *Service) GetSnippet(w http.ResponseWriter, r *http.Request) {
151+
vars := mux.Vars(r)
152+
snippetID := vars["id"]
153+
snippet, err := goplay.GetSnippet(r.Context(), snippetID)
154+
if err != nil {
155+
if err == goplay.ErrSnippetNotFound {
156+
Errorf(http.StatusNotFound, "snippet %q not found", snippetID).Write(w)
157+
return
158+
}
159+
160+
s.log.Errorw("failed to get snippet",
161+
"snippetID", snippetID,
162+
"err", err,
163+
)
164+
NewErrorResponse(err).Write(w)
165+
return
166+
}
167+
168+
WriteJSON(w, SnippetResponse{
169+
FileName: snippet.FileName,
170+
Code: snippet.Contents,
171+
})
172+
}
173+
128174
func (s *Service) Compile(w http.ResponseWriter, r *http.Request) {
129175
src, err, changed := s.goImportsCode(w, r)
130176
if err != nil {
177+
if goplay.IsCompileError(err) {
178+
return
179+
}
180+
131181
s.log.Error(err)
132182
return
133183
}
@@ -149,6 +199,6 @@ func (s *Service) Compile(w http.ResponseWriter, r *http.Request) {
149199
result.Formatted = string(src)
150200
}
151201

152-
s.log.Debugw("resp from compiler", "res", res)
202+
s.log.Debugw("response from compiler", "res", res)
153203
WriteJSON(w, result)
154204
}

0 commit comments

Comments
 (0)