diff --git a/modfile/gop_test.go b/modfile/gop_test.go index 8c05ab3..093e728 100644 --- a/modfile/gop_test.go +++ b/modfile/gop_test.go @@ -311,6 +311,21 @@ class ."spx Sprite doTestParseErr(t, `gop.mod:3: symbol sprite invalid: invalid Go export symbol format`, ` project github.com/goplus/spx math class .spx sprite +`) + doTestParseErr(t, `gop.mod:3: usage: import [name] pkgPath`, ` +project github.com/goplus/spx math +import +`) + doTestParseErr(t, `gop.mod:3: invalid quoted string: invalid syntax`, ` +project github.com/goplus/spx math +import "\?" +`) + doTestParseErr(t, `gop.mod:3: invalid syntax`, ` +project github.com/goplus/spx math +import "\?" math +`) + doTestParseErr(t, `gop.mod:2: import must declare after a project definition`, ` +import math `) doTestParseErr(t, `gop.mod:2: unknown directive: unknown`, ` unknown .spx diff --git a/modfile/regtest_test.go b/modfile/regtest_test.go index 5531073..6f82fff 100644 --- a/modfile/regtest_test.go +++ b/modfile/regtest_test.go @@ -17,11 +17,10 @@ package modfile_test import ( - "os" "testing" "github.com/goplus/mod/gopmod" - "github.com/goplus/mod/modload" + "github.com/goplus/mod/modload/modtest" ) func TestGopMod(t *testing.T) { @@ -43,29 +42,10 @@ func TestGopMod(t *testing.T) { } } -func TestLoadFromEx(t *testing.T) { - const gomodText = ` -module github.com/goplus/community - -go 1.18 +func TestGopClass(t *testing.T) { + modtest.GopClass(t) +} -require ( - github.com/goplus/yap v0.5.0 //gop:class - github.com/qiniu/a v0.1.0 - github.com/qiniu/x v1.13.2 // gop:class -) -` - const gomod = "go.mod" - mod, err := modload.LoadFromEx(gomod, "gop.mod", func(s string) ([]byte, error) { - if s == gomod { - return []byte(gomodText), nil - } - return nil, os.ErrNotExist - }) - if err != nil { - t.Fatal("LoadFromEx:", err) - } - if n := len(mod.Opt.ClassMods); n != 2 { - t.Fatal("len(mod.Opt.Import):", n) - } +func TestImport(t *testing.T) { + modtest.Import(t) } diff --git a/modfile/rule.go b/modfile/rule.go index 295b75f..b988f6f 100644 --- a/modfile/rule.go +++ b/modfile/rule.go @@ -31,7 +31,7 @@ import ( type File struct { Gop *Gop Projects []*Project - ClassMods []string + ClassMods []string // calc by require statements in go.mod (not gop.mod). Syntax *FileSyntax } @@ -51,18 +51,27 @@ func (p *File) proj() *Project { // current project // A Gop is the gop statement. type Gop = modfile.Go -// A Register is the //gop:class statement. -type Register struct { - ClassfileMod string // module path of classfile - Syntax *Line +// A Class is the work class statement. +type Class struct { + Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".spx" + Class string // "Sprite" + Syntax *Line +} + +// A Import is the import statement. +type Import struct { + Name string // maybe empty + Path string + Syntax *Line } // A Project is the project statement. type Project struct { - Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".gmx" - Class string // "Game" - Works []*Class // work class of classfile - PkgPaths []string // package paths of classfile + Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".gmx" + Class string // "Game" + Works []*Class // work class of classfile + PkgPaths []string // package paths of classfile and optional inline-imported packages. + Import []*Import // auto-imported packages Syntax *Line } @@ -79,13 +88,6 @@ func (p *Project) IsProj(ext, fname string) bool { return true } -// A Class is the work class statement. -type Class struct { - Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".spx" - Class string // "Sprite" - Syntax *Line -} - func New(gopmod, gopVer string) *File { gop := &Line{ Token: []string{"gop", gopVer}, @@ -251,6 +253,34 @@ func (f *File) parseVerb(errs *ErrorList, verb string, line *Line, args []string Class: class, Syntax: line, }) + case "import": + proj := f.proj() + if proj == nil { + errorf("import must declare after a project definition") + return + } + var name string + switch len(args) { + case 2: + v, err := parseString(&args[0]) + if err != nil { + wrapError(err) + return + } + name = v + args = args[1:] + fallthrough + case 1: + pkgPath, err := parsePkgPath(&args[0]) + if err != nil { + wrapError(err) + return + } + proj.Import = append(proj.Import, &Import{Name: name, Path: pkgPath, Syntax: line}) + default: + errorf("usage: import [name] pkgPath") + return + } default: if strict { errorf("unknown directive: %s", verb) @@ -320,23 +350,22 @@ func parseString(s *string) (string, error) { return t, nil } -func parseStrings(args []string) (arr []string, err error) { - arr = make([]string, len(args)) - for i := range args { - if arr[i], err = parseString(&args[i]); err != nil { - return - } +func parsePkgPath(s *string) (path string, err error) { + if path, err = parseString(s); err != nil { + err = fmt.Errorf("invalid quoted string: %v", err) + return + } + if !isPkgPath(path) { + err = fmt.Errorf(`"%s" is not a valid package path`, path) } return } func parsePkgPaths(args []string) (paths []string, err error) { - if paths, err = parseStrings(args); err != nil { - return nil, fmt.Errorf("invalid quoted string: %v", err) - } - for _, pkg := range paths { - if !isPkgPath(pkg) { - return nil, fmt.Errorf(`"%s" is not a valid package path`, pkg) + paths = make([]string, len(args)) + for i := range args { + if paths[i], err = parsePkgPath(&args[i]); err != nil { + return } } return diff --git a/modload/modtest/modtest.go b/modload/modtest/modtest.go new file mode 100644 index 0000000..c3c74c6 --- /dev/null +++ b/modload/modtest/modtest.go @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modtest + +import ( + "os" + "testing" + + "github.com/goplus/mod/modload" +) + +func LoadFrom(gomod, gopmod string, gomodText, gopmodText string) (mod modload.Module, err error) { + return modload.LoadFromEx(gomod, gopmod, func(s string) ([]byte, error) { + if s == gomod { + return []byte(gomodText), nil + } else if s == gopmod && gopmodText != "" { + return []byte(gopmodText), nil + } + return nil, os.ErrNotExist + }) +} + +func Load(t *testing.T, gomodText, gopmodText string, errMsg string) modload.Module { + mod, err := LoadFrom("/foo/go.mod", "/foo/gop.mod", gomodText, gopmodText) + if err != nil { + if err.Error() != errMsg { + t.Fatal("LoadFrom:", err) + } + } + return mod +} + +func GopClass(t *testing.T) { + const gomodText = ` +module github.com/goplus/community + +go 1.18 + +require ( + github.com/goplus/yap v0.5.0 //gop:class + github.com/qiniu/a v0.1.0 + github.com/qiniu/x v1.13.2 // gop:class +) +` + mod := Load(t, gomodText, ``, ``) + if n := len(mod.Opt.ClassMods); n != 2 { + t.Fatal("len(mod.Opt.Import):", n) + } +} + +func Import(t *testing.T) { + const gomodText = ` +module github.com/goplus/yap + +go 1.18 +` + const gopmodText = ` +gop 1.2 + +project _yap.gox App github.com/goplus/yap + +project _ytest.gox App github.com/goplus/yap/test +class _ytest.gox Case +import github.com/goplus/yap/ytest/auth/jwt +import yauth github.com/goplus/yap/ytest/auth +` + mod := Load(t, gomodText, gopmodText, ``) + if n := len(mod.Opt.Projects); n != 2 { + t.Fatal("len(mod.Opt.Projects):", n) + } +} diff --git a/modload/regtest_test.go b/modload/regtest_test.go new file mode 100644 index 0000000..8a77348 --- /dev/null +++ b/modload/regtest_test.go @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modload_test + +import ( + "testing" + + "github.com/goplus/mod/modload/modtest" +) + +func TestGopClass(t *testing.T) { + modtest.GopClass(t) +} + +func TestImport(t *testing.T) { + modtest.Import(t) +}