forked from exercism/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathworkspace.go
149 lines (129 loc) · 3.51 KB
/
workspace.go
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
}
if topInfo.Name() == "teams" {
subInfos, err := os.ReadDir(filepath.Join(ws.Dir, "teams"))
if err != nil {
return nil, err
}
for _, subInfo := range subInfos {
teamWs, err := New(filepath.Join(ws.Dir, "teams", subInfo.Name()))
if err != nil {
return nil, err
}
teamExercises, err := teamWs.PotentialExercises()
if err != nil {
return nil, err
}
exercises = append(exercises, teamExercises...)
}
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)
}
}