pkg/prog: Move all Program implementations into own packages.

This fixes #985.
This commit is contained in:
Qi Xiao 2020-04-25 00:05:27 +01:00
parent 5b88057c94
commit 928949077b
18 changed files with 218 additions and 204 deletions

10
main.go
View File

@ -7,13 +7,15 @@ package main
import (
"os"
"github.com/elves/elvish/pkg/buildinfo"
"github.com/elves/elvish/pkg/daemon"
"github.com/elves/elvish/pkg/prog"
"github.com/elves/elvish/pkg/prog/shell"
"github.com/elves/elvish/pkg/prog/web"
)
func main() {
os.Exit(prog.Run(
[3]*os.File{os.Stdin, os.Stdout, os.Stderr},
os.Args,
prog.VersionProgram{}, prog.BuildInfoProgram{}, prog.DaemonProgram{},
prog.WebProgram{}, prog.ShellProgram{}))
[3]*os.File{os.Stdin, os.Stdout, os.Stderr}, os.Args,
buildinfo.Program, daemon.Program, web.Program, shell.Program))
}

View File

@ -5,8 +5,40 @@
// "go get".
package buildinfo
import (
"fmt"
"os"
"runtime"
"github.com/elves/elvish/pkg/prog"
)
// Build information.
var (
Version = "unknown"
Reproducible = "false"
)
// Program is the buildinfo subprogram.
var Program prog.Program = program{}
type program struct{}
func (program) ShouldRun(f *prog.Flags) bool { return f.Version || f.BuildInfo }
func (program) Run(fds [3]*os.File, f *prog.Flags, _ []string) error {
if f.Version {
fmt.Fprintln(fds[1], Version)
return nil
}
if f.JSON {
fmt.Fprintf(fds[1],
`{"version":%s,"goversion":%s,"reproducible":%v}`+"\n",
quoteJSON(Version), quoteJSON(runtime.Version()), Reproducible)
} else {
fmt.Fprintln(fds[1], "Version:", Version)
fmt.Fprintln(fds[1], "Go version:", runtime.Version())
fmt.Fprintln(fds[1], "Reproducible build:", Reproducible)
}
return nil
}

View File

@ -0,0 +1,60 @@
package buildinfo
import (
"encoding/json"
"fmt"
"runtime"
"testing"
"github.com/elves/elvish/pkg/prog"
. "github.com/elves/elvish/pkg/prog/progtest"
)
func TestVersion(t *testing.T) {
f := Setup()
defer f.Cleanup()
prog.Run(f.Fds(), Elvish("-version"), Program)
f.TestOut(t, 1, Version+"\n")
}
func TestBuildInfo(t *testing.T) {
f := Setup()
defer f.Cleanup()
prog.Run(f.Fds(), Elvish("-buildinfo"), Program)
f.TestOut(t, 1,
fmt.Sprintf(
"Version: %v\nGo version: %v\nReproducible build: %v\n",
Version,
runtime.Version(),
Reproducible))
}
func TestBuildInfo_JSON(t *testing.T) {
f := Setup()
defer f.Cleanup()
prog.Run(f.Fds(), Elvish("-buildinfo", "-json"), Program)
f.TestOut(t, 1,
mustToJSON(struct {
Version string `json:"version"`
GoVersion string `json:"goversion"`
Reproducible bool `json:"reproducible"`
}{
Version,
runtime.Version(),
Reproducible == "true",
})+"\n")
}
func mustToJSON(v interface{}) string {
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(b)
}

View File

@ -1,4 +1,4 @@
package prog
package buildinfo
import (
"bytes"

View File

@ -1,4 +1,4 @@
package prog
package buildinfo
import "testing"

View File

@ -5,9 +5,30 @@
// store package and are not documented here.
package daemon
import "github.com/elves/elvish/pkg/util"
import (
"os"
"github.com/elves/elvish/pkg/prog"
"github.com/elves/elvish/pkg/util"
)
var logger = util.GetLogger("[daemon] ")
// Version is the API version. It should be bumped any time the API changes.
const Version = -93
// Program is the daemon subprogram.
var Program prog.Program = program{}
type program struct{}
func (program) ShouldRun(f *prog.Flags) bool { return f.Daemon }
func (program) Run(fds [3]*os.File, f *prog.Flags, args []string) error {
if len(args) > 0 {
return prog.BadUsage("arguments are not allowed with -daemon")
}
setUmaskForDaemon()
Serve(f.Sock, f.DB)
return nil
}

View File

@ -5,6 +5,8 @@ import (
"testing"
"time"
"github.com/elves/elvish/pkg/prog"
. "github.com/elves/elvish/pkg/prog/progtest"
"github.com/elves/elvish/pkg/store/storetest"
"github.com/elves/elvish/pkg/testutil"
"github.com/elves/elvish/pkg/util"
@ -54,3 +56,12 @@ func TestDaemon(t *testing.T) {
storetest.TestDir(t, client)
storetest.TestSharedVar(t, client)
}
func TestProgram_SpuriousArgument(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := prog.Run(f.Fds(), Elvish("-daemon", "x"), Program)
TestError(t, f, exit, "arguments are not allowed with -daemon")
}

View File

@ -1,6 +1,6 @@
// +build !windows,!plan9,!js
package prog
package daemon
import "golang.org/x/sys/unix"

View File

@ -1,4 +1,4 @@
package prog
package daemon
// No-op on Windows.
func setUmaskForDaemon() {}

View File

@ -1,36 +0,0 @@
package prog
import (
"fmt"
"os"
"runtime"
"github.com/elves/elvish/pkg/buildinfo"
)
type BuildInfoProgram struct{}
func (BuildInfoProgram) ShouldRun(f *Flags) bool { return f.BuildInfo }
func (BuildInfoProgram) Run(fds [3]*os.File, f *Flags, _ []string) error {
if f.JSON {
fmt.Fprintf(fds[1],
`{"version":%s,"goversion":%s,"reproducible":%v}`+"\n",
quoteJSON(buildinfo.Version), quoteJSON(runtime.Version()),
buildinfo.Reproducible)
} else {
fmt.Fprintln(fds[1], "Version:", buildinfo.Version)
fmt.Fprintln(fds[1], "Go version:", runtime.Version())
fmt.Fprintln(fds[1], "Reproducible build:", buildinfo.Reproducible)
}
return nil
}
type VersionProgram struct{}
func (VersionProgram) ShouldRun(f *Flags) bool { return f.Version }
func (VersionProgram) Run(fds [3]*os.File, _ *Flags, _ []string) error {
fmt.Fprintln(fds[1], buildinfo.Version)
return nil
}

View File

@ -1,20 +0,0 @@
package prog
import (
"os"
"github.com/elves/elvish/pkg/daemon"
)
type DaemonProgram struct{}
func (DaemonProgram) ShouldRun(f *Flags) bool { return f.Daemon }
func (DaemonProgram) Run(fds [3]*os.File, f *Flags, args []string) error {
if len(args) > 0 {
return BadUsage("arguments are not allowed with -daemon")
}
setUmaskForDaemon()
daemon.Serve(f.Sock, f.DB)
return nil
}

View File

@ -2,60 +2,16 @@ package prog
import (
"encoding/json"
"fmt"
"runtime"
"testing"
"github.com/elves/elvish/pkg/buildinfo"
. "github.com/elves/elvish/pkg/prog/progtest"
)
func TestVersion(t *testing.T) {
f := Setup()
defer f.Cleanup()
Run(f.Fds(), elvish("-version"), VersionProgram{})
f.TestOut(t, 1, buildinfo.Version+"\n")
}
func TestBuildInfo(t *testing.T) {
f := Setup()
defer f.Cleanup()
Run(f.Fds(), elvish("-buildinfo"), BuildInfoProgram{})
f.TestOut(t, 1,
fmt.Sprintf(
"Version: %v\nGo version: %v\nReproducible build: %v\n",
buildinfo.Version,
runtime.Version(),
buildinfo.Reproducible))
}
func TestBuildInfo_JSON(t *testing.T) {
f := Setup()
defer f.Cleanup()
Run(f.Fds(), elvish("-buildinfo", "-json"), BuildInfoProgram{})
f.TestOut(t, 1,
mustToJSON(struct {
Version string `json:"version"`
GoVersion string `json:"goversion"`
Reproducible bool `json:"reproducible"`
}{
buildinfo.Version,
runtime.Version(),
buildinfo.Reproducible == "true",
})+"\n")
}
func TestHelp(t *testing.T) {
f := Setup()
defer f.Cleanup()
Run(f.Fds(), elvish("-help"))
Run(f.Fds(), Elvish("-help"))
f.TestOutSnippet(t, 1, "Usage: elvish [flags] [script]")
}
@ -64,40 +20,9 @@ func TestBadFlag(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := Run(f.Fds(), elvish("-bad-flag"))
exit := Run(f.Fds(), Elvish("-bad-flag"))
testError(t, f, exit, "flag provided but not defined: -bad-flag")
}
func TestWeb_SpuriousArgument(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := Run(f.Fds(), elvish("-web", "x"), WebProgram{})
testError(t, f, exit, "arguments are not allowed with -web")
}
func TestWeb_SpuriousC(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := Run(f.Fds(), elvish("-web", "-c"), WebProgram{})
testError(t, f, exit, "-c cannot be used together with -web")
}
func TestDaemon_SpuriousArgument(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := Run(f.Fds(), elvish("-daemon", "x"), DaemonProgram{})
testError(t, f, exit, "arguments are not allowed with -daemon")
}
func elvish(args ...string) []string {
return append([]string{"elvish"}, args...)
TestError(t, f, exit, "flag provided but not defined: -bad-flag")
}
func mustToJSON(v interface{}) string {
@ -107,11 +32,3 @@ func mustToJSON(v interface{}) string {
}
return string(b)
}
func testError(t *testing.T, f *Fixture, exit int, wantErrSnippet string) {
t.Helper()
if exit != 2 {
t.Errorf("got exit %v, want 2", exit)
}
f.TestOutSnippet(t, 2, wantErrSnippet)
}

View File

@ -113,3 +113,17 @@ func MustWriteFile(name, content string) {
panic(err)
}
}
// Elvish returns an argument slice starting with "elvish".
func Elvish(args ...string) []string {
return append([]string{"elvish"}, args...)
}
// TestError tests the error result of a program.
func TestError(t *testing.T, f *Fixture, exit int, wantErrSnippet string) {
t.Helper()
if exit != 2 {
t.Errorf("got exit %v, want 2", exit)
}
f.TestOutSnippet(t, 2, wantErrSnippet)
}

View File

@ -1,28 +0,0 @@
package prog
import (
"os"
"github.com/elves/elvish/pkg/prog/shell"
)
type ShellProgram struct{}
func (ShellProgram) ShouldRun(*Flags) bool { return true }
func (ShellProgram) Run(fds [3]*os.File, f *Flags, args []string) error {
p := shell.MakePaths(fds[2],
shell.Paths{Bin: f.Bin, Sock: f.Sock, Db: f.DB})
if f.NoRc {
p.Rc = ""
}
if len(args) > 0 {
exit := shell.Script(
fds, args, &shell.ScriptConfig{
SpawnDaemon: true, Paths: p,
Cmd: f.CodeInArg, CompileOnly: f.CompileOnly, JSON: f.JSON})
return Exit(exit)
}
shell.Interact(fds, &shell.InteractConfig{SpawnDaemon: true, Paths: p})
return nil
}

View File

@ -9,12 +9,37 @@ import (
"github.com/elves/elvish/pkg/cli/term"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/prog"
"github.com/elves/elvish/pkg/sys"
"github.com/elves/elvish/pkg/util"
)
var logger = util.GetLogger("[shell] ")
// Program is the shell subprogram.
var Program prog.Program = program{}
type program struct{}
func (program) ShouldRun(*prog.Flags) bool { return true }
func (program) Run(fds [3]*os.File, f *prog.Flags, args []string) error {
p := MakePaths(fds[2],
Paths{Bin: f.Bin, Sock: f.Sock, Db: f.DB})
if f.NoRc {
p.Rc = ""
}
if len(args) > 0 {
exit := Script(
fds, args, &ScriptConfig{
SpawnDaemon: true, Paths: p,
Cmd: f.CodeInArg, CompileOnly: f.CompileOnly, JSON: f.JSON})
return prog.Exit(exit)
}
Interact(fds, &InteractConfig{SpawnDaemon: true, Paths: p})
return nil
}
func setupShell(fds [3]*os.File, p Paths, spawn bool) (*eval.Evaler, func()) {
restoreTTY := term.SetupGlobal()
ev := InitRuntime(fds[2], p, spawn)

View File

@ -1,22 +0,0 @@
package prog
import (
"os"
"github.com/elves/elvish/pkg/prog/web"
)
type WebProgram struct{}
func (WebProgram) ShouldRun(f *Flags) bool { return f.Web }
func (WebProgram) Run(fds [3]*os.File, f *Flags, args []string) error {
if len(args) > 0 {
return BadUsage("arguments are not allowed with -web")
}
if f.CodeInArg {
return BadUsage("-c cannot be used together with -web")
}
p := web.Web{BinPath: f.Bin, SockPath: f.Sock, DbPath: f.DB, Port: f.Port}
return p.Main(fds, nil)
}

View File

@ -12,9 +12,28 @@ import (
"os"
"github.com/elves/elvish/pkg/eval"
"github.com/elves/elvish/pkg/prog"
"github.com/elves/elvish/pkg/prog/shell"
)
// Program is the web subprogram.
var Program prog.Program = program{}
type program struct{}
func (program) ShouldRun(f *prog.Flags) bool { return f.Web }
func (program) Run(fds [3]*os.File, f *prog.Flags, args []string) error {
if len(args) > 0 {
return prog.BadUsage("arguments are not allowed with -web")
}
if f.CodeInArg {
return prog.BadUsage("-c cannot be used together with -web")
}
p := Web{BinPath: f.Bin, SockPath: f.Sock, DbPath: f.DB, Port: f.Port}
return p.Main(fds, nil)
}
type Web struct {
BinPath string
SockPath string

View File

@ -1,7 +1,26 @@
package web
import "testing"
import (
"testing"
func TestWeb(t *testing.T) {
// TODO(xiaq): Add tests.
"github.com/elves/elvish/pkg/prog"
. "github.com/elves/elvish/pkg/prog/progtest"
)
func TestWeb_SpuriousArgument(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := prog.Run(f.Fds(), Elvish("-web", "x"), Program)
TestError(t, f, exit, "arguments are not allowed with -web")
}
func TestWeb_SpuriousC(t *testing.T) {
f := Setup()
defer f.Cleanup()
exit := prog.Run(f.Fds(), Elvish("-web", "-c"), Program)
TestError(t, f, exit, "-c cannot be used together with -web")
}