diff --git a/cmd/lesson.go b/cmd/lesson.go index b91cffc..7a1eeaa 100644 --- a/cmd/lesson.go +++ b/cmd/lesson.go @@ -45,7 +45,7 @@ var lessonCmd = &cobra.Command{ if n > len(lesson.Master.Lessons) || n == 0 { util.LogErrorAndExit(util.NonexistentLesson) } - err = lesson.Master.Run(n, 0) + err = lesson.Master.Run(n, 0, true) if err != nil { util.LogErrorAndExit(err) } diff --git a/lesson/lesson.go b/lesson/lesson.go index ba7406a..aa3fbc3 100644 --- a/lesson/lesson.go +++ b/lesson/lesson.go @@ -4,7 +4,6 @@ import ( "csclub-activities/util" "fmt" "github.com/fatih/color" - "github.com/mitchellh/go-wordwrap" ) const WordWrapLimit = 100 @@ -23,7 +22,15 @@ type master struct { Lessons []Lesson } -func (m *master) Run(lesson int, checkpoint int) error { +// on program startup, it checks whether there was a previously saved checkpoint. +// if there was, it checks if the condition to pass that checkpoint is valid. (ShouldPromote) +// if true, then the next checkpoint is displayed. otherwise, the same checkpoint is displayed. + +// Run +// setManually describes whether the checkpoint was loaded from a file or not. +// if it was not loaded (set manually), we shouldn't skip the current checkpoint +// (in other words, don't check the current checkpoint's ShouldPromote) +func (m *master) Run(lesson int, checkpoint int, setManually bool) error { if m.Lessons == nil || lesson >= len(m.Lessons) { @@ -36,28 +43,20 @@ func (m *master) Run(lesson int, checkpoint int) error { } cps := m.Lessons[lesson].Checkpoints - // TODO: remove this for loop for a single run conditional statement - for i := checkpoint; i < len(cps); i++ { - if cps[i].ShouldPromote() { - if i+1 < len(cps) { - break - } - i++ - continue - } + if !setManually && cps[checkpoint].ShouldPromote() && util.HasNext(checkpoint, cps) { + checkpoint++ + } - fmt.Printf("%-5s %s\n\n", - color.New(color.Bold).Add(color.FgGreen).Sprintf("%2d/%-2d", i+1, len(cps)), - color.New(color.Bold).Sprintf("%s", cps[i].Title), - ) - cps[i].Action() - fmt.Println("\n Note: to get back to the help screen, run: `csa help`") + fmt.Printf("%-5s %s\n\n", + color.New(color.Bold).Add(color.FgGreen).Sprintf("%2d/%-2d", checkpoint+1, len(cps)), + color.New(color.Bold).Sprintf("%s", cps[checkpoint].Title), + ) + cps[checkpoint].Action() + fmt.Println("\n Note: to get back to the help screen, run: `csa help`") - err := util.Save(uint8(lesson), uint8(checkpoint)) - if err != nil { - return err - } - break + err := util.Save(uint8(lesson), uint8(checkpoint)) + if err != nil { + return err } return nil } @@ -81,14 +80,3 @@ type ExpectantError struct { MatchesError func() bool Feedback string } - -func printPrompts(prompts []string) { - for x := range prompts { - if x == 0 { - fmt.Println(wordwrap.WrapString(prompts[x], WordWrapLimit)) - } else { - fmt.Println("\n" + wordwrap.WrapString(prompts[x], WordWrapLimit)) - } - } - -} diff --git a/lesson/one.go b/lesson/one.go index 9c606d6..24f4cad 100644 --- a/lesson/one.go +++ b/lesson/one.go @@ -1,9 +1,12 @@ package lesson import ( + "csclub-activities/util" "fmt" "github.com/mitchellh/go-wordwrap" "os" + "regexp" + "runtime" ) var lessonOne = &Lesson{ @@ -34,18 +37,24 @@ var lessonOne = &Lesson{ "If you come back to the same checkpoint, it means that you haven't met the conditions to move" + " on to the next checkpoint. Ensure that you:", - " 1. Have a version of Bash installed on your system\n" + - " 2. Are running and using Bash at this moment" + - " 3. Are executing this program (csa) within Bash", + orderedList([]string{ + "Have a version of Bash installed on your system", + "Are running and using Bash at this moment", + "[and] Are executing this program (csa) within Bash", + }), + + conditional(runtime.GOOS != "windows", + "If you are having trouble with this step because you are running a custom shell"+ + " (ex. fish), consider populating the `VALIDSHELL` environment variable before running csa again."), }) }, ShouldPromote: func() bool { - switch os.Getenv("SHELL") { - case "/usr/bin/bash", "/usr/bin/zsh", "/usr/bin/sh": - return true + matched, err := regexp.MatchString(`usr([/\\])bin([/\\])(ba|z|)sh`, os.Getenv("SHELL")) + if err != nil { + util.LogErrorAndExit(err) } // for those who run other shells like fish, you could just use the env VALIDSHELL - return os.Getenv("VALIDSHELL") != "" + return matched || os.Getenv("VALIDSHELL") != "" }, }, { diff --git a/lesson/strings.go b/lesson/strings.go new file mode 100644 index 0000000..c3e9c6a --- /dev/null +++ b/lesson/strings.go @@ -0,0 +1,41 @@ +package lesson + +import ( + "fmt" + "github.com/mitchellh/go-wordwrap" + "strings" +) + +func printPrompts(prompts []string) { + for i := range prompts { + if prompts[i] == "" { + continue + } + + n := "\n" + if i == 0 { + n = "" + } + fmt.Println(n + wordwrap.WrapString(prompts[i], WordWrapLimit)) + } +} + +func orderedList(prompts []string) string { + var s strings.Builder + for i := range prompts { + // after wrapping, add indents to any places where it wrapped + x := wordwrap.WrapString( + fmt.Sprintf(" %d. %s", i+1, prompts[i]), + WordWrapLimit) + x = strings.ReplaceAll(x, "\n", "\n ") + "\n" + s.WriteString(x) + } + return strings.TrimSuffix(s.String(), "\n") +} + +func conditional(f bool, prompt string) string { + if f { + return prompt + } + return "" +} diff --git a/main.go b/main.go index 398878b..95111f3 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { return } - err = lesson.Master.Run(int(information.LessonId), int(information.CheckpointID)) + err = lesson.Master.Run(int(information.LessonId), int(information.CheckpointID), false) if err != nil { util.LogErrorAndExit(err) } diff --git a/util/util.go b/util/util.go index 5835a6a..bfba8d0 100644 --- a/util/util.go +++ b/util/util.go @@ -27,3 +27,7 @@ func LogErrorAndExit(err error) { _, _ = c.Printf("Error: %v\n", err) os.Exit(1) } + +func HasNext[T any](position int, set []T) bool { + return position+1 < len(set) +}