From 556b3404e9b5805c4aef9e552cd89ddd61a4f7e0 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sat, 11 Aug 2018 10:42:56 -0600 Subject: [PATCH 1/7] Create exercise from path to exercise directory Given the path to an exercise directory on the filesystem, determine the root, the track, and the exercise slug. --- workspace/exercise.go | 9 +++++++++ workspace/exercise_test.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/workspace/exercise.go b/workspace/exercise.go index 246cf364..2c5cf607 100644 --- a/workspace/exercise.go +++ b/workspace/exercise.go @@ -13,6 +13,15 @@ type Exercise struct { Slug string } +// 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. // It always has forward slashes, regardless // of the operating system. diff --git a/workspace/exercise_test.go b/workspace/exercise_test.go index 83b79406..c3d54a5d 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) +} From 49c6c727b9d4be8412ee255744e3ac62d05423e4 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sat, 11 Aug 2018 15:44:30 -0600 Subject: [PATCH 2/7] Add documents to Exercise type --- workspace/document.go | 29 +++++++++++++++++++++++++++++ workspace/document_test.go | 30 ++++++++++++++++++++++++++++++ workspace/exercise.go | 7 ++++--- 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 workspace/document.go create mode 100644 workspace/document_test.go diff --git a/workspace/document.go b/workspace/document.go new file mode 100644 index 00000000..b44c1345 --- /dev/null +++ b/workspace/document.go @@ -0,0 +1,29 @@ +package workspace + +import ( + "os" + "path/filepath" + "strings" +) + +// Document is a file in a directory. +type Document struct { + Root string + Filepath string +} + +// NewDocument creates a document from a filepath. +func NewDocument(root, file string) Document { + return Document{ + Root: root, + Filepath: file, + } +} + +// Path is the normalized path. +// It uses forward slashes regardless of the operating system. +func (doc Document) Path() string { + path := strings.Replace(doc.Filepath, doc.Root, "", 1) + path = strings.TrimLeft(path, string(os.PathSeparator)) + return filepath.ToSlash(path) +} diff --git a/workspace/document_test.go b/workspace/document_test.go new file mode 100644 index 00000000..b6882f3a --- /dev/null +++ b/workspace/document_test.go @@ -0,0 +1,30 @@ +package workspace + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizedDocumentPath(t *testing.T) { + root := filepath.Join("the", "root", "path", "the-track", "the-exercise") + 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 { + doc := NewDocument(root, tc.filepath) + assert.Equal(t, tc.path, doc.Path()) + } +} diff --git a/workspace/exercise.go b/workspace/exercise.go index 2c5cf607..9c3815e6 100644 --- a/workspace/exercise.go +++ b/workspace/exercise.go @@ -8,9 +8,10 @@ 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. From 0385ab89526cd4e028d6d16177001d71db954a1e Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sat, 11 Aug 2018 16:06:36 -0600 Subject: [PATCH 3/7] Use Exercise type in submit command This normalizes the file sent to the server to use forward slashes. --- cmd/submit.go | 19 ++++++++----------- cmd/submit_test.go | 4 ++-- workspace/exercise.go | 5 +++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index e468fe7d..fc0c1377 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,10 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { fmt.Fprintf(Err, msg, file) continue } - paths = append(paths, file) + exercise.Documents = append(exercise.Documents, exercise.NewDocument(file)) } - if len(paths) == 0 { + if len(exercise.Documents) == 0 { msg := ` No files found to submit. @@ -176,18 +177,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 f4b2e781..556c3d39 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/exercise.go b/workspace/exercise.go index 9c3815e6..469b9775 100644 --- a/workspace/exercise.go +++ b/workspace/exercise.go @@ -59,3 +59,8 @@ func (e Exercise) HasMetadata() (bool, error) { } return false, err } + +// NewDocument creates a document relative to the exercise. +func (e Exercise) NewDocument(file string) Document { + return NewDocument(e.Filepath(), file) +} From 5ed77dd5d593265c227c1a232f739aeef6254880 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 14 Aug 2018 17:18:27 -0600 Subject: [PATCH 4/7] Remove unnecessary indirection on Exercise type --- cmd/submit.go | 2 +- workspace/exercise.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index fc0c1377..ed54202e 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -162,7 +162,7 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { fmt.Fprintf(Err, msg, file) continue } - exercise.Documents = append(exercise.Documents, exercise.NewDocument(file)) + exercise.Documents = append(exercise.Documents, workspace.NewDocument(exercise.Filepath(), file)) } if len(exercise.Documents) == 0 { diff --git a/workspace/exercise.go b/workspace/exercise.go index 469b9775..9c3815e6 100644 --- a/workspace/exercise.go +++ b/workspace/exercise.go @@ -59,8 +59,3 @@ func (e Exercise) HasMetadata() (bool, error) { } return false, err } - -// NewDocument creates a document relative to the exercise. -func (e Exercise) NewDocument(file string) Document { - return NewDocument(e.Filepath(), file) -} From efe66bdae49b716202fa651458d271c286593f1a Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 14 Aug 2018 17:32:08 -0600 Subject: [PATCH 5/7] Clarify doc comment for NewDocument --- workspace/document.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/workspace/document.go b/workspace/document.go index b44c1345..86ea5df1 100644 --- a/workspace/document.go +++ b/workspace/document.go @@ -13,10 +13,12 @@ type Document struct { } // NewDocument creates a document from a filepath. -func NewDocument(root, file string) Document { +// The root is typically the root of the exercise, and +// path is the relative path to the file within the root directory. +func NewDocument(root, path string) Document { return Document{ Root: root, - Filepath: file, + Filepath: path, } } From e5a7345e1aff698d74d9ce095050b4788d713a64 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 14 Aug 2018 17:35:42 -0600 Subject: [PATCH 6/7] Replace gnarly logic with standard library call --- cmd/submit.go | 8 ++++++-- workspace/document.go | 33 ++++++++++++++++++--------------- workspace/document_test.go | 19 ++++++++++++++++--- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index ed54202e..9952b5fd 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -162,7 +162,11 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { fmt.Fprintf(Err, msg, file) continue } - exercise.Documents = append(exercise.Documents, workspace.NewDocument(exercise.Filepath(), file)) + doc, err := workspace.NewDocument(exercise.Filepath(), file) + if err != nil { + return err + } + exercise.Documents = append(exercise.Documents, doc) } if len(exercise.Documents) == 0 { @@ -178,7 +182,7 @@ func runSubmit(cfg config.Config, flags *pflag.FlagSet, args []string) error { writer := multipart.NewWriter(body) for _, doc := range exercise.Documents { - file, err := os.Open(doc.Filepath) + file, err := os.Open(doc.Filepath()) if err != nil { return err } diff --git a/workspace/document.go b/workspace/document.go index 86ea5df1..4b291513 100644 --- a/workspace/document.go +++ b/workspace/document.go @@ -1,31 +1,34 @@ package workspace -import ( - "os" - "path/filepath" - "strings" -) +import "path/filepath" // Document is a file in a directory. type Document struct { - Root string - Filepath string + Root string + RelativePath string } -// NewDocument creates a document from a filepath. +// NewDocument creates a document from a relative filepath. // The root is typically the root of the exercise, and // path is the relative path to the file within the root directory. -func NewDocument(root, path string) Document { - return Document{ - Root: root, - Filepath: path, +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 { - path := strings.Replace(doc.Filepath, doc.Root, "", 1) - path = strings.TrimLeft(path, string(os.PathSeparator)) - return filepath.ToSlash(path) + return filepath.ToSlash(doc.RelativePath) } diff --git a/workspace/document_test.go b/workspace/document_test.go index b6882f3a..4e055df2 100644 --- a/workspace/document_test.go +++ b/workspace/document_test.go @@ -1,6 +1,8 @@ package workspace import ( + "io/ioutil" + "os" "path/filepath" "testing" @@ -8,7 +10,13 @@ import ( ) func TestNormalizedDocumentPath(t *testing.T) { - root := filepath.Join("the", "root", "path", "the-track", "the-exercise") + 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 @@ -24,7 +32,12 @@ func TestNormalizedDocumentPath(t *testing.T) { } for _, tc := range testCases { - doc := NewDocument(root, tc.filepath) - assert.Equal(t, tc.path, doc.Path()) + 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) } } From 60b6ed7bf8c22107e407983043adfd59c1a7e5ab Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 20 Aug 2018 13:13:44 -0600 Subject: [PATCH 7/7] Fix incorrect doc comment for NewDocument --- workspace/document.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace/document.go b/workspace/document.go index 4b291513..52b1264d 100644 --- a/workspace/document.go +++ b/workspace/document.go @@ -8,9 +8,9 @@ type Document struct { RelativePath string } -// NewDocument creates a document from a relative filepath. +// NewDocument creates a document from the filepath. // The root is typically the root of the exercise, and -// path is the relative path to the file within the root directory. +// path is the absolute path to the file. func NewDocument(root, path string) (Document, error) { path, err := filepath.Rel(root, path) if err != nil {