forked from exercism/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworkspace.go
More file actions
127 lines (111 loc) · 3.04 KB
/
workspace.go
File metadata and controls
127 lines (111 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package workspace
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
var errMissingMetadata = errors.New("no exercise metadata file found")
// IsMissingMetadata verifies the type of error.
func IsMissingMetadata(err error) bool {
return err == errMissingMetadata
}
// 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.
type Workspace struct {
Dir string
}
// New returns a configured workspace.
func New(dir string) (Workspace, error) {
_, err := os.Lstat(dir)
if err != nil {
return Workspace{}, err
}
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
return Workspace{}, err
}
return Workspace{Dir: dir}, nil
}
// PotentialExercises are a first-level guess at the user's exercises.
// It looks at the workspace structurally, and guesses based on
// the location of the directory. E.g. any top level directory
// within the workspace (except 'users') is assumed to be a
// track, and any directory within there again is assumed to
// be an exercise.
func (ws Workspace) PotentialExercises() ([]Exercise, error) {
exercises := []Exercise{}
topInfos, err := os.ReadDir(ws.Dir)
if err != nil {
return nil, err
}
for _, topInfo := range topInfos {
if !topInfo.IsDir() {
continue
}
if topInfo.Name() == "users" {
continue
}
subInfos, err := os.ReadDir(filepath.Join(ws.Dir, topInfo.Name()))
if err != nil {
return nil, err
}
for _, subInfo := range subInfos {
if !subInfo.IsDir() {
continue
}
exercises = append(exercises, Exercise{Track: topInfo.Name(), Slug: subInfo.Name(), Root: ws.Dir})
}
}
return exercises, nil
}
// Exercises returns the user's exercises within the workspace.
// This doesn't find legacy exercises where the metadata is missing.
func (ws Workspace) Exercises() ([]Exercise, error) {
candidates, err := ws.PotentialExercises()
if err != nil {
return nil, err
}
exercises := make([]Exercise, 0, len(candidates))
for _, candidate := range candidates {
ok, err := candidate.HasMetadata()
if err != nil {
return nil, err
}
if ok {
exercises = append(exercises, candidate)
}
}
return exercises, nil
}
// ExerciseDir determines the root directory of an exercise.
// This is the directory that contains the exercise metadata file.
func (ws Workspace) ExerciseDir(s string) (string, error) {
if !strings.HasPrefix(s, ws.Dir) {
var err = fmt.Errorf("not in workspace")
if runtime.GOOS == "darwin" {
err = fmt.Errorf("%w: directory location may be case sensitive: workspace directory: %s, "+
"submit path: %s", err, ws.Dir, s)
}
return "", err
}
path := s
for {
if path == ws.Dir {
return "", errMissingMetadata
}
if _, err := os.Lstat(path); os.IsNotExist(err) {
return "", err
}
if _, err := os.Lstat(filepath.Join(path, metadataFilepath)); err == nil {
return path, nil
}
if _, err := os.Lstat(filepath.Join(path, legacyMetadataFilename)); err == nil {
return path, nil
}
path = filepath.Dir(path)
}
}