Skip to content

Commit

Permalink
add fetch and parse html body from question
Browse files Browse the repository at this point in the history
Signed-off-by: Rodolfo Sanchez <[email protected]>
  • Loading branch information
dolfolife committed Feb 21, 2024
1 parent 2011ae3 commit 4d841fd
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 162 deletions.
1 change: 1 addition & 0 deletions pkg/aoc/aoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func GetPuzzles(day string, year string) []puzzle.Puzzle {
rawInput := getBodyFromUrl(inputURL, aocConfig.SessionId)

response, err := ParsePuzzles(day, year, body, rawInput)

if err != nil {
log.Fatalf("Error parsing puzzles: %s", err)
}
Expand Down
55 changes: 27 additions & 28 deletions pkg/aoc/aoc_service.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
package aoc

import (
"context"
"io"
"log"
"net/http"
"time"
"context"
"io"
"log"
"net/http"
"time"
)


func getBodyFromUrl(url string, cookie string) []byte {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)

req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
log.Fatalf("Error making request: %s", err)
}

if err != nil {
log.Fatalf("Error making request: %s", err)
}
if cookie == "" {
log.Fatal("Advent of Code requires a session and your cookie is empty")
}

if cookie == "" {
log.Fatal("Advent of Code requires a session and your cookie is empty")
}
sessionCookie := http.Cookie{Name: "session", Value: cookie}
req.AddCookie(&sessionCookie)

sessionCookie := http.Cookie{Name: "session", Value: cookie}
req.AddCookie(&sessionCookie)
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Error fetching the page: %s", err)
}

res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Error fetching the page: %s", err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)

defer res.Body.Close()
body, err := io.ReadAll(res.Body)

if err != nil {
log.Fatalf("Error reading the body of the request: %s", err)
}
if err != nil {
log.Fatalf("Error reading the body of the request: %s", err)
}

return body
return body
}
78 changes: 39 additions & 39 deletions pkg/aoc/html_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,52 @@ import (
)

func ParsePuzzles(day string, year string, responseBody []byte, input []byte) ([]puzzle.Puzzle, error) {
node, err := html.Parse(bytes.NewReader(responseBody))
if err != nil {
log.Fatalf("Error parsing the body: %s", err)
}

puzzlePartsHTMLNodes := findRootNodesPuzzle(node)
var parts []puzzle.Puzzle
for _, node := range puzzlePartsHTMLNodes {
parts = append(parts, puzzle.NewPuzzleFromHTML(day, year, parsePuzzleHTML(node), input))
}
return parts, nil
node, err := html.Parse(bytes.NewReader(responseBody))

if err != nil {
log.Fatalf("Error parsing the body: %s", err)
}

puzzlePartsHTMLNodes := findRootNodesPuzzle(node)

var parts []puzzle.Puzzle
for _, node := range puzzlePartsHTMLNodes {
parts = append(parts, puzzle.NewPuzzleFromHTML(day, year, parsePuzzleHTML(node), input))
}
return parts, nil
}

func findRootNodesPuzzle(node *html.Node) []*html.Node {
var nodes []*html.Node
for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.ElementNode && child.Data == "article" && hasAttr(child.Attr, "day-desc") {
nodes = append(nodes, child)
}
nodes = append(nodes, findRootNodesPuzzle(child)...)
}

return nodes
var nodes []*html.Node

for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.ElementNode && child.Data == "article" && hasAttr(child.Attr, "day-desc") {
nodes = append(nodes, child)
}
nodes = append(nodes, findRootNodesPuzzle(child)...)
}

return nodes
}

func hasAttr(attrs []html.Attribute, attr string) bool {
for _, a := range attrs {
if a.Key == "class" && a.Val == attr {
return true
}
}
return false
for _, a := range attrs {
if a.Key == "class" && a.Val == attr {
return true
}
}
return false
}

func parsePuzzleHTML(node *html.Node) string {
buffer := strings.Builder{}
for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.TextNode {
buffer.WriteString(child.Data)
}
buffer.WriteString(parsePuzzleHTML(child))
}

return buffer.String()
buffer := strings.Builder{}

for child := node.FirstChild; child != nil; child = child.NextSibling {
if child.Type == html.TextNode {
buffer.WriteString(child.Data)
}
buffer.WriteString(parsePuzzleHTML(child))
}

return buffer.String()
}
173 changes: 97 additions & 76 deletions pkg/puzzle/puzzle.go
Original file line number Diff line number Diff line change
@@ -1,111 +1,132 @@
package puzzle

import(
"gopkg.in/yaml.v3"
"log"
"os"
"errors"
"fmt"
"strings"
import (
"errors"
"fmt"
"log"
"os"
"strings"

"gopkg.in/yaml.v3"
)

type PuzzleStatus string

type PuzzleMetadata struct {
Day string `yaml:"day"`
Title string `yaml:"title"`
Year string `yaml:"year"`
Day string `yaml:"day"`
Title string `yaml:"title"`
Year string `yaml:"year"`
}

type PuzzlePart struct {
Answer string `yaml:"answer,omitempty"`
Description string `yaml:"description"`
Status PuzzleStatus `yaml:"status"`
Answer string `yaml:"answer,omitempty"`
Description string `yaml:"description"`
Status PuzzleStatus `yaml:"status"`

RawInput []byte
RawInput []byte
}

type Puzzle struct {
Metadata PuzzleMetadata `yaml:"metadata"`
Puzzles []PuzzlePart `yaml:"puzzles"`
Metadata PuzzleMetadata `yaml:"metadata"`
Puzzles []PuzzlePart `yaml:"puzzles"`
}

func NewPuzzleFromHTML(day string, year string, htmlString string, input []byte) Puzzle {
return Puzzle{
Metadata: PuzzleMetadata{
Day: day,
Year: year,
Title: getTitleFromBody(htmlString),
},
Puzzles: []PuzzlePart{},
}
return Puzzle{
Metadata: PuzzleMetadata{
Day: day,
Year: year,
Title: getTitleFromBody(htmlString),
},
Puzzles: getPuzzlePartsFromHTMLString(htmlString),
}
}

// The field status is a collection of status and we need to validate that the
// status is in the set of valid statuses
func (p *Puzzle) ParseFields() error {
mapStatus := map[string]PuzzleStatus{
"UNSOLVED": Unsolved,
"SOLVED": Solved,
"UNREACHABLE": Unreachable,
}
for i, puzzle := range p.Puzzles {
status := strings.ToUpper(string(puzzle.Status))
if _, ok := mapStatus[status]; ok {
p.Puzzles[i].Status = mapStatus[status]
} else {
errorMessage := fmt.Sprintf("cannot parse Puzzle Part %d", i)
return NewError(ErrInvalidStatus, errors.New(errorMessage))
}
}
return nil
mapStatus := map[string]PuzzleStatus{
"UNSOLVED": Unsolved,
"SOLVED": Solved,
"UNREACHABLE": Unreachable,
}
for i, puzzle := range p.Puzzles {
status := strings.ToUpper(string(puzzle.Status))
if _, ok := mapStatus[status]; ok {
p.Puzzles[i].Status = mapStatus[status]
} else {
errorMessage := fmt.Sprintf("cannot parse Puzzle Part %d", i)
return NewError(ErrInvalidStatus, errors.New(errorMessage))
}
}
return nil
}

func NewPuzzleFromCache(filepath string, inputFilepath []string) (Puzzle, error) {
var puzzle Puzzle
yamlFile, err := os.ReadFile(filepath)
if err != nil {
log.Printf("Error trying to read the YAML file err = #%v ", err)
return Puzzle{}, err
}
err = yaml.Unmarshal(yamlFile, &puzzle)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
return Puzzle{}, err
}

// yaml.Umarshall does not have validation on sets like the status field
// We need to map the status of the puzzle
err = puzzle.ParseFields()
if err != nil {
log.Fatalf("Error trying to parse the Puzzle err = #%v ", err)
return Puzzle{}, err
}
for i, inputFile := range inputFilepath {
rawInput, err := os.ReadFile(inputFile)
if err != nil {
log.Fatalf("Error trying to read the input for Puzzle Part %d err #%v ", i, err)
return Puzzle{}, err
}
// we need to delete the last byte of the input because it is a newline or EOF
rawInput = rawInput[:len(rawInput)-1]
puzzle.Puzzles[i].RawInput = rawInput
}
return puzzle, nil
var puzzle Puzzle
yamlFile, err := os.ReadFile(filepath)
if err != nil {
log.Printf("Error trying to read the YAML file err = #%v ", err)
return Puzzle{}, err
}
err = yaml.Unmarshal(yamlFile, &puzzle)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
return Puzzle{}, err
}

// yaml.Umarshall does not have validation on sets like the status field
// We need to map the status of the puzzle
err = puzzle.ParseFields()
if err != nil {
log.Fatalf("Error trying to parse the Puzzle err = #%v ", err)
return Puzzle{}, err
}

for i, inputFile := range inputFilepath {
rawInput, err := os.ReadFile(inputFile)
if err != nil {
log.Fatalf("Error trying to read the input for Puzzle Part %d err #%v ", i, err)
return Puzzle{}, err
}
// we need to delete the last byte of the input because it is a newline or EOF
rawInput = rawInput[:len(rawInput)-1]
puzzle.Puzzles[i].RawInput = rawInput
}
return puzzle, nil
}

func getTitleFromBody(body string) string {
return "Title of the Puzzle Part"
parts := strings.Split(body, "---")
titleParts := strings.Split(parts[1], ":")
return strings.TrimSpace(titleParts[1])
}

func getPuzzlePartsFromHTMLString(body string) []PuzzlePart {
puzzleParts := make([]PuzzlePart, 0)

parts := strings.Split(body, "---")

if len(parts) < 3 {
log.Fatal("Description of the puzzle is not found")
return puzzleParts
}
puzzleParts = append(puzzleParts, PuzzlePart{
Answer: "",
Status: "UNSOLVED",
Description: parts[2],
})

return puzzleParts
}

type PuzzleSolver[T any] struct {
Puzzle Puzzle
NormalizeInput func(string) T
Solve func() Response
Puzzle Puzzle
NormalizeInput func(string) T
Solve func() Response
}

type Response struct {
Value string
Error error
Value string
Error error
}
Loading

0 comments on commit 4d841fd

Please sign in to comment.