From 10e22d84280386d8458a11818240af6278c3fc9f Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sun, 18 Mar 2018 05:04:46 +0000 Subject: [PATCH] Extract scanning ASCII files into a util package --- internal/data/accounting.go | 7 +- internal/data/animated_object_definitions.go | 22 ++--- internal/data/data.go | 79 ++--------------- internal/data/generic.go | 33 ++++--- internal/util/asciiscan/asciiscan.go | 90 ++++++++++++++++++++ 5 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 internal/util/asciiscan/asciiscan.go diff --git a/internal/data/accounting.go b/internal/data/accounting.go index 80f30c6..30f081f 100644 --- a/internal/data/accounting.go +++ b/internal/data/accounting.go @@ -3,6 +3,8 @@ package data import ( "bytes" "errors" + + "ur.gs/chaos-gate/internal/util/asciiscan" ) // Comments are `//`, start of line only, for accounting @@ -13,11 +15,14 @@ var ( type Accounting map[string]string func LoadAccounting(filename string) (Accounting, error) { - scanLines, err := fileToScanner(filename) + s, err := asciiscan.New(filename) if err != nil { return nil, err } + defer s.Close() + scanLines := s.Bufio() + out := make(Accounting) for scanLines.Scan() { diff --git a/internal/data/animated_object_definitions.go b/internal/data/animated_object_definitions.go index 43b8d04..23dd561 100644 --- a/internal/data/animated_object_definitions.go +++ b/internal/data/animated_object_definitions.go @@ -1,12 +1,12 @@ package data import ( - "bufio" "errors" - // "fmt" "io" "strconv" "strings" + + "ur.gs/chaos-gate/internal/util/asciiscan" ) type Cell struct { @@ -35,13 +35,15 @@ type AnimatedObject struct { func LoadAnimatedObjectDefinitions(filename string) ([]AnimatedObject, error) { var out []AnimatedObject - scanLines, err := fileToScanner(filename) + s, err := asciiscan.New(filename) if err != nil { return nil, err } + defer s.Close() + for { - obj, err := consumeAnimatedObjectDefinition(scanLines) + obj, err := consumeAnimatedObjectDefinition(s) if err == io.EOF { return out, nil } else if err != nil { @@ -52,7 +54,7 @@ func LoadAnimatedObjectDefinitions(filename string) ([]AnimatedObject, error) { } } -func consumeAnimatedObjectDefinition(scanner *bufio.Scanner) (AnimatedObject, error) { +func consumeAnimatedObjectDefinition(scanner *asciiscan.Scanner) (AnimatedObject, error) { var out AnimatedObject var err error @@ -61,8 +63,7 @@ func consumeAnimatedObjectDefinition(scanner *bufio.Scanner) (AnimatedObject, er return out, err } - if err := consumeIntPtrs( - scanner, + if err := scanner.ConsumeIntPtrs( &out.Direction, &out.Type, &out.AnimationID, &out.AnimationGroup, &out.AnimatedType, ); err != nil { return out, err @@ -89,8 +90,7 @@ func consumeAnimatedObjectDefinition(scanner *bufio.Scanner) (AnimatedObject, er return out, err } - if err := consumeIntPtrs( - scanner, + if err := scanner.ConsumeIntPtrs( &out.Visibility, &out.Protection, &out.MinDelay, &out.DelayRange, ); err != nil { return out, err @@ -99,9 +99,9 @@ func consumeAnimatedObjectDefinition(scanner *bufio.Scanner) (AnimatedObject, er return out, nil } -func consumeCell(scanner *bufio.Scanner) (Cell, error) { +func consumeCell(scanner *asciiscan.Scanner) (Cell, error) { out := Cell{} - str, err := consumeString(scanner) + str, err := scanner.ConsumeString() if err != nil { return out, err } diff --git a/internal/data/data.go b/internal/data/data.go index 9ac59ce..c6ab26a 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -1,15 +1,9 @@ package data import ( - "bufio" - "bytes" - "io" - "io/ioutil" - "strconv" + "ur.gs/chaos-gate/internal/util/asciiscan" ) -var hashComment = []byte("#") - type CompassPoints struct { N int NE int @@ -26,80 +20,17 @@ type Range struct { End int } -func fileToScanner(filename string) (*bufio.Scanner, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - return bufio.NewScanner(bytes.NewReader(data)), nil -} - -func consumeString(scanner *bufio.Scanner) (string, error) { - for scanner.Scan() { - line := scanner.Bytes() - - if len(line) == 0 || line[0] == hashComment[0] { // Most .dat files use # for comments - continue - } - - comment := bytes.Index(line, hashComment) - if comment > 0 { - line = line[0:comment] - } - - return string(bytes.TrimRight(line, "\r\n\t ")), nil - } - - err := scanner.Err() - if err == nil { - return "", io.EOF - } - - return "", err -} - -func consumeInt(scanner *bufio.Scanner) (int, error) { - str, err := consumeString(scanner) - if err != nil { - return 0, err - } - - return strconv.Atoi(str) -} - -func consumeIntPtr(to *int, scanner *bufio.Scanner) error { - val, err := consumeInt(scanner) - if err != nil { - return err - } - - *to = val - return nil -} - -func consumeIntPtrs(scanner *bufio.Scanner, ptrs ...*int) error { - for _, ptr := range ptrs { - if err := consumeIntPtr(ptr, scanner); err != nil { - return err - } - } - - return nil -} - -func consumeRange(scanner *bufio.Scanner) (Range, error) { +func consumeRange(scanner *asciiscan.Scanner) (Range, error) { var out Range - err := consumeIntPtrs(scanner, &out.Start, &out.End) + err := scanner.ConsumeIntPtrs(&out.Start, &out.End) return out, err } -func consumeCompassPoints(scanner *bufio.Scanner) (CompassPoints, error) { +func consumeCompassPoints(scanner *asciiscan.Scanner) (CompassPoints, error) { var out CompassPoints - err := consumeIntPtrs( - scanner, + err := scanner.ConsumeIntPtrs( &out.N, &out.NE, &out.E, &out.SE, &out.S, &out.SW, &out.W, &out.NW, ) diff --git a/internal/data/generic.go b/internal/data/generic.go index d092971..5b7b977 100644 --- a/internal/data/generic.go +++ b/internal/data/generic.go @@ -1,5 +1,9 @@ package data +import ( + "ur.gs/chaos-gate/internal/util/asciiscan" +) + type ActionPointEnum int type SettingEnum int type TransitionFrameEnum int @@ -205,10 +209,11 @@ type Generic struct { // TODO: consume these values from the file func LoadGeneric(filename string) (*Generic, error) { - scanLines, err := fileToScanner(filename) + s, err := asciiscan.New(filename) if err != nil { return nil, err } + defer s.Close() out := &Generic{ ActionPoints: make(map[ActionPointEnum]int), @@ -224,7 +229,7 @@ func LoadGeneric(filename string) (*Generic, error) { // Various action point values, first to last for ap := ActionPointEnum(0); ap < APMax; ap++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -234,7 +239,7 @@ func LoadGeneric(filename string) (*Generic, error) { // Other miscellaneous data fields, first to last for setting := SettingEnum(0); setting < SettingMax; setting++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -244,7 +249,7 @@ func LoadGeneric(filename string) (*Generic, error) { // Vehicle walk transition frames. Whatever they are. for tf := TransitionFrameEnum(0); tf < TFMax; tf++ { - val, err := consumeCompassPoints(scanLines) + val, err := consumeCompassPoints(s) if err != nil { return nil, err } @@ -253,7 +258,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for st := SquadTypeEnum(0); st < SquadMax; st++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -262,7 +267,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for vt := VehicleTypeEnum(0); vt < VehicleMax; vt++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -271,7 +276,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for op := OptionEnum(0); op < OptionMax; op++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -280,7 +285,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for i := 0; i < NumCampaignScenarios; i++ { - val, err := consumeString(scanLines) + val, err := s.ConsumeString() if err != nil { return nil, err } @@ -289,7 +294,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for i := 0; i < NumCampaignScenarios; i++ { - val, err := consumeString(scanLines) + val, err := s.ConsumeString() if err != nil { return nil, err } @@ -298,13 +303,13 @@ func LoadGeneric(filename string) (*Generic, error) { out.CampaignEndMissionWavs = append(out.CampaignEndMissionWavs, val) } - out.AICoherentForce, err = consumeInt(scanLines) + out.AICoherentForce, err = s.ConsumeInt() if err != nil { return nil, err } for sp := SpellEnum(0); sp < SpellMax; sp++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -313,7 +318,7 @@ func LoadGeneric(filename string) (*Generic, error) { } for ai := AIPriorityEnum(0); ai < AIPriorityMax; ai++ { - val, err := consumeInt(scanLines) + val, err := s.ConsumeInt() if err != nil { return nil, err } @@ -321,13 +326,13 @@ func LoadGeneric(filename string) (*Generic, error) { out.AIPriority[ai] = val } - out.AICommandInfiniteLoopCounterWatermark, err = consumeInt(scanLines) + out.AICommandInfiniteLoopCounterWatermark, err = s.ConsumeInt() if err != nil { return nil, err } for rg := RngRangeEnum(0); rg < RngRangeMax; rg++ { - val, err := consumeRange(scanLines) + val, err := consumeRange(s) if err != nil { return nil, err } diff --git a/internal/util/asciiscan/asciiscan.go b/internal/util/asciiscan/asciiscan.go new file mode 100644 index 0000000..0a17bb1 --- /dev/null +++ b/internal/util/asciiscan/asciiscan.go @@ -0,0 +1,90 @@ +// package asciiscan is used to parse plaintext files into structured data +package asciiscan + +import ( + "bufio" + "bytes" + "io" + "os" + "strconv" +) + +var hashComment = []byte("#") + +type Scanner struct { + bufio *bufio.Scanner + closer io.Closer +} + +func New(filename string) (*Scanner, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + return &Scanner{ + bufio: bufio.NewScanner(f), + closer: f, + }, nil +} + +func (s *Scanner) Bufio() *bufio.Scanner { + return s.bufio +} + +func (s *Scanner) Close() error { + return s.closer.Close() +} + +func (s *Scanner) ConsumeString() (string, error) { + for s.bufio.Scan() { + line := s.bufio.Bytes() + + if len(line) == 0 || line[0] == hashComment[0] { // Most .dat files use # for comments + continue + } + + comment := bytes.Index(line, hashComment) + if comment > 0 { + line = line[0:comment] + } + + return string(bytes.TrimRight(line, "\r\n\t ")), nil + } + + err := s.bufio.Err() + if err == nil { + return "", io.EOF + } + + return "", err +} + +func (s *Scanner) ConsumeInt() (int, error) { + str, err := s.ConsumeString() + if err != nil { + return 0, err + } + + return strconv.Atoi(str) +} + +func (s *Scanner) ConsumeIntPtr(to *int) error { + val, err := s.ConsumeInt() + if err != nil { + return err + } + + *to = val + return nil +} + +func (s *Scanner) ConsumeIntPtrs(ptrs ...*int) error { + for _, ptr := range ptrs { + if err := s.ConsumeIntPtr(ptr); err != nil { + return err + } + } + + return nil +}