Skip to content

Commit f179129

Browse files
authored
Add command to create a .md file including the entire contents (#156)
1 parent 32ff2cd commit f179129

19 files changed

+234
-27
lines changed

.github/cmd/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package main
22

3-
import "github.com/spring1843/go-dsa/.github/cmd/cmd"
3+
import "github.com/spring1843/go-dsa/.github/cmd/pkg/cmd"
44

55
func main() {
66
cmd.Execute()

.github/cmd/cmd/count_problems.go renamed to .github/cmd/pkg/cmd/count_problems.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"path/filepath"
88

9+
"github.com/spring1843/go-dsa/.github/cmd/pkg/problems"
10+
911
"github.com/spf13/cobra"
1012
)
1113

@@ -20,7 +22,7 @@ var countRehearsalsCommand = &cobra.Command{
2022
}
2123

2224
count := 0
23-
for _, section := range sections {
25+
for _, section := range problems.OrderedSections {
2426
matches, err := filepath.Glob(filepath.Join(dir, section, "*_test.go"))
2527
if err != nil {
2628
log.Fatalf("Error while globbing: %s", err)

.github/cmd/cmd/export_md.go renamed to .github/cmd/pkg/cmd/export_md.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"path/filepath"
88

9+
"github.com/spring1843/go-dsa/.github/cmd/pkg/problems"
10+
911
"github.com/spf13/cobra"
1012
)
1113

@@ -19,21 +21,26 @@ var exportMDCommand = &cobra.Command{
1921
log.Fatalf("Error finding current directory: %s", err)
2022
}
2123

22-
allFiles := []string{
24+
nonSectionMDFiles := []string{
2325
"README.md",
2426
"preface.md",
2527
"complexity.md",
2628
}
27-
for _, section := range sections {
28-
allFiles = append(allFiles, filepath.Join(section, "README.md"))
29-
}
3029

31-
for _, file := range allFiles {
30+
for _, file := range nonSectionMDFiles {
3231
content, err := os.ReadFile(filepath.Join(dir, file))
3332
if err != nil {
3433
log.Fatalf("Error reading file: %s", err)
3534
}
3635
fmt.Println(string(content))
3736
}
37+
38+
for _, section := range problems.OrderedSections {
39+
parsedSection, err := problems.ParseSection(dir, section)
40+
if err != nil {
41+
log.Fatalf("Error parsing section: %s", err)
42+
}
43+
fmt.Println(parsedSection)
44+
}
3845
},
3946
}

.github/cmd/cmd/random_challenge.go renamed to .github/cmd/pkg/cmd/random_challenge.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"path/filepath"
88

99
"github.com/spf13/cobra"
10+
"github.com/spring1843/go-dsa/.github/cmd/pkg/problems"
1011
)
1112

1213
const (
@@ -39,7 +40,7 @@ var randomChallengeCommand = &cobra.Command{
3940
}
4041

4142
allChallenges := []string{}
42-
for _, section := range sections {
43+
for _, section := range problems.OrderedSections {
4344
matches, err := filepath.Glob(filepath.Join(dir, section, "*_test.go"))
4445
if err != nil {
4546
log.Fatalf("Error while globbing: %s", err)
File renamed without changes.

.github/cmd/pkg/problems/parse.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package problems
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"regexp"
10+
)
11+
12+
func ParseSection(dir, section string) (string, error) {
13+
content, err := os.ReadFile(filepath.Join(dir, section, "README.md"))
14+
if err != nil {
15+
return "", fmt.Errorf("error reading file: %w", err)
16+
}
17+
18+
preparedContent, err := replaceRehearsal(string(content), dir, section)
19+
if err != nil {
20+
return "", fmt.Errorf("error replacing the rehearsal section in file: %w", err)
21+
}
22+
23+
return preparedContent, nil
24+
}
25+
26+
func replaceRehearsal(input, dir, section string) (string, error) {
27+
rehearsalRegex := regexp.MustCompile(`(?s)(## Rehearsal\n.*?)(\n##|\z)`)
28+
29+
match := rehearsalRegex.FindStringSubmatch(input)
30+
if match == nil {
31+
return "", errors.New("no rehearsal section found")
32+
}
33+
34+
rehearsalContent := match[1]
35+
newRehearsals, err := newRehearsalEntry(rehearsalContent)
36+
if err != nil {
37+
return "", fmt.Errorf("error parsing rehearsal entry: %w", err)
38+
}
39+
if len(newRehearsals) == 0 {
40+
log.Printf("no rehearsals found %s, %s", section, rehearsalContent)
41+
}
42+
replacement := fmt.Sprintf("## Rehearsal\n%s", stringRehearsalEntries(dir, section, newRehearsals))
43+
return rehearsalRegex.ReplaceAllString(input, replacement), nil
44+
}

.github/cmd/pkg/problems/rehearsal.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package problems
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
type rehearsalEntry struct {
12+
Name string
13+
TestFileName string
14+
SolutionFileName string
15+
}
16+
17+
func (r *rehearsalEntry) String() string {
18+
return fmt.Sprintf("### %s\n", r.Name)
19+
}
20+
21+
/*
22+
newRehearsalEntry parses the rehearsal section of the README.md file that looks like:
23+
24+
## Rehearsal
25+
26+
* [Some Name 1](./problem_test1.go), [Solution](./solution1.go)
27+
* [Some Name 2](./problem_test2.go), [Solution](./solution2.go).
28+
*/
29+
func newRehearsalEntry(input string) ([]rehearsalEntry, error) {
30+
lines := strings.Split(input, "\n")
31+
entries := []rehearsalEntry{}
32+
re := regexp.MustCompile(`\* \[([^\]]+)\]\(\.\/([^\)]+)\), \[Solution\]\(\.\/([^\)]+)\)`)
33+
34+
for _, line := range lines {
35+
line = strings.TrimSpace(line)
36+
if line == "" || strings.HasPrefix(line, "##") {
37+
continue // Skip empty lines and heading lines
38+
}
39+
matches := re.FindStringSubmatch(line)
40+
if len(matches) != 4 {
41+
return nil, fmt.Errorf("invalid line format: %s, %d", line, len(matches))
42+
}
43+
44+
entry := rehearsalEntry{
45+
Name: matches[1],
46+
TestFileName: matches[2],
47+
SolutionFileName: matches[3],
48+
}
49+
entries = append(entries, entry)
50+
}
51+
52+
return entries, nil
53+
}
54+
55+
func stringRehearsalEntries(dir, section string, entries []rehearsalEntry) string {
56+
output := ""
57+
for _, entry := range entries {
58+
output += fmt.Sprintf("\n### %s\n", entry.Name)
59+
60+
testFileContent, err := os.ReadFile(filepath.Join(dir, section, entry.TestFileName))
61+
if err != nil {
62+
output += fmt.Sprintf("Error reading test file: %s\n", err)
63+
continue
64+
}
65+
output += "```GO\n" + string(testFileContent) + "\n```\n"
66+
}
67+
68+
output += "\n## Rehearsal Solutions\n"
69+
70+
for _, entry := range entries {
71+
output += fmt.Sprintf("\n### %s\n", entry.Name)
72+
73+
testFileContent, err := os.ReadFile(filepath.Join(dir, section, entry.SolutionFileName))
74+
if err != nil {
75+
output += fmt.Sprintf("Error reading test file: %s\n", err)
76+
continue
77+
}
78+
output += "```GO\n" + string(testFileContent) + "\n```\n"
79+
}
80+
81+
return output
82+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package problems
2+
3+
import "testing"
4+
5+
func TestNewRehearsalEntry(t *testing.T) {
6+
input := `## Rehearsal
7+
8+
* [Bubble Sort](./bubble_sort_test.go), [Solution](./bubble_sort.go)
9+
* [Reverse Array In-place](./reverse_inplace_test.go), [Solution](./reverse_inplace.go)
10+
* [Add Two Numbers](./add_two_numbers_test.go), [Solution](./add_two_numbers.go)
11+
* [Find Duplicate in Array](./find_duplicate_in_array_test.go), [Solution](./find_duplicate_in_array.go)
12+
* [Zero Sum Triplets](./zero_sum_triplets_test.go), [Solution](./zero_sum_triplets.go)
13+
* [Product of All Other Elements](./product_of_all_other_elements_test.go), [Solution](./product_of_all_other_elements.go)`
14+
15+
entries, err := newRehearsalEntry(input)
16+
if err != nil {
17+
t.Fatalf("unexpected error: %v", err)
18+
}
19+
20+
if len(entries) != 6 {
21+
t.Fatalf("expected 5 entries, got %d", len(entries))
22+
}
23+
24+
strOutput := stringRehearsalEntries(entries)
25+
if strOutput == "" {
26+
t.Fatal("expected non-empty string, got empty string")
27+
}
28+
}

.github/cmd/cmd/sections.go renamed to .github/cmd/pkg/problems/sections.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
package cmd
1+
package problems
22

3-
var sections = []string{
3+
// OrderedSections list of sections in go-dsa corresponding to directory names.
4+
var OrderedSections = []string{
45
"array",
56
"strings",
67
"linkedlist",

.github/workflows/tests.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
steps:
3333
- uses: actions/checkout@v4
3434
- uses: actions/setup-go@v5
35-
- run: go test -v -coverprofile=profile.cov ./...
35+
- run: make ci
3636
- uses: shogo82148/actions-goveralls@v1
3737
with:
3838
path-to-profile: profile.cov

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ profile.cov
33
.pre-commit-config.yaml
44
.vscode
55
.idea
6+
bin

Makefile

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
all: build
2+
3+
build:
4+
mkdir -p ./bin
5+
go run ./.github/cmd/main.go export-md > ./bin/go-dsa.md
6+
7+
test:
8+
go test --race -v -coverprofile=profile.cov ./...
9+
10+
clean:
11+
go clean
12+
rm -f ./bin/*
13+
14+
fmt:
15+
go fmt ./...
16+
gofmt -w -s -r 'interface{} -> any' .
17+
18+
deps:
19+
go mod tidy
20+
go mod download
21+
22+
vet:
23+
go vet ./...
24+
25+
ci: fmt vet test build clean
26+
27+
.PHONY: all build run test bench clean fmt deps update

array/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,5 @@ Arrays are used wherever sequential data or more than one piece of data is neede
9393
* [Product of All Other Elements](./product_of_all_other_elements_test.go), [Solution](./product_of_all_other_elements.go)
9494
* [Equal Sum Sub-arrays](./equal_sum_subarrays_test.go), [Solution](./equal_sum_subarrays.go)
9595
* [Rotate K Times](./rotate_k_steps_test.go), [Solution](./rotate_k_steps.go)
96-
* [Bubble Sort](./bubble_sort_test.go), [Solution](bubble_sort.go)
96+
* [Bubble Sort](./bubble_sort_test.go), [Solution](./bubble_sort.go)
9797
* [Insertion Sort](./insertion_sort_test.go), [Solution](./insertion_sort.go)

backtracking/README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ Backtracking is widely used to solve board games and computers use it to select
2323

2424
## Rehearsal
2525

26-
## Permutations
27-
28-
* [Permutations](./permutations_test.go), [Solution](./permutations.go)
26+
* [Permutations](./permutations_test.go), [Solution](./permutations.go)
2927
* [Generate Parentheses](./generate_parentheses_test.go), [Solution](./generate_parentheses.go)
3028
* [Phone Letter Combinations](./phone_letter_combinations_test.go), [Solution](./phone_letter_combinations.go)
3129
* [Maze](./maze_test.go), [Solution](./maze.go)

bit/README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ Negation can be used to invert a set of flags or find the two's complement of a
108108

109109
## Rehearsal
110110

111-
* [Division without multiplication or division operators](division_without_operators_test.go), [Solution](division_without_operators.go)
112-
* [Middle without division](middle_without_division_test.go), [Solution](middle_without_division.go)
113-
* [Addition without using plus (+) or any other arithmetic operators](addition_without_operators_test.go), [Solution](addition_without_operators.go)
111+
* [Division without multiplication or division operators](./division_without_operators_test.go), [Solution](./division_without_operators.go)
112+
* [Middle without division](./middle_without_division_test.go), [Solution](./middle_without_division.go)
113+
* [Addition without using plus (+) or any other arithmetic operators](./addition_without_operators_test.go), [Solution](./addition_without_operators.go)
114114
* [Power of Two](./is_power_of_two_test.go), [Solution](./is_power_of_two.go)
115-
* [Maximum without if conditions](max_function_without_conditions_test.go), [Solution](max_function_without_conditions.go)
116-
* [Oddly Repeated Number](oddly_repeated_number_test.go), [Solution](oddly_repeated_number.go)
115+
* [Maximum without if conditions](./max_function_without_conditions_test.go), [Solution](./max_function_without_conditions.go)
116+
* [Oddly Repeated Number](./oddly_repeated_number_test.go), [Solution](./oddly_repeated_number.go)

dnc/README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ DNC algorithms are suitable for solving problems that can be divided into smalle
9292

9393
## Rehearsal
9494

95-
* [Binary Search](binary_search_test.go), [Solution](binary_search.go)
96-
* [Square Root with Binary Search](square_root_test.go), [Solution](square_root.go)
97-
* [Rate Limit](rate_limit_test.go), [Solution](rate_limit.go)
98-
* [Towers of Hanoi](towers_of_hanoi_test.go), [Solution](towers_of_hanoi.go)
99-
* [Merge Sort](merge_sort_test.go), [Solution](merge_sort.go)
100-
* [Quick Sort](quick_sort_test.go), [Solution](quick_sort.go)
95+
* [Binary Search](./binary_search_test.go), [Solution](./binary_search.go)
96+
* [Square Root with Binary Search](./square_root_test.go), [Solution](./square_root.go)
97+
* [Rate Limit](./rate_limit_test.go), [Solution](./rate_limit.go)
98+
* [Towers of Hanoi](./towers_of_hanoi_test.go), [Solution](./towers_of_hanoi.go)
99+
* [Merge Sort](./merge_sort_test.go), [Solution](./merge_sort.go)
100+
* [Quick Sort](./quick_sort_test.go), [Solution](./quick_sort.go)

go.mod

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module github.com/spring1843/go-dsa
22

33
go 1.22
4+
5+
require (
6+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
7+
github.com/spf13/cobra v1.8.1 // indirect
8+
github.com/spf13/pflag v1.0.5 // indirect
9+
)

go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
4+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5+
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
6+
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
7+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
8+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

stack/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,4 @@ During process execution, a portion of memory known as the "stack" is reserved t
9090
* [Infix to Postfix Conversion](./infix_to_postfix_test.go), [Solution](./infix_to_postfix.go)
9191
* [Evaluate Postfix](./evaluate_postfix_test.go), [Solution](./evaluate_postfix.go)
9292
* [Longest Valid Parentheses](./longest_valid_parentheses_test.go), [Solution](./longest_valid_parentheses.go)
93-
* [Basic Calculator](./basic_calculator_test.go), [Solution](basic_calculator.go)
93+
* [Basic Calculator](./basic_calculator_test.go), [Solution](./basic_calculator.go)

0 commit comments

Comments
 (0)