From 1d479733bcbeb630557cd07f721d4510e989934c Mon Sep 17 00:00:00 2001
From: Flu0r1ne <flur01ne@flu0r1ne.net>
Date: Sun, 19 Sep 2021 00:27:55 -0500
Subject: Add list command, copy for bash command, and ability to run
 non-directory commands anywhere

---
 adapters/bash/adapter.go  | 32 ++++++++++++++--
 adapters/bash/config.go   |  1 +
 cmd/planr/main.go         | 30 +++++++++------
 cmd/planr/sub/cli.go      | 14 +++++++
 cmd/planr/sub/evaluate.go | 28 +++++++++++---
 cmd/planr/sub/list.go     | 13 +++++++
 copy.go                   | 96 +++++++++++++++++++++++++++++++++++++++++++++++
 runner.go                 | 13 ++++++-
 version.go                |  2 +-
 9 files changed, 206 insertions(+), 23 deletions(-)
 create mode 100644 cmd/planr/sub/list.go
 create mode 100644 copy.go

diff --git a/adapters/bash/adapter.go b/adapters/bash/adapter.go
index 8c7713c..cfa2032 100644
--- a/adapters/bash/adapter.go
+++ b/adapters/bash/adapter.go
@@ -3,13 +3,14 @@ package bash
 import (
 	"context"
 	"errors"
+	"fmt"
 	"log"
 	"os"
 	"os/exec"
 	"path"
 	"strings"
 	"time"
-        "fmt"
+        "io/ioutil"
 
 	"golang.flu0r1ne.net/planr"
 )
@@ -42,11 +43,34 @@ func (a *Adapter) Init(ctx planr.PipelineContext) {
   a.buildDir = ctx.AdapterDir
 }
 
+func createTmpDir(dir string) string {
+  name, err := ioutil.TempDir(dir, "tmpbuild.*")
+
+  if err != nil {
+    log.Fatal(err)
+  }
+
+  return name
+}
+
+func removeTmpDir(dir string) {
+  if err := os.RemoveAll(dir); err != nil {
+    log.Fatal(err)
+  }
+}
+
 func (adapter Adapter) Build(tcs []planr.TestCase) { }
 
-func executeScriptedTest(builddir, testdir string, tc planr.TestCase) planr.TestResult {
+func executeScriptedTest(builddir, testdir, srcdir string, tc planr.TestCase) planr.TestResult {
   cfg := tc.AdapterConfig().(*Config)
 
+  tmpdir := createTmpDir(builddir)
+  defer removeTmpDir(tmpdir)
+
+  if err := planr.RecursiveCopyDir(srcdir, tmpdir); err != nil {
+    log.Fatalf("Could not copy sources: %v", err)
+  }
+
   timeout := time.Duration(cfg.Timeout) * time.Millisecond
 
   ctx, cancel := context.WithTimeout(context.Background(), timeout)
@@ -60,7 +84,7 @@ func executeScriptedTest(builddir, testdir string, tc planr.TestCase) planr.Test
 
   cmd := exec.CommandContext(ctx, "bash", path)
 
-  cmd.Dir = builddir
+  cmd.Dir = tmpdir
     
   if out, err := cmd.CombinedOutput(); err != nil {
     result.Status = planr.RUNTIME_FAILURE  
@@ -91,7 +115,7 @@ func (adapter Adapter) Evaluate(tcs []planr.TestCase) [] planr.TestResult {
   c := make(chan planr.TestResult, 0)
   for i := range tcs {
     go func(i int) {
-      c <- executeScriptedTest(adapter.buildDir, adapter.dirs.Tests(), tcs[i])
+      c <- executeScriptedTest(adapter.buildDir, adapter.dirs.Tests(), adapter.dirs.Src(), tcs[i])
     }(i)
   }
 
diff --git a/adapters/bash/config.go b/adapters/bash/config.go
index aaa405a..7e6ca3b 100644
--- a/adapters/bash/config.go
+++ b/adapters/bash/config.go
@@ -33,6 +33,7 @@ func (c *Config) finalize(path string) {
   if c.Timeout == 0 {
     c.Timeout = DEFAULT_TIMEOUT;
   }
+
 }
 
 func finalizeConfigs(tcs []planr.TestCase) {
diff --git a/cmd/planr/main.go b/cmd/planr/main.go
index 2243203..e2ed19f 100644
--- a/cmd/planr/main.go
+++ b/cmd/planr/main.go
@@ -80,29 +80,37 @@ func main() {
 	  dieUsage()
 	}
 
+	subcommand := flag.Arg(0)
+	subargs := flag.Args()[1:]
+
+        switch subcommand {
+	  case "version":
+	    fmt.Printf("%s\n", planr.VERSION)
+            os.Exit(0)
+	  case "help", "-h", "-help", "--help":
+	    printUsage(os.Stdout)
+            os.Exit(0)
+          case "build", "evaluate", "eval", "clean", "config", "list":
+            break
+	  default:
+	    fmt.Fprintf(os.Stderr, "unrecognized command %s\n", subcommand)
+	    dieUsage()
+        }
+
         runner := getConfiguredRunner()
 
         cfg := planr.DecodeConfig(runner.ConfigDir())
 
-	subcommand := flag.Arg(0)
-	subargs := flag.Args()[1:]
-
 	switch subcommand {
-	  case "version":
-		fmt.Printf("%s\n", planr.VERSION)
 	  case "build":
 		sub.Build(runner, subargs, cfg)
 	  case "evaluate", "eval":
 		sub.Evaluate(runner, subargs, cfg)
+          case "list":
+                sub.List(runner, subargs, cfg)
           case "clean":
                 sub.Clean(runner, subargs)
           case "config":
                 sub.Config(runner, subargs)
-	  case "help", "-h", "-help", "--help":
-		printUsage(os.Stdout)
-	  default:
-		fmt.Fprintf(os.Stderr, "unrecognized command %s\n", subcommand)
-		dieUsage()
 	}
-
 }
diff --git a/cmd/planr/sub/cli.go b/cmd/planr/sub/cli.go
index 5defa96..9e1099c 100644
--- a/cmd/planr/sub/cli.go
+++ b/cmd/planr/sub/cli.go
@@ -82,6 +82,20 @@ func (opt PrintOpts) HasFlag(flag PrintOpts) bool {
   return (opt & flag) == flag 
 }
 
+func tcPrint(tc planr.TestCase) {
+  title := ""
+  
+  if tc.Config.Title != "" {
+    title = tc.Config.Title
+  } else {
+    title = tc.Cname
+  }
+
+  col_title.Println(title)
+  pprintLabeled("id", tc.Cname)
+  fmt.Println()
+}
+
 func tcPprint(tr planr.TestResult, opt PrintOpts) {
   tcStatusLine(tr)
 
diff --git a/cmd/planr/sub/evaluate.go b/cmd/planr/sub/evaluate.go
index 4f5e4e1..3f1718f 100644
--- a/cmd/planr/sub/evaluate.go
+++ b/cmd/planr/sub/evaluate.go
@@ -2,9 +2,10 @@ package sub
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
+	"io/ioutil"
 	"log"
-        "flag"
 
 	"golang.flu0r1ne.net/planr"
 )
@@ -30,26 +31,39 @@ func prettyPrint(results gradingResults, verbose, summarize bool) {
   }
 }
 
-func jsonPrint(results gradingResults) {
+func jsonPrint(results gradingResults, file string) {
   res, err := json.Marshal(results)
 
   if err != nil {
     log.Fatalf("Error printing JSON: %v\n", err)
   }
 
-  fmt.Println(string(res))
+  if file == "" {
+    fmt.Println(string(res))
+    return
+  }
+
+
+  if err := ioutil.WriteFile(file, res, 0664); err != nil {
+    log.Fatalf("Could not write JSON file %v", err)
+  }
 }
 
 func Evaluate(runner planr.Runner, params []string, cfg *planr.Config) {
   f := flag.NewFlagSet("evaluate", flag.ExitOnError)
 
   jsonOutput := f.Bool("json", false, "print json output")
+  jsonFile   := f.String("json_to_file", "", "export json to a file")
   extra      := f.Bool("extra", false, "print extra grading information")
 
   dieIncompatibleVersion(cfg)
 
   f.Parse(params)
 
+  if *jsonFile != "" && *jsonOutput {
+    log.Fatalf("-json and -json_to_file are mutually exclusive")
+  }
+
   tcs := runner.CollectCases()
 
   // Filter those tests which patch IDs in params
@@ -80,9 +94,11 @@ func Evaluate(runner planr.Runner, params []string, cfg *planr.Config) {
     Score:       planr.Score(trs),
   }
   
-  if *jsonOutput {
-    jsonPrint(results)
-  } else {
+  if *jsonOutput || *jsonFile != "" {
+    jsonPrint(results, *jsonFile)
+  }
+
+  if ! *jsonOutput {
     prettyPrint(results, *extra, summarizeScore)
   }
 }
diff --git a/cmd/planr/sub/list.go b/cmd/planr/sub/list.go
new file mode 100644
index 0000000..911a7e6
--- /dev/null
+++ b/cmd/planr/sub/list.go
@@ -0,0 +1,13 @@
+package sub
+
+import (
+	"golang.flu0r1ne.net/planr"
+)
+
+func List(runner planr.Runner, params []string, cfg * planr.Config) {
+  tcs := runner.CollectCases()
+
+  for _, tc := range tcs {
+    tcPrint(tc)
+  }
+}
diff --git a/copy.go b/copy.go
new file mode 100644
index 0000000..72d9635
--- /dev/null
+++ b/copy.go
@@ -0,0 +1,96 @@
+package planr
+
+import (
+	"fmt"
+        "os"
+        "path/filepath"
+	"io"
+)
+
+
+func Exists(filePath string) bool {
+    if _, err := os.Stat(filePath); os.IsNotExist(err) {
+        return false
+    }
+
+    return true
+}
+
+func CreateIfNotExists(dir string, perm os.FileMode) error {
+    if Exists(dir) {
+        return nil
+    }
+
+    if err := os.MkdirAll(dir, perm); err != nil {
+        return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
+    }
+
+    return nil
+}
+
+func CopySymlink(src, dst string, perm os.FileMode) error {
+    link, err := os.Readlink(src)
+    if err != nil {
+        return err
+    }
+
+    if err := os.Chmod(src, perm); err != nil {
+      return err
+    }
+
+    return os.Symlink(link, dst)
+}
+
+func Copy(srcFile, dstFile string, perm os.FileMode) error {
+    src, err := os.Open(srcFile)
+    if err != nil {
+      return err
+    }
+
+    defer src.Close()
+
+    dst, err := os.OpenFile(dstFile, os.O_CREATE | os.O_WRONLY | os.O_TRUNC, perm)
+    if err != nil {
+        return err
+    }
+
+    defer dst.Close()
+
+    _, err = io.Copy(dst, src)
+    if err != nil {
+        return err
+    }
+
+    return nil
+}
+
+
+func RecursiveCopyDir(source, destination string) error {
+    err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
+        relPath, err := filepath.Rel(source, path)
+
+        if err != nil {
+          return err
+        }
+
+	dst := filepath.Join(destination, relPath)
+        src  := filepath.Join(source, relPath)
+
+        mode := info.Mode()
+
+        if relPath == "" {
+            return nil
+        }
+
+        switch info.Mode() & os.ModeType {
+          case os.ModeDir:
+            return CreateIfNotExists(dst, mode)
+          case os.ModeSymlink:
+            return CopySymlink(src, dst, mode)
+          default:
+            return Copy(src, dst, mode)
+        }
+    });
+
+    return err
+}
diff --git a/runner.go b/runner.go
index 2f86d2e..11fceab 100644
--- a/runner.go
+++ b/runner.go
@@ -92,6 +92,10 @@ func (r Runner) CollectCases() []TestCase {
 }
 
 func (r Runner) Build(tcs []TestCase) {
+  wd, err := os.Getwd()
+  if err != nil {
+    log.Fatal(err)
+  }
 
   if !directoryExists(r.dirs.Build()) {
     r.dirs.MkBuild()
@@ -107,9 +111,16 @@ func (r Runner) Build(tcs []TestCase) {
 
     adapter.Build(cases)
   }
+
+  safeCd(wd)
 }
 
 func (r Runner) Evaluate(tcs []TestCase) []TestResult {
+  wd, err := os.Getwd()
+  if err != nil {
+    log.Fatal(err)
+  }
+
   testSets := r.groupByAdapter(tcs)
   results := make([]TestResult, 0)
   
@@ -131,7 +142,7 @@ func (r Runner) Evaluate(tcs []TestCase) []TestResult {
 
   sort.Sort(ByReadIdx(results))
   
-  safeCd(r.dirs.Config())
+  safeCd(wd)
 
   return results
 }
diff --git a/version.go b/version.go
index 72a80a2..907dd02 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
 package planr
 
-const VERSION = "0.1.5"
+const VERSION = "0.1.6"
-- 
cgit v1.2.3