Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/uuid v1.6.0
github.com/hexops/gotextdiff v1.0.3
github.com/manifoldco/promptui v0.9.0
github.com/mergestat/timediff v0.0.4
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mergestat/timediff v0.0.4 h1:NZ3sqG/6K9flhTubdltmRx3RBfIiYv6LsGP+4FlXMM8=
github.com/mergestat/timediff v0.0.4/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand Down
5 changes: 3 additions & 2 deletions internal/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/fatih/color"
"net/http"
"os"
"path"
"strings"
"time"

"github.com/fatih/color"
)

type releaseResponse struct {
Expand Down Expand Up @@ -57,7 +58,7 @@ func CheckForUpdate(currentVersion string) {
func printVersionNotice(currentVersion string, availableVersion string) {
c := color.New(color.FgHiYellow)
_, _ = c.Printf("A new version of the CLI is available: %s (current: %s)\n", availableVersion, currentVersion)
_, _ = c.Printf("Some features may not work correctly. Please update ASAP!\n")
_, _ = c.Printf("Some features may be missing or not work correctly. Please update soon!\n")
_, _ = c.Printf("See instructions at: %v\n", repoURL)
fmt.Println()
}
Expand Down
7 changes: 7 additions & 0 deletions tdl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ var app = &cli.App{
return newHandlers(c).List(c.Context)
},
},
{
Name: "checkout",
Usage: "checkout one of your past solutions for the current exercise",
Action: func(c *cli.Context) error {
return newHandlers(c).Checkout(c.Context)
},
},
{
Name: "clone",
Usage: "clone solution files to current directory",
Expand Down
52 changes: 50 additions & 2 deletions trainings/api/protobuf/server.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ service Trainings {
rpc Init(InitRequest) returns (InitResponse) {};

rpc GetTrainings(google.protobuf.Empty) returns (GetTrainingsResponse) {};
rpc StartTraining(StartTrainingRequest) returns (google.protobuf.Empty) {};
rpc StartTraining(StartTrainingRequest) returns (StartTrainingResponse) {};

rpc NextExercise(NextExerciseRequest) returns (NextExerciseResponse) {};

rpc VerifyExercise(VerifyExerciseRequest) returns (stream VerifyExerciseResponse) {};

rpc GetSolutions(GetSolutionsRequest) returns (GetSolutionsResponse) {};
rpc GetSolutionFiles(GetSolutionFilesRequest) returns (GetSolutionFilesResponse) {};
rpc GetAllSolutionFiles(GetAllSolutionFilesRequest) returns (GetAllSolutionFilesResponse) {};

rpc GetExercises(GetExercisesRequest) returns (GetExercisesResponse) {};
rpc GetExercise(GetExerciseRequest) returns (NextExerciseResponse) {};
Expand Down Expand Up @@ -45,7 +47,7 @@ message StartTrainingRequest {
}

message StartTrainingResponse {

bool previous_solutions_available = 1;
}

message NextExerciseRequest {
Expand Down Expand Up @@ -86,6 +88,28 @@ message NextExerciseResponse {
google.protobuf.Timestamp next_batch_date = 8;
}

message ExerciseSolution {
string exercise_id = 1;
string dir = 2;
repeated File files = 3;
bool is_text_only = 4;
bool is_optional = 5;

Module module = 6;
Exercise exercise = 7;

message Module {
string id = 1;
string name = 2;
}

message Exercise {
string id = 1;
Module module = 2;
string name = 3;
}
}

message NextExercise {
string dir = 1;
repeated File files_to_create = 2;
Expand Down Expand Up @@ -142,6 +166,15 @@ message GetSolutionFilesResponse {
repeated File files_to_create = 4;
}

message GetAllSolutionFilesRequest {
string training_name = 1;
string token = 2;
}

message GetAllSolutionFilesResponse {
repeated ExerciseSolution solutions = 1;
}

message GetExercisesRequest {
string training_name = 1;
string token = 2;
Expand Down Expand Up @@ -188,3 +221,18 @@ message CanSkipExerciseResponse {
bool can_skip = 1;
bool can_skip_all_optional = 2;
}

message GetSolutionsRequest {
string exercise_id = 1;
string token = 2;
}

message GetSolutionsResponse {
message Solution {
string verification_id = 1;
bool successful = 2;
google.protobuf.Timestamp executed_at = 3;
}

repeated Solution solutions = 1;
}
93 changes: 93 additions & 0 deletions trainings/checkout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package trainings

import (
"context"
"fmt"

"github.com/ThreeDotsLabs/cli/trainings/config"
"github.com/ThreeDotsLabs/cli/trainings/files"
"github.com/ThreeDotsLabs/cli/trainings/genproto"
"github.com/manifoldco/promptui"
"github.com/mergestat/timediff"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

func (h *Handlers) Checkout(ctx context.Context) error {
trainingRoot, err := h.config.FindTrainingRoot()
if errors.Is(err, config.TrainingRootNotFoundError) {
h.printNotInATrainingDirectory()
return nil
}

trainingRootFs := newTrainingRootFs(trainingRoot)

resp, err := h.newGrpcClient().GetSolutions(ctx, &genproto.GetSolutionsRequest{
ExerciseId: h.config.ExerciseConfig(trainingRootFs).ExerciseID,
Token: h.config.GlobalConfig().Token,
})
if err != nil {
return fmt.Errorf("failed to get solutions: %w", err)
}

logrus.WithFields(logrus.Fields{
"resp": resp,
"err": err,
}).Debug("Received solutions from server")

items := []string{"(cancel)"}
for _, solution := range resp.Solutions {
text := ""
if solution.Successful {
text += "✅"
} else {
text += "❌"
}
text += " "
text += solution.VerificationId
text += " "
text += timediff.TimeDiff(solution.ExecutedAt.AsTime())

items = append(items, text)
}

selectUI := promptui.Select{
Label: "Select a solution to checkout",
Items: items,
Size: 10,
Templates: &promptui.SelectTemplates{
Label: "{{ . }}",
Active: "{{ . | cyan }}",
Inactive: "{{ . }}",
},
HideSelected: true,
}

index, _, err := selectUI.Run()
if err != nil {
return err
}

if index == 0 {
fmt.Println("Cancelled")
return nil
}

getResp, err := h.newGrpcClient().GetSolutionFiles(ctx, &genproto.GetSolutionFilesRequest{
ExecutionId: resp.Solutions[index-1].VerificationId,
})
if err != nil {
return fmt.Errorf("failed to get solution files: %w", err)
}

if err := h.writeExerciseFiles(files.NewFilesWithConfig(true, true), getSolutionFilesToExerciseSolution(getResp), trainingRootFs); err != nil {
return err
}

err = addModuleToWorkspace(trainingRoot, getResp.Dir)
if err != nil {
logrus.WithError(err).Warn("Failed to add module to workspace")
}

return nil
}
23 changes: 13 additions & 10 deletions trainings/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"path"

"github.com/ThreeDotsLabs/cli/trainings/config"
"github.com/ThreeDotsLabs/cli/trainings/files"
"github.com/ThreeDotsLabs/cli/trainings/genproto"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand All @@ -33,7 +34,7 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri

absoluteDirToClone = path.Join(absoluteDirToClone, directory)

if _, err := h.startTraining(ctx, resp.TrainingName, absoluteDirToClone); err != nil {
if _, _, err := h.startTraining(ctx, resp.TrainingName, absoluteDirToClone); err != nil {
return err
}

Expand All @@ -43,15 +44,7 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri
return errors.Wrap(err, "can't write training config")
}

files := &genproto.NextExerciseResponse{
TrainingStatus: genproto.NextExerciseResponse_IN_PROGRESS,
Dir: resp.Dir,
ExerciseId: resp.ExerciseId,
FilesToCreate: resp.FilesToCreate,
IsTextOnly: false,
}

if err := h.writeExerciseFiles(files, trainingRootFs); err != nil {
if err := h.writeExerciseFiles(files.NewFiles(), getSolutionFilesToExerciseSolution(resp), trainingRootFs); err != nil {
return err
}

Expand All @@ -62,3 +55,13 @@ func (h *Handlers) Clone(ctx context.Context, executionID string, directory stri

return nil
}

func getSolutionFilesToExerciseSolution(resp *genproto.GetSolutionFilesResponse) *genproto.ExerciseSolution {
return &genproto.ExerciseSolution{
ExerciseId: resp.ExerciseId,
Dir: resp.Dir,
Files: resp.FilesToCreate,
IsTextOnly: false,
IsOptional: false,
}
}
10 changes: 10 additions & 0 deletions trainings/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import (
type Files struct {
stdin io.Reader
stdout io.Writer

deleteUnusedFiles bool
showFullDiff bool
}

func NewFiles() Files {
return NewFilesWithStdOuts(os.Stdin, os.Stdout)
}

func NewFilesWithConfig(deleteUnusedFiles bool, showFullDiff bool) Files {
f := NewFiles()
f.deleteUnusedFiles = deleteUnusedFiles
f.showFullDiff = showFullDiff
return f
}

func NewFilesWithStdOuts(stdin io.Reader, stdout io.Writer) Files {
return Files{
stdin: stdin,
Expand Down
Loading