From 6a23a157d880302e4a19e3ee72a6f61d72773842 Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 10:45:20 +0530 Subject: [PATCH 01/11] recover_module added --- internal/exercises/catalog.yaml | 12 ++++++++ .../solutions/110_recover/recover.go | 30 +++++++++++++++++++ .../templates/110_recover/recover.go | 25 ++++++++++++++++ .../templates/110_recover/recover_test.go | 28 +++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 internal/exercises/solutions/110_recover/recover.go create mode 100644 internal/exercises/templates/110_recover/recover.go create mode 100644 internal/exercises/templates/110_recover/recover_test.go diff --git a/internal/exercises/catalog.yaml b/internal/exercises/catalog.yaml index 8d0a808..06ac22e 100644 --- a/internal/exercises/catalog.yaml +++ b/internal/exercises/catalog.yaml @@ -201,3 +201,15 @@ projects: - "Use Go's `time.Unix()` to convert an epoch to time." - "Use `t.Unix()` to convert time back to epoch." - "Remember Go’s `time.Parse` can help parse date strings." + +- slug: recover + title: Safe Panic Recovery + difficulty: medium + topics: ["panic", "recover", "defer"] + hints: + - hint_type: concept + text: The `recover()` function is ONLY useful inside a function that is executed by a `defer` statement. Outside of a deferred function, `recover` will return `nil` and have no effect. + - hint_type: structure + text: You must place a `defer` statement at the beginning of the `Run` function. The function passed to `defer` is where you should call `recover()`. + - hint_type: solution + text: "The core logic of the deferred function should look like this: `if r := recover(); r != nil { recoveredValue = r }`" \ No newline at end of file diff --git a/internal/exercises/solutions/110_recover/recover.go b/internal/exercises/solutions/110_recover/recover.go new file mode 100644 index 0000000..c506865 --- /dev/null +++ b/internal/exercises/solutions/110_recover/recover.go @@ -0,0 +1,30 @@ +package recover_exercise + +// DoWork simulates a function that might panic if the input is negative. +func DoWork(n int) { + if n < 0 { + // A panic occurs if n is negative + panic("input cannot be negative") + } + // Normal, non-panicking code path... +} + +// Run calls DoWork and uses defer/recover to safely handle any panic. +// It returns the recovered panic value or nil if no panic occurred. +func Run(n int) (recoveredValue interface{}) { + // The deferred function is executed just before Run returns. + defer func() { + // Call recover() to check if a panic has occurred. + if r := recover(); r != nil { + // If recover() returns a non-nil value (a panic occurred), + // assign it to the named return variable 'recoveredValue'. + recoveredValue = r + } + }() + + DoWork(n) + + // 'recoveredValue' is returned. It will be the panic value if + // a panic occurred and was recovered, or nil otherwise. + return recoveredValue +} \ No newline at end of file diff --git a/internal/exercises/templates/110_recover/recover.go b/internal/exercises/templates/110_recover/recover.go new file mode 100644 index 0000000..0404a26 --- /dev/null +++ b/internal/exercises/templates/110_recover/recover.go @@ -0,0 +1,25 @@ +package recover_exercise + +// DoWork simulates a function that might panic if the input is negative. +func DoWork(n int) { + if n < 0 { + // A panic occurs if n is negative + panic("input cannot be negative") + } + // Normal, non-panicking code path... +} + +// Run should call DoWork and safely recover from any panic +// that occurs during its execution, returning the recovered value. +// It should return nil if no panic occurred. +func Run(n int) (recoveredValue interface{}) { + // 1. Add your 'defer' statement here. + // 2. The deferred function should call recover() and assign the result + // to 'recoveredValue' if it is not nil. + + // Your code here: + + DoWork(n) + + return recoveredValue +} \ No newline at end of file diff --git a/internal/exercises/templates/110_recover/recover_test.go b/internal/exercises/templates/110_recover/recover_test.go new file mode 100644 index 0000000..ddf6a96 --- /dev/null +++ b/internal/exercises/templates/110_recover/recover_test.go @@ -0,0 +1,28 @@ +package recover_exercise + +import ( + "testing" +) + +func TestRun_NoPanic(t *testing.T) { + t.Parallel() + result := Run(10) + if result != nil { + t.Errorf("Run(10) should not panic and should return nil. Got: %v", result) + } +} + +func TestRun_WithPanic(t *testing.T) { + t.Parallel() + expectedPanicValue := "input cannot be negative" + result := Run(-5) + + if result == nil { + t.Errorf("Run(-5) should panic and recover, returning the panic value. Got nil") + } + + // Check if the recovered value is what we expected + if resultAsString, ok := result.(string); !ok || resultAsString != expectedPanicValue { + t.Errorf("Run(-5) recovered with unexpected value. Expected: %q, Got: %v (%T)", expectedPanicValue, result, result) + } +} \ No newline at end of file From 36e46df2e30fa347870858581f8609f69cab7a50 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Oct 2025 05:15:56 +0000 Subject: [PATCH 02/11] chore(format): auto-format code [skip ci] --- internal/exercises/solutions/110_recover/recover.go | 2 +- internal/exercises/templates/110_recover/recover.go | 2 +- internal/exercises/templates/110_recover/recover_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/exercises/solutions/110_recover/recover.go b/internal/exercises/solutions/110_recover/recover.go index c506865..f3dbaf1 100644 --- a/internal/exercises/solutions/110_recover/recover.go +++ b/internal/exercises/solutions/110_recover/recover.go @@ -27,4 +27,4 @@ func Run(n int) (recoveredValue interface{}) { // 'recoveredValue' is returned. It will be the panic value if // a panic occurred and was recovered, or nil otherwise. return recoveredValue -} \ No newline at end of file +} diff --git a/internal/exercises/templates/110_recover/recover.go b/internal/exercises/templates/110_recover/recover.go index 0404a26..017cdb3 100644 --- a/internal/exercises/templates/110_recover/recover.go +++ b/internal/exercises/templates/110_recover/recover.go @@ -22,4 +22,4 @@ func Run(n int) (recoveredValue interface{}) { DoWork(n) return recoveredValue -} \ No newline at end of file +} diff --git a/internal/exercises/templates/110_recover/recover_test.go b/internal/exercises/templates/110_recover/recover_test.go index ddf6a96..8b5cf3e 100644 --- a/internal/exercises/templates/110_recover/recover_test.go +++ b/internal/exercises/templates/110_recover/recover_test.go @@ -25,4 +25,4 @@ func TestRun_WithPanic(t *testing.T) { if resultAsString, ok := result.(string); !ok || resultAsString != expectedPanicValue { t.Errorf("Run(-5) recovered with unexpected value. Expected: %q, Got: %v (%T)", expectedPanicValue, result, result) } -} \ No newline at end of file +} From b09ddfd68a6fc9a426bb05b94282e761030e1bd2 Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 11:01:46 +0530 Subject: [PATCH 03/11] doucmentation added for recover exercise --- internal/exercises/solutions/110_recover/recover.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/exercises/solutions/110_recover/recover.go b/internal/exercises/solutions/110_recover/recover.go index c506865..39c013c 100644 --- a/internal/exercises/solutions/110_recover/recover.go +++ b/internal/exercises/solutions/110_recover/recover.go @@ -1,6 +1,9 @@ package recover_exercise // DoWork simulates a function that might panic if the input is negative. +// +// It is designed to be called by Run, which should demonstrate +// how to safely handle the panic using recover(). func DoWork(n int) { if n < 0 { // A panic occurs if n is negative From 787d661f461fbaf2bd0cca4725b3cf3706e7d87f Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 11:15:31 +0530 Subject: [PATCH 04/11] minor changes in catalog file recover --- internal/exercises/catalog.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exercises/catalog.yaml b/internal/exercises/catalog.yaml index 06ac22e..cb3527d 100644 --- a/internal/exercises/catalog.yaml +++ b/internal/exercises/catalog.yaml @@ -202,7 +202,7 @@ projects: - "Use `t.Unix()` to convert time back to epoch." - "Remember Go’s `time.Parse` can help parse date strings." -- slug: recover +- slug: 110_recover title: Safe Panic Recovery difficulty: medium topics: ["panic", "recover", "defer"] From 89ad5305cee126e2ac238e7ea39b820ceaf21af7 Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 15:53:39 +0530 Subject: [PATCH 05/11] minor edits in recover --- internal/exercises/catalog.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/exercises/catalog.yaml b/internal/exercises/catalog.yaml index cb3527d..0a708dd 100644 --- a/internal/exercises/catalog.yaml +++ b/internal/exercises/catalog.yaml @@ -192,20 +192,16 @@ projects: - Implement MarshalPerson to convert a struct into JSON using json.Marshal. - Implement UnmarshalPerson to convert a JSON string into a struct using json.Unmarshal. - Handle and return errors properly in both functions. - - slug: 109_epoch title: "Epoch Conversion" - difficulty: beginner - topics: ["time", "epoch", "unix"] + test_regex: ".*" hints: - "Use Go's `time.Unix()` to convert an epoch to time." - "Use `t.Unix()` to convert time back to epoch." - "Remember Go’s `time.Parse` can help parse date strings." - - slug: 110_recover title: Safe Panic Recovery - difficulty: medium - topics: ["panic", "recover", "defer"] + test_regex: ".*" hints: - hint_type: concept text: The `recover()` function is ONLY useful inside a function that is executed by a `defer` statement. Outside of a deferred function, `recover` will return `nil` and have no effect. From fd4124816c67dda215128d04cf45190cb8284e1d Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 15:58:34 +0530 Subject: [PATCH 06/11] copied the json files from the main branch --- internal/exercises/solutions/36_json/json.go | 2 +- internal/exercises/templates/36_json/json.go | 2 +- internal/exercises/templates/36_json/json_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/exercises/solutions/36_json/json.go b/internal/exercises/solutions/36_json/json.go index 80b284c..e3d3fd7 100644 --- a/internal/exercises/solutions/36_json/json.go +++ b/internal/exercises/solutions/36_json/json.go @@ -32,4 +32,4 @@ func UnmarshalPerson(jsonStr string) (Person, error) { return Person{}, err } return p, nil -} +} \ No newline at end of file diff --git a/internal/exercises/templates/36_json/json.go b/internal/exercises/templates/36_json/json.go index 737f7ba..ed133f4 100644 --- a/internal/exercises/templates/36_json/json.go +++ b/internal/exercises/templates/36_json/json.go @@ -21,4 +21,4 @@ func MarshalPerson(p Person) (string, error) { // UnmarshalPerson should convert a JSON string to a Person struct. func UnmarshalPerson(jsonStr string) (Person, error) { return Person{}, nil -} +} \ No newline at end of file diff --git a/internal/exercises/templates/36_json/json_test.go b/internal/exercises/templates/36_json/json_test.go index 7949e19..f4a45ae 100644 --- a/internal/exercises/templates/36_json/json_test.go +++ b/internal/exercises/templates/36_json/json_test.go @@ -30,4 +30,4 @@ func TestUnmarshalPerson(t *testing.T) { if p.Email != "golearn@example.com" { t.Errorf("Expected email 'golearn@example.com', got %s", p.Email) } -} +} \ No newline at end of file From 841947351818236fac079b8b82066a3bc24388f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Oct 2025 10:29:11 +0000 Subject: [PATCH 07/11] chore(format): auto-format code [skip ci] --- internal/exercises/solutions/36_json/json.go | 2 +- internal/exercises/templates/36_json/json.go | 2 +- internal/exercises/templates/36_json/json_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/exercises/solutions/36_json/json.go b/internal/exercises/solutions/36_json/json.go index e3d3fd7..80b284c 100644 --- a/internal/exercises/solutions/36_json/json.go +++ b/internal/exercises/solutions/36_json/json.go @@ -32,4 +32,4 @@ func UnmarshalPerson(jsonStr string) (Person, error) { return Person{}, err } return p, nil -} \ No newline at end of file +} diff --git a/internal/exercises/templates/36_json/json.go b/internal/exercises/templates/36_json/json.go index ed133f4..737f7ba 100644 --- a/internal/exercises/templates/36_json/json.go +++ b/internal/exercises/templates/36_json/json.go @@ -21,4 +21,4 @@ func MarshalPerson(p Person) (string, error) { // UnmarshalPerson should convert a JSON string to a Person struct. func UnmarshalPerson(jsonStr string) (Person, error) { return Person{}, nil -} \ No newline at end of file +} diff --git a/internal/exercises/templates/36_json/json_test.go b/internal/exercises/templates/36_json/json_test.go index f4a45ae..7949e19 100644 --- a/internal/exercises/templates/36_json/json_test.go +++ b/internal/exercises/templates/36_json/json_test.go @@ -30,4 +30,4 @@ func TestUnmarshalPerson(t *testing.T) { if p.Email != "golearn@example.com" { t.Errorf("Expected email 'golearn@example.com', got %s", p.Email) } -} \ No newline at end of file +} From dddd67d85c93d8de80b5a0379375e43dec81d9ab Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 16:25:18 +0530 Subject: [PATCH 08/11] minor edits in excercise.go --- internal/exercises/exercises.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/exercises/exercises.go b/internal/exercises/exercises.go index 7f588d6..46b99d9 100644 --- a/internal/exercises/exercises.go +++ b/internal/exercises/exercises.go @@ -124,6 +124,15 @@ func Get(slug string) (Exercise, error) { return ex, nil } } + // Fallback: if an embedded template or solution exists, synthesize an Exercise entry + if templateExists(slug) || SolutionExists(slug) { + return Exercise{ + Slug: slug, + Title: slug, + TestRegex: ".*", + Hints: nil, + }, nil + } return Exercise{}, fmt.Errorf("exercise not found: %s", slug) } From 559ac729e79b69e61602762e2e21136685dcb6c1 Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 16:43:57 +0530 Subject: [PATCH 09/11] added fallback for matadata title in exercises.go --- internal/exercises/exercises.go | 42 +++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/internal/exercises/exercises.go b/internal/exercises/exercises.go index 46b99d9..82316d5 100644 --- a/internal/exercises/exercises.go +++ b/internal/exercises/exercises.go @@ -7,6 +7,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" "sync" "gopkg.in/yaml.v3" @@ -126,11 +127,15 @@ func Get(slug string) (Exercise, error) { } // Fallback: if an embedded template or solution exists, synthesize an Exercise entry if templateExists(slug) || SolutionExists(slug) { + fmt.Fprintf(os.Stderr, + "Warning: exercise '%s' found in templates/solutions but missing from catalog.yaml\n", + slug, + ) return Exercise{ Slug: slug, - Title: slug, + Title: formatSlugAsTitle(slug), // e.g., "110 Recover" TestRegex: ".*", - Hints: nil, + Hints: []string{"This exercise is missing proper catalog metadata. Check documentation."}, }, nil } return Exercise{}, fmt.Errorf("exercise not found: %s", slug) @@ -196,4 +201,37 @@ func copyExerciseTemplate(slug string) error { }) } +func formatSlugAsTitle(slug string) string { + s := strings.TrimSpace(slug) + if s == "" { + return "Exercise" + } + parts := strings.Split(s, "_") + for i, p := range parts { + if p == "" { + continue + } + // Keep purely numeric segments as-is (e.g., "110") + isDigits := true + for _, r := range p { + if r < '0' || r > '9' { + isDigits = false + break + } + } + if isDigits { + parts[i] = p + continue + } + upper := strings.ToUpper(p) + switch upper { + case "JSON", "XML", "HTTP", "CLI", "KV", "ID", "URL", "IO": + parts[i] = upper + default: + parts[i] = strings.ToUpper(p[:1]) + strings.ToLower(p[1:]) + } + } + return strings.Join(parts, " ") +} + var ErrNoTemplates = errors.New("no templates found") From 3338c711aedcbbf983b045d83fa214d1fa868134 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Oct 2025 11:14:48 +0000 Subject: [PATCH 10/11] chore(format): auto-format code [skip ci] --- internal/exercises/exercises.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exercises/exercises.go b/internal/exercises/exercises.go index 82316d5..6a4b44b 100644 --- a/internal/exercises/exercises.go +++ b/internal/exercises/exercises.go @@ -133,7 +133,7 @@ func Get(slug string) (Exercise, error) { ) return Exercise{ Slug: slug, - Title: formatSlugAsTitle(slug), // e.g., "110 Recover" + Title: formatSlugAsTitle(slug), // e.g., "110 Recover" TestRegex: ".*", Hints: []string{"This exercise is missing proper catalog metadata. Check documentation."}, }, nil From 27c96b76977c7aa0e47b57b21e2dd82b40f4cc43 Mon Sep 17 00:00:00 2001 From: Omesh Mehta Date: Thu, 2 Oct 2025 16:46:47 +0530 Subject: [PATCH 11/11] minor edits --- internal/exercises/exercises.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exercises/exercises.go b/internal/exercises/exercises.go index 82316d5..6a4b44b 100644 --- a/internal/exercises/exercises.go +++ b/internal/exercises/exercises.go @@ -133,7 +133,7 @@ func Get(slug string) (Exercise, error) { ) return Exercise{ Slug: slug, - Title: formatSlugAsTitle(slug), // e.g., "110 Recover" + Title: formatSlugAsTitle(slug), // e.g., "110 Recover" TestRegex: ".*", Hints: []string{"This exercise is missing proper catalog metadata. Check documentation."}, }, nil