diff --git a/hw08_envdir_tool/env_reader.go b/hw08_envdir_tool/env_reader.go index 92e9823..a67d23a 100644 --- a/hw08_envdir_tool/env_reader.go +++ b/hw08_envdir_tool/env_reader.go @@ -1,5 +1,15 @@ package main +import ( + "bufio" + "bytes" + "fmt" + "log" + "os" + "path/filepath" + "strings" +) + type Environment map[string]EnvValue // EnvValue helps to distinguish between empty files and files with the first empty line. @@ -11,6 +21,74 @@ type EnvValue struct { // ReadDir reads a specified directory and returns map of env variables. // Variables represented as files where filename is name of variable, file first line is a value. func ReadDir(dir string) (Environment, error) { - // Place your code here - return nil, nil + dir = filepath.Clean(dir) + + des, err := os.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("readDir: %w", err) + } + + env := make(Environment) + + for _, de := range des { + info, err := de.Info() + if err != nil { + log.Printf("info file '%s': %s", de.Name(), err) + continue + } + if !info.Mode().IsRegular() { + log.Printf("file '%s' is not regular", de.Name()) + continue + } + + if info.Size() == 0 { + env[de.Name()] = EnvValue{Value: "", NeedRemove: true} + continue + } + + fName := fmt.Sprintf("%s%v%s", dir, string(os.PathSeparator), de.Name()) + f, err := os.Open(fName) + if err != nil { + log.Printf("open file '%s': %s", fName, err) + continue + } + + rd := bufio.NewReader(f) + data, flg, err := rd.ReadLine() + if err != nil { + log.Printf("ReadLine: %s", err) + continue + } + + var d []byte + for flg { + d, flg, err = rd.ReadLine() + if err != nil { + log.Printf("ReadLine: %s", err) + continue + } + data = append(data, d...) + } + + data = bytes.ReplaceAll(data, []byte("\000"), []byte("\n")) + + env[de.Name()] = EnvValue{Value: strings.TrimRight(string(data), " \t"), NeedRemove: false} + } + + return env, nil +} + +func SetEnv(env Environment) error { + for envName, envVal := range env { + if envVal.NeedRemove { + if err := os.Unsetenv(envName); err != nil { + return err + } + } else { + if err := os.Setenv(envName, envVal.Value); err != nil { + return err + } + } + } + return nil } diff --git a/hw08_envdir_tool/env_reader_test.go b/hw08_envdir_tool/env_reader_test.go index 7962c06..7d71ab5 100644 --- a/hw08_envdir_tool/env_reader_test.go +++ b/hw08_envdir_tool/env_reader_test.go @@ -1,7 +1,106 @@ package main -import "testing" +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const testdataPath = "." + string(os.PathSeparator) + "testdata" func TestReadDir(t *testing.T) { - // Place your code here + t.Run("no error", func(t *testing.T) { + envDir := testdataPath + string(os.PathSeparator) + "env" + testEnv := Environment{ + "BAR": EnvValue{Value: "bar", NeedRemove: false}, + "EMPTY": EnvValue{Value: "", NeedRemove: false}, + "FOO": EnvValue{Value: " foo\nwith new line", NeedRemove: false}, + "HELLO": EnvValue{Value: "\"hello\"", NeedRemove: false}, + "UNSET": EnvValue{Value: "", NeedRemove: true}, + } + + fmt.Println("envDir =", envDir) + env, err := ReadDir(envDir) + + require.NoError(t, err, "no error") + require.Equal(t, env, testEnv, "Environment incorrect") + }) + + t.Run("no such directory", func(t *testing.T) { + envDir := testdataPath + string(os.PathSeparator) + "env_nosuch" + + _, tstErr := ReadDir(envDir) + + require.EqualError( + t, + tstErr, + "readDir: open testdata/env_nosuch: no such file or directory", + "actual err - %v", tstErr) + }) + + t.Run("empty directory", func(t *testing.T) { + envDir, err := os.MkdirTemp(testdataPath, "env_*") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(envDir) + + env, tstErr := ReadDir(envDir) + + require.Len(t, env, 0, "Env must be empty") + require.NoError(t, tstErr, "no error") + }) +} + +func TestSetEnv(t *testing.T) { + t.Run("empty map", func(t *testing.T) { + errTest := SetEnv(Environment{}) + + require.NoError(t, errTest, "no error") + }) + + t.Run("var remove", func(t *testing.T) { + os.Setenv("var1", "Value1") + os.Setenv("var2", "Value2") + + errTest := SetEnv(Environment{ + "var1": {"", true}, + "var2": {"value2", false}, + "var3": {"", false}, + }) + + require.NoError(t, errTest, "no error") + + _, var1Exists := os.LookupEnv("var1") + require.Equal(t, false, var1Exists, "var1 must be remove") + + var2Val, var2Exists := os.LookupEnv("var2") + require.Equal(t, true, var2Exists, "var2 should be") + require.Equal(t, "value2", var2Val, "var2 must be 'value2'") + + var3Val, var3Exists := os.LookupEnv("var3") + require.Equal(t, true, var3Exists, "var3 should be") + require.Equal(t, "", var3Val, "var3 must be empty") + }) + + t.Run("error variable name", func(t *testing.T) { + errTest := SetEnv(Environment{ + "var=": {"varVal", false}, + }) + + require.EqualError(t, errTest, "setenv: invalid argument", "expected error: 'setenv: invalid argument'") + }) + + t.Run("error variable value", func(t *testing.T) { + varVal := "var" + string([]byte("\000")) + "Val" + + errTest := SetEnv(Environment{ + "var": {varVal, false}, + }) + + require.EqualError(t, errTest, "setenv: invalid argument", "expected error: 'setenv: invalid argument'") + }) } diff --git a/hw08_envdir_tool/executor.go b/hw08_envdir_tool/executor.go index 33589ea..5c1fcf7 100644 --- a/hw08_envdir_tool/executor.go +++ b/hw08_envdir_tool/executor.go @@ -1,7 +1,36 @@ package main +import ( + "log" + "os" + "os/exec" +) + // RunCmd runs a command + arguments (cmd) with environment variables from env. func RunCmd(cmd []string, env Environment) (returnCode int) { - // Place your code here. - return + if len(cmd) < 1 { + return 0 + } + if err := SetEnv(env); err != nil { + log.Println("SetEnv:", err) + return 0x7F + } + cmdName := cmd[0] + + cmdArgs := []string{} + if len(cmd) > 1 { + cmdArgs = cmd[1:] + } + + cmdExec := exec.Command(cmdName, cmdArgs...) + + cmdExec.Stdin = os.Stdin + cmdExec.Stdout = os.Stdout + cmdExec.Stderr = os.Stderr + + if err := cmdExec.Run(); err != nil { + log.Println("error Run:", err) + } + + return cmdExec.ProcessState.ExitCode() } diff --git a/hw08_envdir_tool/executor_test.go b/hw08_envdir_tool/executor_test.go index 6402ce3..11acaf3 100644 --- a/hw08_envdir_tool/executor_test.go +++ b/hw08_envdir_tool/executor_test.go @@ -1,7 +1,33 @@ package main -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/require" +) func TestRunCmd(t *testing.T) { - // Place your code here + t.Run("ls no error no args", func(t *testing.T) { + exitCode := RunCmd([]string{"ls"}, Environment{}) + + require.Equal(t, exitCode, 0, "exitCode incorrect") + }) + + t.Run("unknow command", func(t *testing.T) { + exitCode := RunCmd([]string{"lssssss"}, Environment{}) + + require.Equal(t, exitCode, -1, "exitCode incorrect") + }) + + t.Run("ls correct argument", func(t *testing.T) { + exitCode := RunCmd([]string{"ls", "-l"}, Environment{}) + + require.Equal(t, exitCode, 0, "exitCode incorrect") + }) + + t.Run("ls unknow argument", func(t *testing.T) { + exitCode := RunCmd([]string{"ls", "-y"}, Environment{}) + + require.Equal(t, exitCode, 2, "exitCode incorrect") + }) } diff --git a/hw08_envdir_tool/go.mod b/hw08_envdir_tool/go.mod index 55e759b..f77cbf1 100644 --- a/hw08_envdir_tool/go.mod +++ b/hw08_envdir_tool/go.mod @@ -1,3 +1,11 @@ -module github.com/fixme_my_friend/hw08_envdir_tool +module github.com/DimVlas/otus_hw/hw08_envdir_tool go 1.19 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/hw08_envdir_tool/go.sum b/hw08_envdir_tool/go.sum new file mode 100644 index 0000000..60ce688 --- /dev/null +++ b/hw08_envdir_tool/go.sum @@ -0,0 +1,10 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw08_envdir_tool/main.go b/hw08_envdir_tool/main.go index 1eca213..7b50471 100644 --- a/hw08_envdir_tool/main.go +++ b/hw08_envdir_tool/main.go @@ -1,5 +1,25 @@ package main +import ( + "fmt" + "os" + "path/filepath" +) + func main() { - // Place your code here. + if len(os.Args) < 3 { + fmt.Printf("usage: %s [arg1 arg2 ...]\n", filepath.Base(os.Args[0])) + return + } + + envDir := os.Args[1] + + env, err := ReadDir(envDir) + if err != nil { + fmt.Println("ReadDir:", err) + } + + retCode := RunCmd(os.Args[2:], env) + + os.Exit(retCode) }