diff --git a/Makefile b/Makefile index 375c89ba..07218c5a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,28 @@ +ifndef version +version=$(shell git rev-list -1 HEAD) +endif + +buildargs = -ldflags "-X main.VersionString=$(version)" -v + all: build test install build: - cd cmd/nash && make -e build - cd cmd/nashfmt && make -e build + go build $(buildargs) -o ./cmd/nash/nash ./cmd/nash + go build $(buildargs) -o ./cmd/nashfmt/nashfmt ./cmd/nashfmt + +NASHPATH=$(HOME)/nash +NASHROOT=$(HOME)/nashroot + +install: build + @echo + @echo "Installing nash at: "$(NASHROOT) + mkdir -p $(NASHROOT)/bin + rm -f $(NASHROOT)/bin/nash + rm -f $(NASHROOT)/bin/nashfmt + cp -p ./cmd/nash/nash $(NASHROOT)/bin + cp -p ./cmd/nashfmt/nashfmt $(NASHROOT)/bin + rm -rf $(NASHROOT)/stdlib + cp -pr ./stdlib $(NASHROOT)/stdlib deps: go get -v -t golang.org/x/exp/ebnf @@ -14,14 +34,10 @@ docs: docsdeps mdtoc -w ./README.md mdtoc -w ./docs/interactive.md mdtoc -w ./docs/reference.md + mdtoc -w ./docs/stdlib/fmt.md test: deps build - GO15VENDOREXPERIMENT=1 ./hack/check.sh - -install: - cd cmd/nash && make -e install - cd cmd/nashfmt && make -e install - @echo "Nash installed on $(GOPATH)/bin/nash" + ./hack/check.sh update-vendor: cd cmd/nash && nash ./vendor.sh diff --git a/README.md b/README.md index 6e457ce9..a3c266df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ + # Table of Contents - [nash](#nash) @@ -6,6 +7,9 @@ - [Useful stuff](#useful-stuff) - [Why nash scripts are reliable?](#why-nash-scripts-are-reliable) - [Installation](#installation) + - [Release](#release) + - [Linux](#linux) + - [Master](#master) - [Getting started](#getting-started) - [Accessing command line args](#accessing-command-line-args) - [Namespace features](#namespace-features) @@ -16,7 +20,9 @@ - [Security](#security) - [Concept](#concept) - [Motivation](#motivation) +- [Releasing](#releasing) - [Want to contribute?](#want-to-contribute) + # nash @@ -50,14 +56,44 @@ Nash is a system shell, inspired by plan9 `rc`, that makes it easy to create rel ## Installation -If you have Go, go-get it: +Before proceeding to any of the install procedures it is important that you +set in your environment the **NASHPATH** and **NASHROOT** variables. -```sh -# Make sure GOPATH/bin is in your PATH -go get github.com/NeowayLabs/nash/cmd/nash -go get github.com/NeowayLabs/nash/cmd/nashfmt +The **NASHROOT** variable is used to find the stdlib, while **NASHPATH** +is used to find libraries in general. It is important to have two different +paths since this will allow you to delete all libraries +(cleaning up your installation) by removing your $NASHPATH directory +without affecting your base nash installation and standard library. + +If no **NASHPATH** set a default of $HOME/nash will be assumed. +If no **NASHROOT** set a default of $HOME/nashroot will be assumed. + +The libraries lookup dir will be $NASHPATH/lib. +The standard library lookup dir will be $NASHROOT/stdlib. + +After installing the nash binary will be located at $NASHROOT/bin. + +### Release + +Installing is so stupid that we provide small scripts to do it. +If your platform is not supported take a look at the existent ones +and send a MR with the script for your platform. + +#### Linux + +Run: + +``` +./hack/install/linux.sh +``` + +### Master + +Run: + +``` +make install ``` -If not, [download the latest binary release](https://github.com/NeowayLabs/nash/releases) and copy to somewhere in your PATH. ## Getting started diff --git a/cmd/nash/Makefile b/cmd/nash/Makefile deleted file mode 100644 index bc0e9d9c..00000000 --- a/cmd/nash/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -all: build install - -ifndef version -version=$(shell git rev-list -1 HEAD) -endif - -buildargs = -ldflags "-X main.VersionString=$(version)" -v - -build: - @echo "building nash version: "$(version) - GO15VENDOREXPERIMENT=1 go build $(buildargs) - -install: - GO15VENDOREXPERIMENT=1 go install $(buildargs) diff --git a/cmd/nash/main.go b/cmd/nash/main.go index 5983fce5..362fb36c 100644 --- a/cmd/nash/main.go +++ b/cmd/nash/main.go @@ -49,7 +49,7 @@ func main() { if version { fmt.Printf("build tag: %s\n", VersionString) - os.Exit(0) + return } if len(flag.Args()) > 0 { diff --git a/cmd/nashfmt/Makefile b/cmd/nashfmt/Makefile deleted file mode 100644 index 61608bba..00000000 --- a/cmd/nashfmt/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -all: build install - -build: - GO15VENDOREXPERIMENT=1 go build -v - -install: - GO15VENDOREXPERIMENT=1 go install diff --git a/cmd/nashfmt/main.go b/cmd/nashfmt/main.go index b5aa887e..46a6f4c2 100644 --- a/cmd/nashfmt/main.go +++ b/cmd/nashfmt/main.go @@ -12,10 +12,14 @@ import ( var ( overwrite bool + version bool + // version is set at build time + VersionString = "No version provided" ) func init() { flag.BoolVar(&overwrite, "w", false, "overwrite file") + flag.BoolVar(&version, "version", false, "Show version") } func main() { @@ -26,6 +30,11 @@ func main() { flag.Parse() + if version { + fmt.Printf("build tag: %s\n", VersionString) + return + } + if len(flag.Args()) <= 0 { flag.PrintDefaults() return diff --git a/docs/interactive.md b/docs/interactive.md index a1015a2e..a15f192f 100644 --- a/docs/interactive.md +++ b/docs/interactive.md @@ -1,10 +1,12 @@ + # Table of Contents - [Line mode](#line-mode) - [Autocomplete](#autocomplete) - [Hooks](#hooks) - [bindfn](#bindfn) + When used as an interactive shell, nash supports a few features to diff --git a/docs/reference.md b/docs/reference.md index 4e1b2b3a..3a6a7ab8 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,17 +1,28 @@ + # Table of Contents - [Command line arguments](#command-line-arguments) - [Flow control](#flow-control) + - [Branching](#branching) + - [Looping](#looping) + - [Lists](#lists) + - [Forever](#forever) - [Functions](#functions) - [Operators](#operators) - - [+ (Concatenation)](#-concatenation) + - [+](#) - [string](#string) +- [Packages](#packages) - [Iterating](#iterating) - [Built-in functions](#builtin-functions) + - [print](#print) + - [format](#format) - [len](#len) - [append](#append) -- [Packages](#packages) + - [exit](#exit) + - [glob](#glob) +- [Standard Library](#standard-library) + Here lies a comprehensive reference documentation of nash @@ -171,10 +182,19 @@ echo $a+$b #Output:"12" ``` +# Packages + +TODO + # Iterating +TODO + # Built-in functions +Built-in functions are functions that are embedded on the +language. You do not have to import any package to use them. + ## print The function **print** is used to print simple @@ -237,4 +257,20 @@ example_list <= append($example_list, "2") echo $example_list ``` -# Packages +## exit + +TODO + +## glob + +TODO + +# Standard Library + +The standard library is a set of packages that comes with the +nash install (although not obligatory). + +They must be imported explicitly (as any other package) to +be used. + +* [fmt](docs/stdlib/fmt.md) diff --git a/docs/stdlib/fmt.md b/docs/stdlib/fmt.md new file mode 100644 index 00000000..d932c790 --- /dev/null +++ b/docs/stdlib/fmt.md @@ -0,0 +1,21 @@ + + +# Table of Contents + +- [fmt](#fmt) + - [fmt_println](#fmtprintln) + + + +# fmt + +## fmt_println + +Same behavior as print but adds a newline on the end. + +```nash +import fmt + +fmt_println("hi") +#Output:"hi\n" +``` diff --git a/hack/check.sh b/hack/check.sh index 67be14e2..c89b6883 100755 --- a/hack/check.sh +++ b/hack/check.sh @@ -33,6 +33,10 @@ done $GO tool cover -func coverage.txt +echo "running stdlib tests" +export NASHPATH=`pwd` +./cmd/nash/nash ./stdlib/fmt_test.sh + # To submit the test coverage result to coveralls.io, # use goveralls (https://github.com/mattn/goveralls) # goveralls -coverprofile=profile.cov -service=travis-ci diff --git a/hack/install/linux.sh b/hack/install/linux.sh new file mode 100755 index 00000000..3fe0c134 --- /dev/null +++ b/hack/install/linux.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -o errexit +set -o nounset + +VERSION="v0.5" +echo "installing nash: "$VERSION" at NASHPATH: "$NASHPATH + +cd $NASHPATH +tarfile="nash-$VERSION-linux-amd64.tar.gz" +wget https://github.com/NeowayLabs/nash/releases/download/$VERSION/$tarfile +tar xvfz $tarfile +rm -f $tarfile diff --git a/hack/releaser.sh b/hack/releaser.sh index 916c7e54..5275b90d 100755 --- a/hack/releaser.sh +++ b/hack/releaser.sh @@ -19,18 +19,21 @@ mkdir -p dist fn prepare_execs(distfiles, os) { if $os == "windows" { newfiles = () - + for distfile in $distfiles { - file = $distfile+".exe" - - newfiles <= append($newfiles, $file) + src = $distfile[0] + dst = $distfile[1] + newsrc = $src + ".exe" + newdst = $dst + ".exe" + newfiles <= append($newfiles, ($newsrc $newdst)) } - + return $newfiles } + if $os == "linux" { for distfile in $distfiles { - strip $distfile + strip $distfile[0] } } @@ -45,13 +48,36 @@ for os in $supported_os { echo "building OS: "+$GOOS+" ARCH : "+$GOARCH make build "version="+$version - nash = "cmd/nash/nash" - nashfmt = "cmd/nashfmt/nashfmt" - execfiles = ($nash $nashfmt) + pkgdir <= mktemp -d + bindir = $pkgdir + "/bin" + stdlibdir = $pkgdir + "/stdlib" + mkdir -p $bindir + mkdir -p $stdlibdir + + nash_src = "./cmd/nash/nash" + nash_dst = $bindir + "/nash" + nashfmt_src = "./cmd/nashfmt/nashfmt" + nashfmt_dst = $bindir + "/nashfmt" + + execfiles = ( ($nash_src $nash_dst) ($nashfmt_src $nashfmt_dst) ) + execfiles <= prepare_execs($execfiles, $os) + + # TODO: Improve with glob, right now have only one package =) + distfiles <= append($execfiles, ("./stdlib/fmt.sh" $stdlibdir)) + + for distfile in $distfiles { + src = $distfile[0] + dst = $distfile[1] + cp -pr $src $dst + } - distfiles <= prepare_execs($execfiles, $os) - distar <= format("dist/nash-%s-%s-%s.tar.gz", $version, $os, $arch) + projectdir <= pwd + distar <= format("%s/dist/nash-%s-%s-%s.tar.gz", $projectdir, $version, $os, $arch) - tar cvfz $distar $distfiles + chdir($pkgdir) + pkgraw <= ls + pkgfiles <= split($pkgraw, "\n") + tar cvfz $distar $pkgfiles + chdir($projectdir) } } diff --git a/internal/sh/shell.go b/internal/sh/shell.go index ec03672a..4592895d 100644 --- a/internal/sh/shell.go +++ b/internal/sh/shell.go @@ -752,6 +752,65 @@ func (shell *Shell) executeReturn(n *ast.ReturnNode) ([]sh.Obj, error) { return returns, newErrStopWalking() } +func (shell *Shell) getNashRootFromGOPATH(preverr error) (string, error) { + g, hasgopath := shell.Getenv("GOPATH") + if !hasgopath { + return "", errors.NewError("%s\nno GOPATH env var setted", preverr) + } + gopath := g.String() + return gopath + "/src/github.com/NeowayLabs/nash", nil +} + +func isValidNashRoot(nashroot string) bool { + _, err := os.Stat(nashroot + "/stdlib") + return err == nil +} + +func (shell *Shell) getNashRoot() (string, error) { + // TODO(katcipis): It is very annoying to load env vars, perhaps a shell.GetStringEnv ? + + nashroot, ok := shell.Getenv("NASHROOT") + + if !ok { + h, hashome := shell.Getenv("HOME") + if !hashome { + return shell.getNashRootFromGOPATH(errors.NewError("no HOME env var setted")) + } + home := h.String() + nashroot := home + "/nashroot" + + if !isValidNashRoot(nashroot) { + return shell.getNashRootFromGOPATH(errors.NewError("$HOME/nashroot is not valid NASHROOT\n%s")) + } + + return nashroot, nil + } + + return nashroot.String(), nil +} + +func (shell *Shell) getNashPath() (string, error) { + // TODO(katcipis): It is very annoying to load env vars, perhaps a shell.GetStringEnv ? + + nashPath, ok := shell.Getenv("NASHPATH") + + if !ok { + h, hashome := shell.Getenv("HOME") + if !hashome { + return "", errors.NewError("No NASHPATH and no HOME env vars set") + } + home := h.String() + return home + "/nash", nil + } + + if nashPath.Type() != sh.StringType { + return "", errors.NewError("NASHPATH must be a string") + } + + return nashPath.String(), nil + +} + func (shell *Shell) executeImport(node *ast.ImportNode) error { obj, err := shell.evalExpr(node.Path) if err != nil { @@ -798,20 +857,17 @@ func (shell *Shell) executeImport(node *ast.ImportNode) error { } } - nashPath, ok := shell.Getenv("NASHPATH") - - if !ok { - return errors.NewError("NASHPATH environment variable not set on shell %s", shell.name) - } else if nashPath.Type() != sh.StringType { - return errors.NewError("NASHPATH must be n string") + dotDir, nashpatherr := shell.getNashPath() + if nashpatherr == nil { + tries = append(tries, dotDir+"/lib/"+fname) + if !hasExt { + tries = append(tries, dotDir+"/lib/"+fname+".sh") + } } - dotDir := nashPath.String() - - tries = append(tries, dotDir+"/lib/"+fname) - - if !hasExt { - tries = append(tries, dotDir+"/lib/"+fname+".sh") + nashroot, nashrooterr := shell.getNashRoot() + if nashrooterr == nil { + tries = append(tries, nashroot+"/stdlib/"+fname+".sh") } shell.logf("Trying %q\n", tries) @@ -828,10 +884,21 @@ func (shell *Shell) executeImport(node *ast.ImportNode) error { } } - return errors.NewEvalError(shell.filename, node, + errmsg := fmt.Sprintf( "Failed to import path '%s'. The locations below have been tried:\n \"%s\"", fname, - strings.Join(tries, `", "`)) + strings.Join(tries, `", "`), + ) + + if nashpatherr != nil { + errmsg += "\nnashpath error: " + nashpatherr.Error() + } + + if nashrooterr != nil { + errmsg += "\nnashroot error: " + nashrooterr.Error() + } + + return errors.NewEvalError(shell.filename, node, errmsg) } // executePipe executes a pipe of ast.Command's. Each command can be diff --git a/internal/sh/shell_env_test.go b/internal/sh/shell_env_test.go new file mode 100644 index 00000000..3444b58c --- /dev/null +++ b/internal/sh/shell_env_test.go @@ -0,0 +1,269 @@ +package sh + +import ( + "bytes" + "io/ioutil" + "os" + "testing" +) + +func TestLoadsLibFromNASHPATH(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + nashpath := home + "/testnashpath" + os.Setenv("NASHPATH", nashpath) + + nashlib := nashpath + "/lib" + mkdirAll(t, nashlib) + + writeFile(t, nashlib+"/lib.sh", ` + fn test() { + echo "hasnashpath" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "hasnashpath\n") +} + +func TestLoadsLibFromHOMEIfNASHPATHIsUnset(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + nashlib := home + "/nash/lib" + mkdirAll(t, nashlib) + + writeFile(t, nashlib+"/lib.sh", ` + fn test() { + echo "defaultnashpath" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "defaultnashpath\n") +} + +func TestLoadsLibIfNoEnvVarIsSet(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + unsetAllEnvVars(t) + + writeFile(t, home+"/lib.sh", ` + fn test() { + echo "noenv" + } + `) + + oldcwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + chdir(t, home) + defer chdir(t, oldcwd) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "noenv\n") +} + +func TestLoadsStdlibFromNASHROOT(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + nashroot := home + "/testnashroot" + os.Setenv("NASHROOT", nashroot) + + nashstdlib := nashroot + "/stdlib" + mkdirAll(t, nashstdlib) + + writeFile(t, nashstdlib+"/lib.sh", ` + fn test() { + echo "hasnashroot" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "hasnashroot\n") +} + +func TestLoadsStdlibFromHOMEIfNASHROOTIsUnset(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + nashroot := home + "/nashroot" + nashstdlib := nashroot + "/stdlib" + mkdirAll(t, nashstdlib) + + writeFile(t, nashstdlib+"/lib.sh", ` + fn test() { + echo "defaultnashroot" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "defaultnashroot\n") +} + +func TestLoadsStdlibFromGOPATHOnIfHOMEIsUnset(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + os.Unsetenv("HOME") + os.Setenv("GOPATH", home) + // Avoid failure of no NASHPATH/HOME + os.Setenv("NASHPATH", "/whatever") + + nashstdlib := nashStdlibGoPath(home) + mkdirAll(t, nashstdlib) + + writeFile(t, nashstdlib+"/lib.sh", ` + fn test() { + echo "gopathnashroot" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "gopathnashroot\n") +} + +func TestLoadsStdlibFromGOPATHIfStdlibNotOnHOME(t *testing.T) { + home, teardown := setupEnvTests(t) + defer teardown() + + os.Setenv("GOPATH", home) + + nashstdlib := nashStdlibGoPath(home) + mkdirAll(t, nashstdlib) + + writeFile(t, nashstdlib+"/lib.sh", ` + fn test() { + echo "nostdlibonhome" + } + `) + + newTestShell(t).Exec(t, ` + import lib + test() + `, "nostdlibonhome\n") +} + +type testshell struct { + shell *Shell + stdout *bytes.Buffer +} + +func (s *testshell) Exec(t *testing.T, code string, expectedOutupt string) { + err := s.shell.Exec("shellenvtest", code) + if err != nil { + t.Fatal(err) + } + + output := s.stdout.String() + s.stdout.Reset() + + if output != expectedOutupt { + t.Fatalf( + "expected output: [%s] got: [%s]", + expectedOutupt, + output, + ) + } +} + +func newTestShell(t *testing.T) *testshell { + + shell, err := NewShell() + if err != nil { + t.Fatal(err) + } + var out bytes.Buffer + shell.SetStdout(&out) + + return &testshell{shell: shell, stdout: &out} +} + +func setupEnvTests(t *testing.T) (string, func()) { + + home, err := ioutil.TempDir("", "nashenvtests") + if err != nil { + t.Fatal(err) + } + + curhome := os.Getenv("HOME") + curgopath := os.Getenv("GOPATH") + + err = os.Setenv("HOME", home) + if err != nil { + t.Fatal(err) + } + + mkdirAll(t, home) + + return home, func() { + errs := []error{} + errs = append(errs, os.Setenv("HOME", curhome)) + errs = append(errs, os.Setenv("GOPATH", curgopath)) + errs = append(errs, os.Unsetenv("NASHPATH")) + errs = append(errs, os.Unsetenv("NASHROOT")) + errs = append(errs, os.RemoveAll(home)) + + for _, err := range errs { + if err != nil { + t.Errorf("error tearing down: %s", err) + } + } + } +} + +func unsetEnv(t *testing.T, name string) { + err := os.Unsetenv(name) + if err != nil { + t.Fatalf("error[%s] unsetting [%s]", err, name) + } +} + +func unsetAllEnvVars(t *testing.T) { + unsetEnv(t, "HOME") + unsetEnv(t, "GOPATH") + unsetEnv(t, "NASHPATH") + unsetEnv(t, "NASHROOT") +} + +func mkdirAll(t *testing.T, nashlib string) { + err := os.MkdirAll(nashlib, os.ModePerm) + if err != nil { + t.Fatal(err) + } +} + +func writeFile(t *testing.T, filename string, data string) { + err := ioutil.WriteFile(filename, []byte(data), os.ModePerm) + if err != nil { + t.Fatal(err) + } +} + +func chdir(t *testing.T, dir string) { + err := os.Chdir(dir) + if err != nil { + t.Fatalf("error[%s] changing dir to[%s]", err, dir) + } +} + +func nashStdlibGoPath(gopath string) string { + return gopath + "/src/github.com/NeowayLabs/nash/stdlib" +} diff --git a/internal/sh/shell_linux_test.go b/internal/sh/shell_linux_test.go index f216e8d6..3b451719 100644 --- a/internal/sh/shell_linux_test.go +++ b/internal/sh/shell_linux_test.go @@ -49,6 +49,8 @@ func TestExecuteRforkUserNS(t *testing.T) { } var out bytes.Buffer + f, teardown := setup(t) + defer teardown() sh, err := NewShell() @@ -57,7 +59,7 @@ func TestExecuteRforkUserNS(t *testing.T) { return } - sh.SetNashdPath(nashdPath) + sh.SetNashdPath(f.nashdPath) sh.SetStdout(&out) err = sh.Exec("rfork test", ` @@ -83,6 +85,9 @@ func TestExecuteRforkEnvVars(t *testing.T) { return } + f, teardown := setup(t) + defer teardown() + sh, err := NewShell() if err != nil { @@ -90,7 +95,7 @@ func TestExecuteRforkEnvVars(t *testing.T) { return } - sh.SetNashdPath(nashdPath) + sh.SetNashdPath(f.nashdPath) err = sh.Exec("test env", `abra = "cadabra" setenv abra @@ -111,6 +116,8 @@ func TestExecuteRforkUserNSNested(t *testing.T) { } var out bytes.Buffer + f, teardown := setup(t) + defer teardown() sh, err := NewShell() @@ -119,7 +126,7 @@ func TestExecuteRforkUserNSNested(t *testing.T) { return } - sh.SetNashdPath(nashdPath) + sh.SetNashdPath(f.nashdPath) sh.SetStdout(&out) err = sh.Exec("rfork userns nested", ` diff --git a/internal/sh/shell_test.go b/internal/sh/shell_test.go index e0e8fb77..2e240be6 100644 --- a/internal/sh/shell_test.go +++ b/internal/sh/shell_test.go @@ -23,29 +23,45 @@ type execTestCase struct { expectedErr string } -var ( - testDir, gopath, nashdPath string -) +type fixture struct { + dir string + nashdPath string +} -func init() { - gopath = os.Getenv("GOPATH") +func setup(t *testing.T) (fixture, func()) { + gopath := os.Getenv("GOPATH") if gopath == "" { - panic("Please, run tests from inside GOPATH") + t.Fatal("Please, run tests from inside GOPATH") } - testDir = gopath + "/src/github.com/NeowayLabs/nash/" + "testfiles" - nashdPath = gopath + "/src/github.com/NeowayLabs/nash/cmd/nash/nash" + testDir := gopath + "/src/github.com/NeowayLabs/nash/" + "testfiles" + nashdPath := gopath + "/src/github.com/NeowayLabs/nash/cmd/nash/nash" if _, err := os.Stat(nashdPath); err != nil { - panic("Please, run make build before running tests") + t.Fatal("Please, run make build before running tests") + } + + err := os.Setenv("NASHPATH", "/tmp/.nash") + if err != nil { + t.Fatal(err) } - os.Setenv("NASHPATH", "/tmp/.nash") + return fixture{ + dir: testDir, + nashdPath: nashdPath, + }, func() { + err := os.Unsetenv("NASHPATH") + if err != nil { + t.Fatal(err) + } + } } func testExecuteFile(t *testing.T, path, expected string) { var out bytes.Buffer + f, teardown := setup(t) + defer teardown() shell, err := NewShell() @@ -54,7 +70,7 @@ func testExecuteFile(t *testing.T, path, expected string) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = shell.ExecFile(path) @@ -107,18 +123,25 @@ func testShellExec(t *testing.T, shell *Shell, testcase execTestCase) { } func testExec(t *testing.T, testcase execTestCase) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { t.Error(err) return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) testShellExec(t, shell, testcase) } func testInteractiveExec(t *testing.T, testcase execTestCase) { + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -126,7 +149,7 @@ func testInteractiveExec(t *testing.T, testcase execTestCase) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetInteractive(true) testShellExec(t, shell, testcase) @@ -156,11 +179,13 @@ func TestExecuteFile(t *testing.T) { path string expected string } + f, teardown := setup(t) + defer teardown() for _, ftest := range []fileTests{ {path: "/ex1.sh", expected: "hello world\n"}, } { - testExecuteFile(t, testDir+ftest.path, ftest.expected) + testExecuteFile(t, f.dir+ftest.path, ftest.expected) } } @@ -553,6 +578,9 @@ for i in $range { } func TestExecuteRedirection(t *testing.T) { + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -560,7 +588,7 @@ func TestExecuteRedirection(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) path := "/tmp/nashell.test.txt" defer os.Remove(path) @@ -639,6 +667,9 @@ a = ".2" } func TestExecuteRedirectionMap(t *testing.T) { + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -646,7 +677,7 @@ func TestExecuteRedirectionMap(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) err = shell.Exec("redirect map", ` echo -n "hello world" > /tmp/test1.txt @@ -671,24 +702,28 @@ func TestExecuteRedirectionMap(t *testing.T) { } func TestExecuteSetenv(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + for _, test := range []execTestCase{ { "test setenv basic", `test = "hello" setenv test - ` + nashdPath + ` -c "echo $test"`, + ` + f.nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, { "test setenv assignment", `setenv test = "hello" - ` + nashdPath + ` -c "echo $test"`, + ` + f.nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, { "test setenv exec cmd", `setenv test <= echo -n "hello" - ` + nashdPath + ` -c "echo $test"`, + ` + f.nashdPath + ` -c "echo $test"`, "hello\n", "", "", }, { @@ -745,6 +780,9 @@ func TestExecuteCd(t *testing.T) { func TestExecuteImport(t *testing.T) { var out bytes.Buffer + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -752,7 +790,7 @@ func TestExecuteImport(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = ioutil.WriteFile("/tmp/test.sh", []byte(`TESTE="teste"`), 0644) @@ -780,6 +818,9 @@ func TestExecuteImport(t *testing.T) { func TestExecuteIfEqual(t *testing.T) { var out bytes.Buffer + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -787,7 +828,7 @@ func TestExecuteIfEqual(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = shell.Exec("test if equal", ` @@ -826,6 +867,9 @@ func TestExecuteIfEqual(t *testing.T) { func TestExecuteIfElse(t *testing.T) { var out bytes.Buffer + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -833,7 +877,7 @@ func TestExecuteIfElse(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = shell.Exec("test if else", ` @@ -876,6 +920,9 @@ func TestExecuteIfElse(t *testing.T) { func TestExecuteIfElseIf(t *testing.T) { var out bytes.Buffer + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -883,7 +930,7 @@ func TestExecuteIfElseIf(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = shell.Exec("test if else", ` @@ -924,6 +971,10 @@ func TestExecuteIfElseIf(t *testing.T) { } func TestExecuteFnDecl(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -931,7 +982,7 @@ func TestExecuteFnDecl(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) err = shell.Exec("test fnDecl", ` fn build(image, debug) { @@ -945,6 +996,10 @@ func TestExecuteFnDecl(t *testing.T) { } func TestExecuteFnInv(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -952,7 +1007,7 @@ func TestExecuteFnInv(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) var out bytes.Buffer @@ -1093,6 +1148,10 @@ func TestFnComposition(t *testing.T) { } func TestExecuteFnInvOthers(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1100,7 +1159,7 @@ func TestExecuteFnInvOthers(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) var out bytes.Buffer @@ -1133,6 +1192,10 @@ echo -n $integers } func TestNonInteractive(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1140,7 +1203,7 @@ func TestNonInteractive(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetInteractive(true) testShellExec(t, shell, execTestCase{ @@ -1217,8 +1280,11 @@ func TestExecutePipe(t *testing.T) { var stderr bytes.Buffer var stdout bytes.Buffer + f, teardown := setup(t) + defer teardown() + // Case 1 - cmd := exec.Command(nashdPath, "-c", `echo hello | tr -d "[:space:]"`) + cmd := exec.Command(f.nashdPath, "-c", `echo hello | tr -d "[:space:]"`) cmd.Stderr = &stderr cmd.Stdout = &stdout @@ -1240,7 +1306,7 @@ func TestExecutePipe(t *testing.T) { stderr.Reset() // Case 2 - cmd = exec.Command(nashdPath, "-c", `echo hello | wc -l | tr -d "[:space:]"`) + cmd = exec.Command(f.nashdPath, "-c", `echo hello | wc -l | tr -d "[:space:]"`) cmd.Stderr = &stderr cmd.Stdout = &stdout @@ -1263,7 +1329,10 @@ func TestExecutePipe(t *testing.T) { func TestExecuteRedirectionPipe(t *testing.T) { var stderr bytes.Buffer - cmd := exec.Command(nashdPath, "-c", `cat stuff >[2=] | grep file`) + f, teardown := setup(t) + defer teardown() + + cmd := exec.Command(f.nashdPath, "-c", `cat stuff >[2=] | grep file`) cmd.Stderr = &stderr @@ -1302,6 +1371,9 @@ func testTCPRedirection(t *testing.T, port, command string) { t.Fatal(err) } + f, teardown := setup(t) + defer teardown() + go func() { shell, err := NewShell() @@ -1310,7 +1382,7 @@ func testTCPRedirection(t *testing.T, port, command string) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) <-done @@ -1374,6 +1446,9 @@ func TestExecuteUnixRedirection(t *testing.T) { done := make(chan bool) writeDone := make(chan bool) + f, teardown := setup(t) + defer teardown() + go func() { defer func() { writeDone <- true @@ -1386,7 +1461,7 @@ func TestExecuteUnixRedirection(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) <-done @@ -1437,6 +1512,9 @@ func TestExecuteUnixRedirection(t *testing.T) { func TestExecuteUDPRedirection(t *testing.T) { message := "hello world" + f, teardown := setup(t) + defer teardown() + done := make(chan bool) writeDone := make(chan bool) @@ -1452,7 +1530,7 @@ func TestExecuteUDPRedirection(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) <-done @@ -1577,6 +1655,10 @@ echo -n $a`, } func TestExecuteFnAsFirstClass(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1584,7 +1666,7 @@ func TestExecuteFnAsFirstClass(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) var out bytes.Buffer @@ -1616,6 +1698,10 @@ func TestExecuteFnAsFirstClass(t *testing.T) { } func TestExecuteDump(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1623,7 +1709,7 @@ func TestExecuteDump(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.Reset() var out bytes.Buffer @@ -1709,6 +1795,10 @@ func TestExecuteDump(t *testing.T) { } func TestExecuteDumpVariable(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1716,7 +1806,7 @@ func TestExecuteDumpVariable(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.Reset() var out bytes.Buffer @@ -1776,6 +1866,10 @@ func TestExecuteDumpVariable(t *testing.T) { } func TestExecuteConcat(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1783,7 +1877,7 @@ func TestExecuteConcat(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.Reset() var out bytes.Buffer @@ -1824,6 +1918,10 @@ echo -n $c`) } func TestExecuteFor(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1831,7 +1929,7 @@ func TestExecuteFor(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) var out bytes.Buffer @@ -1862,6 +1960,10 @@ loop` } func TestExecuteInfiniteLoop(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1869,7 +1971,7 @@ func TestExecuteInfiniteLoop(t *testing.T) { return } - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) doneCtrlc := make(chan bool) doneLoop := make(chan bool) @@ -1917,6 +2019,10 @@ func TestExecuteInfiniteLoop(t *testing.T) { } func TestExecuteVariableIndexing(t *testing.T) { + + f, teardown := setup(t) + defer teardown() + shell, err := NewShell() if err != nil { @@ -1926,7 +2032,7 @@ func TestExecuteVariableIndexing(t *testing.T) { var out bytes.Buffer - shell.SetNashdPath(nashdPath) + shell.SetNashdPath(f.nashdPath) shell.SetStdout(&out) err = shell.Exec("indexing", `list = ("1" "2" "3") diff --git a/stdlib/fmt.sh b/stdlib/fmt.sh new file mode 100644 index 00000000..3ead6f97 --- /dev/null +++ b/stdlib/fmt.sh @@ -0,0 +1,4 @@ + +fn fmt_println(msg, args...) { + print($msg + "\n", $args...) +} diff --git a/stdlib/fmt_example.sh b/stdlib/fmt_example.sh new file mode 100644 index 00000000..29e8b8b9 --- /dev/null +++ b/stdlib/fmt_example.sh @@ -0,0 +1,8 @@ +import fmt + +if len($ARGS) == "2" { + fmt_println($ARGS[1]) + exit("0") +} + +fmt_println($ARGS[1], $ARGS[2]) diff --git a/stdlib/fmt_test.sh b/stdlib/fmt_test.sh new file mode 100644 index 00000000..3f96548f --- /dev/null +++ b/stdlib/fmt_test.sh @@ -0,0 +1,31 @@ + +fn run_example(args...) { + got, status <= ./cmd/nash/nash ./stdlib/fmt_example.sh $args + return $got, $status +} + +fn assert_success(expected, got, status) { + if $status != "0" { + exit("1") + } + if $got != $expected { + print("expected [%s] got [%s]\n", $expected, $got) + exit("1") + } +} + +fn test_println_format() { + got, status <= run_example("hello %s", "world") + + assert_success("hello world", $got, $status) +} + +fn test_println() { + expected = "pazu" + got, status <= run_example($expected) + + assert_success($expected, $got, $status) +} + +test_println_format() +test_println()