-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into 2021/day08
- Loading branch information
Showing
9 changed files
with
504 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package solutions | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"text/template" | ||
|
||
"github.com/obalunenko/advent-of-code/internal/puzzles/solutions/templates" | ||
) | ||
|
||
func createNewFromTemplate(purl string) error { | ||
const ( | ||
perms = 0o655 | ||
yearLen = 4 | ||
dayLen = 2 | ||
) | ||
|
||
pd, err := parsePuzzleURL(purl) | ||
if err != nil { | ||
return fmt.Errorf("parse puzzle url %q: %w", purl, err) | ||
} | ||
|
||
day := strconv.Itoa(pd.day) | ||
if len(day) < dayLen { | ||
day = "0" + day | ||
} | ||
|
||
if len(day) != dayLen { | ||
return fmt.Errorf("invalid day: %s", day) | ||
} | ||
|
||
year := strconv.Itoa(pd.year) | ||
|
||
if len(year) != yearLen { | ||
return fmt.Errorf("invalid year: %s", year) | ||
} | ||
|
||
params := templates.Params{ | ||
Year: year, | ||
Day: pd.day, | ||
DayStr: day, | ||
URL: purl, | ||
} | ||
|
||
path := filepath.Clean(filepath.Join(year, "day"+day)) | ||
|
||
if err = createPuzzleDir(path, perms); err != nil { | ||
return fmt.Errorf("failed to create puzzle dir: %w", err) | ||
} | ||
|
||
testdata := filepath.Clean(filepath.Join(path, "testdata")) | ||
|
||
if err = createTestdata(testdata, perms); err != nil { | ||
return fmt.Errorf("failed to create testdata: %w", err) | ||
} | ||
|
||
tmplsFns := []func() (*template.Template, error){ | ||
templates.SolutionTmpl, templates.SolutionTestTmpl, templates.SpecTmpl, | ||
} | ||
|
||
for _, tmplFn := range tmplsFns { | ||
var tmpl *template.Template | ||
|
||
tmpl, err = tmplFn() | ||
if err != nil { | ||
return fmt.Errorf("failed to get template: %w", err) | ||
} | ||
|
||
if err = createFromTemplate(tmpl, path, perms, params); err != nil { | ||
return fmt.Errorf("failed to create from template: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createFromTemplate(tmpl *template.Template, path string, perms os.FileMode, params templates.Params) error { | ||
fpath := filepath.Clean(filepath.Join(path, tmpl.Name())) | ||
|
||
if isExist(fpath) { | ||
return nil | ||
} | ||
|
||
var content []byte | ||
|
||
content, err := templates.SubstituteTemplate(tmpl, params) | ||
if err != nil { | ||
return fmt.Errorf("failed to substitute template: %w", err) | ||
} | ||
|
||
if err = os.WriteFile(fpath, content, perms); err != nil { | ||
return fmt.Errorf("failed to write file: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createPuzzleDir(path string, perms os.FileMode) error { | ||
if !isExist(path) { | ||
if err := os.MkdirAll(path, perms); err != nil { | ||
return fmt.Errorf("failed to create dir: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createTestdata(path string, perms os.FileMode) error { | ||
if !isExist(path) { | ||
if err := os.MkdirAll(path, perms); err != nil { | ||
return fmt.Errorf("failed to create dir: %w", err) | ||
} | ||
} | ||
|
||
input := filepath.Clean(filepath.Join(path, "input.txt")) | ||
|
||
if !isExist(input) { | ||
var f *os.File | ||
|
||
f, err := os.Create(input) | ||
if err != nil { | ||
return fmt.Errorf("failed to create file: %w", err) | ||
} | ||
|
||
if err = f.Close(); err != nil { | ||
return fmt.Errorf("failed to close file: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func isExist(path string) bool { | ||
stat, err := os.Stat(path) | ||
if err != nil && !os.IsNotExist(err) { | ||
return false | ||
} | ||
|
||
return stat != nil && stat.Name() != "" | ||
} | ||
|
||
type puzzleDate struct { | ||
year int | ||
day int | ||
} | ||
|
||
func parsePuzzleURL(url string) (puzzleDate, error) { | ||
const ( | ||
urlFmt = "https://adventofcode.com/%d/day/%d" | ||
paramsNum = 2 | ||
) | ||
|
||
var year, day int | ||
|
||
n, err := fmt.Sscanf(url, urlFmt, &year, &day) | ||
if err != nil { | ||
return puzzleDate{}, fmt.Errorf("parse puzzle url: %w", err) | ||
} | ||
|
||
if n != paramsNum { | ||
return puzzleDate{}, fmt.Errorf("invalid puzzle url: %s", url) | ||
} | ||
|
||
return puzzleDate{ | ||
year: year, | ||
day: day, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package solutions | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/obalunenko/getenv" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func Test_createNewFromTemplate(t *testing.T) { | ||
const envName = "AOC_PUZZLE_URL" | ||
|
||
purl, err := getenv.Env[string](envName) | ||
if err != nil { | ||
if errors.Is(err, getenv.ErrNotSet) { | ||
t.Skipf("%s is not set", envName) | ||
} | ||
|
||
t.Fatalf("failed to get environment variable[%s]: %v", envName, err) | ||
} | ||
|
||
require.NoError(t, createNewFromTemplate(purl)) | ||
} | ||
|
||
func Test_parsePuzzleURL(t *testing.T) { | ||
type args struct { | ||
url string | ||
} | ||
|
||
tests := []struct { | ||
name string | ||
args args | ||
wandDate puzzleDate | ||
wantErr assert.ErrorAssertionFunc | ||
}{ | ||
{ | ||
name: "valid url", | ||
args: args{ | ||
url: "https://adventofcode.com/2022/day/1", | ||
}, | ||
wandDate: puzzleDate{ | ||
year: 2022, | ||
day: 1, | ||
}, | ||
wantErr: assert.NoError, | ||
}, | ||
{ | ||
name: "invalid url", | ||
args: args{ | ||
url: "https://adventofcode.com/2022", | ||
}, | ||
wandDate: puzzleDate{ | ||
year: 0, | ||
day: 0, | ||
}, | ||
wantErr: assert.Error, | ||
}, | ||
{ | ||
name: "empty url", | ||
args: args{ | ||
url: "", | ||
}, | ||
wandDate: puzzleDate{ | ||
year: 0, | ||
day: 0, | ||
}, | ||
wantErr: assert.Error, | ||
}, | ||
{ | ||
name: "whitespace url", | ||
args: args{ | ||
url: " ", | ||
}, | ||
wandDate: puzzleDate{ | ||
year: 0, | ||
day: 0, | ||
}, | ||
wantErr: assert.Error, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
gotDate, err := parsePuzzleURL(tt.args.url) | ||
if !tt.wantErr(t, err) { | ||
return | ||
} | ||
|
||
assert.Equal(t, tt.wandDate, gotDate) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Package templates contains templates for solution.go, solution_test.go and spec.md files. | ||
package templates | ||
|
||
import ( | ||
"bytes" | ||
_ "embed" | ||
"fmt" | ||
"text/template" | ||
) | ||
|
||
var ( | ||
//go:embed solution.go.tmpl | ||
solutionTmpl string | ||
//go:embed solution_test.go.tmpl | ||
solutionTestTmpl string | ||
//go:embed spec.md.tmpl | ||
specTmpl string | ||
) | ||
|
||
// Params contains parameters for templates. | ||
type Params struct { | ||
Year string // e.g. "2023" | ||
Day int // e.g. 2 | ||
DayStr string // e.g. "02" | ||
URL string // e.g. "https://adventofcode.com/2023/day/2" | ||
} | ||
|
||
// SolutionTmpl returns template for solution.go file. | ||
func SolutionTmpl() (*template.Template, error) { | ||
tmpl, err := template.New("solution.go").Parse(solutionTmpl) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse solution template: %w", err) | ||
} | ||
|
||
return tmpl, nil | ||
} | ||
|
||
// SolutionTestTmpl returns template for solution_test.go file. | ||
func SolutionTestTmpl() (*template.Template, error) { | ||
tmpl, err := template.New("solution_test.go").Parse(solutionTestTmpl) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse solution test template: %w", err) | ||
} | ||
|
||
return tmpl, nil | ||
} | ||
|
||
// SpecTmpl returns template for spec.md file. | ||
func SpecTmpl() (*template.Template, error) { | ||
tmpl, err := template.New("spec.md").Parse(specTmpl) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse spec template: %w", err) | ||
} | ||
|
||
return tmpl, nil | ||
} | ||
|
||
// SubstituteTemplate substitutes template with given parameters. | ||
func SubstituteTemplate(tmpl *template.Template, p Params) ([]byte, error) { | ||
var buf bytes.Buffer | ||
|
||
err := tmpl.Execute(&buf, p) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to execute template: %w", err) | ||
} | ||
|
||
return buf.Bytes(), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Package day{{ .DayStr }} contains solution for {{ .URL }} puzzle. | ||
package day{{ .DayStr }} | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/obalunenko/advent-of-code/internal/puzzles" | ||
) | ||
|
||
func init() { | ||
puzzles.Register(solution{}) | ||
} | ||
|
||
type solution struct{} | ||
|
||
func (s solution) Year() string { | ||
return puzzles.Year{{ .Year }}.String() | ||
} | ||
|
||
func (s solution) Day() string { | ||
return puzzles.Day{{ .DayStr }}.String() | ||
} | ||
|
||
func (s solution) Part1(input io.Reader) (string, error) { | ||
return "", puzzles.ErrNotImplemented | ||
} | ||
|
||
func (s solution) Part2(input io.Reader) (string, error) { | ||
return "", puzzles.ErrNotImplemented | ||
} |
Oops, something went wrong.