Repository: exercism/cli
Pull request #722 (2018-08-26 20:00:12)

 Handle exercise directories with numeric suffixes
An early design decision was to allow people to work on multiple solutions to the same
exercise at the same time. We later decided not to do this, but the CLI still had
logic that supported it.

This caused us to (incorrectly) create exercise directories that were named with a
numeric suffix, which would then get submitted to the backend server, which doesn't
care about filepaths.

Because the exercise with the numeric suffix doesn't match the expected path, we did
not correctly trim off any leading directories on submit, which further caused
the download command to put the solution in a weirdly and deeply nested directory,
making the solution hard to find and review.

Closes https://github.com/exercism/exercism/issues/4275
Closes https://github.com/exercism/exercism/issues/4066


nywilken: @kytrinyx I’m reviewing this, but I’m having a hard time verifying  what it is actually doing. Without this change the origin reported issue, at least on Linux, is part of the payload but gets skipped by the continue block. 

That aside the regex in the change  captures the whole file and replaces it with an empty string. Is that right or am I just reading this wrong? I’m going to give it another run through, I’m curious to know how the tests fail if we remove the regex from the download command. 



Patch:
diff --git a/cmd/download.go b/cmd/download.go
index a7491c4..c0139e8 100644
--- a/cmd/download.go
+++ b/cmd/download.go
@@ -9,6 +9,7 @@ import (
 	netURL "net/url"
 	"os"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"github.com/exercism/cli/api"
@@ -200,6 +201,15 @@ func runDownload(cfg config.Config, flags *pflag.FlagSet, args []string) error {
 		// TODO: if there's a collision, interactively resolve (show diff, ask if overwrite).
 		// TODO: handle --force flag to overwrite without asking.
 
+		// Work around a path bug due to an early design decision (later reversed) to
+		// allow numeric suffixes for exercise directories, allowing people to have
+		// multiple parallel versions of an exercise.
+		pattern := fmt.Sprintf(`\A.*[/\\]%s-\d*/`, solution.Exercise)
+		rgxNumericSuffix := regexp.MustCompile(pattern)
+		if rgxNumericSuffix.MatchString(file) {
+			file = string(rgxNumericSuffix.ReplaceAll([]byte(file), []byte("")))
+		}
+
 		// Rewrite paths submitted with an older, buggy client where the Windows path is being treated as part of the filename.
 		file = strings.Replace(file, "\\", "/", -1)
 
diff --git a/workspace/workspace.go b/workspace/workspace.go
index 0e9092e..8439530 100644
--- a/workspace/workspace.go
+++ b/workspace/workspace.go
@@ -5,7 +5,6 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"regexp"
 	"strings"
 )
 
@@ -16,8 +15,6 @@ func IsMissingMetadata(err error) bool {
 	return err == errMissingMetadata
 }
 
-var rgxSerialSuffix = regexp.MustCompile(`-\d*$`)
-
 // Workspace represents a user's Exercism workspace.
 // It may contain a user's own exercises, and other people's
 // exercises that they've downloaded to look at or run locally.


Test patch:
diff --git a/cmd/download_test.go b/cmd/download_test.go
index 401eb6d..7beb26a 100644
--- a/cmd/download_test.go
+++ b/cmd/download_test.go
@@ -166,6 +166,10 @@ func fakeDownloadServer(requestor, teamSlug string) *httptest.Server {
 		fmt.Fprint(w, "this is file 2")
 	})
 
+	mux.HandleFunc("/full/path/with/numeric-suffix/bogus-track/bogus-exercise-12345/subdir/numeric.txt", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, "with numeric suffix")
+	})
+
 	mux.HandleFunc("/special-char-filename#.txt", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprint(w, "this is a special file")
 	})
@@ -217,6 +221,11 @@ func assertDownloadedCorrectFiles(t *testing.T, targetDir string) {
 			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "subdir", "file-2.txt"),
 			contents: "this is file 2",
 		},
+		{
+			desc:     "a path with a numeric suffix",
+			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "subdir", "numeric.txt"),
+			contents: "with numeric suffix",
+		},
 		{
 			desc:     "a file that requires URL encoding",
 			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "special-char-filename#.txt"),
@@ -278,7 +287,8 @@ const payloadTemplate = `
 			"/with-leading-slash.txt",
 			"\\with-leading-backslash.txt",
 			"\\with\\backslashes\\in\\path.txt",
-			"file-3.txt"
+			"file-3.txt",
+			"/full/path/with/numeric-suffix/bogus-track/bogus-exercise-12345/subdir/numeric.txt"
 		],
 		"iteration": {
 			"submitted_at": "2017-08-21t10:11:12.130z"