diff --git a/cmd/submit.go b/cmd/submit.go index e468fe7d8..9952b5fdb 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -8,7 +8,6 @@ import ( "mime/multipart" "os" "path/filepath" - "strings" "github.com/exercism/cli/api" "github.com/exercism/cli/config" @@ -125,6 +124,8 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { exerciseDir = dir } + exercise := workspace.NewExerciseFromDir(exerciseDir) + solution, err := workspace.NewSolution(exerciseDir) if err != nil { return err @@ -143,7 +144,7 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { return fmt.Errorf(msg, BinaryName, solution.Exercise, solution.Track) } - paths := make([]string, 0, len(args)) + exercise.Documents = make([]workspace.Document, 0, len(args)) for _, file := range args { // Don't submit empty files info, err := os.Stat(file) @@ -161,10 +162,14 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { fmt.Fprintf(Err, msg, file) continue } - paths = append(paths, file) + doc, err := workspace.NewDocument(exercise.Filepath(), file) + if err != nil { + return err + } + exercise.Documents = append(exercise.Documents, doc) } - if len(paths) == 0 { + if len(exercise.Documents) == 0 { msg := ` No files found to submit. @@ -176,18 +181,14 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { body := &bytes.Buffer{} writer := multipart.NewWriter(body) - for _, path := range paths { - file, err := os.Open(path) + for _, doc := range exercise.Documents { + file, err := os.Open(doc.Filepath()) if err != nil { return err } defer file.Close() - dirname := fmt.Sprintf("%s%s%s", string(os.PathSeparator), solution.Exercise, string(os.PathSeparator)) - pieces := strings.Split(path, dirname) - filename := pieces[len(pieces)-1] - - part, err := writer.CreateFormFile("files[]", filename) + part, err := writer.CreateFormFile("files[]", doc.Path()) if err != nil { return err } diff --git a/cmd/submit_test.go b/cmd/submit_test.go index f4b2e781e..556c3d390 100644 --- a/cmd/submit_test.go +++ b/cmd/submit_test.go @@ -180,7 +180,7 @@ func TestSubmitFiles(t *testing.T) { assert.Equal(t, 3, len(submittedFiles)) assert.Equal(t, "This is file 1.", submittedFiles["file-1.txt"]) - assert.Equal(t, "This is file 2.", submittedFiles[filepath.Join("subdir", "file-2.txt")]) + assert.Equal(t, "This is file 2.", submittedFiles["subdir/file-2.txt"]) assert.Equal(t, "This is the readme.", submittedFiles["README.md"]) } @@ -278,7 +278,7 @@ func TestSubmitFilesForTeamExercise(t *testing.T) { assert.Equal(t, 2, len(submittedFiles)) assert.Equal(t, "This is file 1.", submittedFiles["file-1.txt"]) - assert.Equal(t, "This is file 2.", submittedFiles[filepath.Join("subdir", "file-2.txt")]) + assert.Equal(t, "This is file 2.", submittedFiles["subdir/file-2.txt"]) } func TestSubmitOnlyEmptyFile(t *testing.T) { diff --git a/workspace/document.go b/workspace/document.go new file mode 100644 index 000000000..52b1264d8 --- /dev/null +++ b/workspace/document.go @@ -0,0 +1,34 @@ +package workspace + +import "path/filepath" + +// Document is a file in a directory. +type Document struct { + Root string + RelativePath string +} + +// NewDocument creates a document from the filepath. +// The root is typically the root of the exercise, and +// path is the absolute path to the file. +func NewDocument(root, path string) (Document, error) { + path, err := filepath.Rel(root, path) + if err != nil { + return Document{}, err + } + return Document{ + Root: root, + RelativePath: path, + }, nil +} + +// Filepath is the absolute path to the document on the filesystem. +func (doc Document) Filepath() string { + return filepath.Join(doc.Root, doc.RelativePath) +} + +// Path is the normalized path. +// It uses forward slashes regardless of the operating system. +func (doc Document) Path() string { + return filepath.ToSlash(doc.RelativePath) +} diff --git a/workspace/document_test.go b/workspace/document_test.go new file mode 100644 index 000000000..4e055df2f --- /dev/null +++ b/workspace/document_test.go @@ -0,0 +1,43 @@ +package workspace + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizedDocumentPath(t *testing.T) { + root, err := ioutil.TempDir("", "docpath") + assert.NoError(t, err) + defer os.RemoveAll(root) + + err = os.MkdirAll(filepath.Join(root, "subdirectory"), os.FileMode(0755)) + assert.NoError(t, err) + + testCases := []struct { + filepath string + path string + }{ + { + filepath: filepath.Join(root, "file.txt"), + path: "file.txt", + }, + { + filepath: filepath.Join(root, "subdirectory", "file.txt"), + path: "subdirectory/file.txt", + }, + } + + for _, tc := range testCases { + err = ioutil.WriteFile(tc.filepath, []byte("a file"), os.FileMode(0600)) + assert.NoError(t, err) + + doc, err := NewDocument(root, tc.filepath) + assert.NoError(t, err) + + assert.Equal(t, doc.Path(), tc.path) + } +} diff --git a/workspace/exercise.go b/workspace/exercise.go index 246cf364f..9c3815e66 100644 --- a/workspace/exercise.go +++ b/workspace/exercise.go @@ -8,9 +8,19 @@ import ( // Exercise is an implementation of a problem in a track. type Exercise struct { - Root string - Track string - Slug string + Root string + Track string + Slug string + Documents []Document +} + +// NewExerciseFromDir constructs an exercise given the exercise directory. +func NewExerciseFromDir(dir string) Exercise { + slug := filepath.Base(dir) + dir = filepath.Dir(dir) + track := filepath.Base(dir) + root := filepath.Dir(dir) + return Exercise{Root: root, Track: track, Slug: slug} } // Path is the normalized relative path. diff --git a/workspace/exercise_test.go b/workspace/exercise_test.go index 83b794064..c3d54a5d7 100644 --- a/workspace/exercise_test.go +++ b/workspace/exercise_test.go @@ -33,3 +33,12 @@ func TestHasMetadata(t *testing.T) { assert.NoError(t, err) assert.False(t, ok) } + +func TestNewFromDir(t *testing.T) { + dir := filepath.Join("something", "another", "whatever", "the-track", "the-exercise") + + exercise := NewExerciseFromDir(dir) + assert.Equal(t, filepath.Join("something", "another", "whatever"), exercise.Root) + assert.Equal(t, "the-track", exercise.Track) + assert.Equal(t, "the-exercise", exercise.Slug) +}