diff --git a/logparser/v6/parse/count.go b/logparser/v6/logly/parse/count.go similarity index 81% rename from logparser/v6/parse/count.go rename to logparser/v6/logly/parse/count.go index 95582ae..d01443c 100644 --- a/logparser/v6/parse/count.go +++ b/logparser/v6/logly/parse/count.go @@ -21,11 +21,6 @@ func CountRecords(p Parser) *Count { return &Count{Parser: p} } -// Last counted record number. -func (c *Count) Last() int { - return c.count - 1 -} - // Parse increments the counter. func (c *Count) Parse() bool { c.count++ @@ -34,8 +29,9 @@ func (c *Count) Parse() bool { // Err returns the first error that was encountered by the Log. func (c *Count) Err() (err error) { - if err = c.Parser.Err(); err != nil { - err = fmt.Errorf("record #%d: %v", c.Last()+1, err) + err = c.Parser.Err() + if err != nil { + err = fmt.Errorf("record #%d: %v", c.count, err) } return } diff --git a/logparser/v6/parse/json.go b/logparser/v6/logly/parse/json.go similarity index 80% rename from logparser/v6/parse/json.go rename to logparser/v6/logly/parse/json.go index 703283b..0500a47 100644 --- a/logparser/v6/parse/json.go +++ b/logparser/v6/logly/parse/json.go @@ -10,20 +10,22 @@ package parse import ( "encoding/json" "io" + + "github.com/inancgumus/learngo/logparser/v6/logly/record" ) // JSONParser parses json records. type JSONParser struct { in *json.Decoder - err error // last error - last *Record // last parsed record + err error // last error + last *record.Record // last parsed record } // JSON creates a json parser. func JSON(r io.Reader) *JSONParser { return &JSONParser{ in: json.NewDecoder(r), - last: new(Record), + last: new(record.Record), } } @@ -34,6 +36,7 @@ func (p *JSONParser) Parse() bool { } p.last.Reset() + err := p.in.Decode(&p.last) if err == io.EOF { return false @@ -45,7 +48,7 @@ func (p *JSONParser) Parse() bool { } // Value returns the most recent record parsed by a call to Parse. -func (p *JSONParser) Value() Record { +func (p *JSONParser) Value() record.Record { return *p.last } diff --git a/logparser/v6/parse/parser.go b/logparser/v6/logly/parse/parser.go similarity index 83% rename from logparser/v6/parse/parser.go rename to logparser/v6/logly/parse/parser.go index 251c372..a22acc5 100644 --- a/logparser/v6/parse/parser.go +++ b/logparser/v6/logly/parse/parser.go @@ -7,13 +7,15 @@ package parse +import "github.com/inancgumus/learngo/logparser/v6/logly/record" + // Parser is an interface for the parsers. type Parser interface { // Parse the next record from the source. Parse() bool // Value returns the last parsed record by a call to Parse. - Value() Record + Value() record.Record // Err returns the first error that was encountered. Err() error diff --git a/logparser/v6/parse/text.go b/logparser/v6/logly/parse/text.go similarity index 74% rename from logparser/v6/parse/text.go rename to logparser/v6/logly/parse/text.go index df1d150..e13300b 100644 --- a/logparser/v6/parse/text.go +++ b/logparser/v6/logly/parse/text.go @@ -10,35 +10,41 @@ package parse import ( "bufio" "io" + + "github.com/inancgumus/learngo/logparser/v6/logly/record" ) // TextParser parses text based log lines. type TextParser struct { in *bufio.Scanner - err error // last error - last *Record // last parsed record + err error // last error + last *record.Record // last parsed record } // Text creates a text parser. func Text(r io.Reader) *TextParser { return &TextParser{ in: bufio.NewScanner(r), - last: new(Record), + last: new(record.Record), } } // Parse the next line. func (p *TextParser) Parse() bool { - if p.err != nil || !p.in.Scan() { + if p.err != nil { + return false + } + if !p.in.Scan() { return false } p.err = p.last.FromText(p.in.Bytes()) + return true } // Value returns the most recent record parsed by a call to Parse. -func (p *TextParser) Value() Record { +func (p *TextParser) Value() record.Record { return *p.last } diff --git a/logparser/v6/logly/record/json.go b/logparser/v6/logly/record/json.go new file mode 100644 index 0000000..3310820 --- /dev/null +++ b/logparser/v6/logly/record/json.go @@ -0,0 +1,22 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package record + +import "encoding/json" + +// UnmarshalJSON to a record. +func (r *Record) UnmarshalJSON(data []byte) error { + type rjson Record + + err := json.Unmarshal(data, (*rjson)(r)) + if err != nil { + return err + } + + return r.validate() +} diff --git a/logparser/v6/logly/record/record.go b/logparser/v6/logly/record/record.go new file mode 100644 index 0000000..df828e6 --- /dev/null +++ b/logparser/v6/logly/record/record.go @@ -0,0 +1,25 @@ +package record + +const fieldsLength = 4 + +// Record stores fields of a log line. +type Record struct { + Domain string + Page string + Visits int + Uniques int +} + +// Sum the numeric fields with another record. +func (r *Record) Sum(other Record) { + r.Visits += other.Visits + r.Uniques += other.Uniques +} + +// Reset all the fields of this record. +func (r *Record) Reset() { + r.Domain = "" + r.Page = "" + r.Visits = 0 + r.Uniques = 0 +} diff --git a/logparser/v6/logly/record/sum.go b/logparser/v6/logly/record/sum.go new file mode 100644 index 0000000..3d0e510 --- /dev/null +++ b/logparser/v6/logly/record/sum.go @@ -0,0 +1,36 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package record + +// Sum groups the records by summing their numeric fields. +type Sum struct { + sum map[string]Record +} + +// SumGroup the records by domain. +func SumGroup() *Sum { + return &Sum{ + sum: make(map[string]Record), + } +} + +// Group the record. +func (s *Sum) Group(r Record) { + k := r.Domain + r.Sum(s.sum[k]) + s.sum[k] = r +} + +// Records returns the grouped records. +func (s *Sum) Records() []Record { + var out []Record + for _, res := range s.sum { + out = append(out, res) + } + return out +} diff --git a/logparser/v6/logly/record/text.go b/logparser/v6/logly/record/text.go new file mode 100644 index 0000000..0fc7ef9 --- /dev/null +++ b/logparser/v6/logly/record/text.go @@ -0,0 +1,36 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package record + +import ( + "fmt" + "strconv" + "strings" +) + +// FromText unmarshals the log line into this record. +func (r *Record) FromText(p []byte) (err error) { + fields := strings.Fields(string(p)) + + if len(fields) != fieldsLength { + return fmt.Errorf("wrong number of fields %q", fields) + } + + r.Domain = fields[0] + r.Page = fields[1] + + const msg = "record.UnmarshalText %q: %v" + if r.Visits, err = strconv.Atoi(fields[2]); err != nil { + return fmt.Errorf(msg, "visits", err) + } + if r.Uniques, err = strconv.Atoi(fields[3]); err != nil { + return fmt.Errorf(msg, "uniques", err) + } + + return r.validate() +} diff --git a/logparser/v6/logly/record/validate.go b/logparser/v6/logly/record/validate.go new file mode 100644 index 0000000..3392609 --- /dev/null +++ b/logparser/v6/logly/record/validate.go @@ -0,0 +1,31 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package record + +import "errors" + +// validate whether the current record is valid or not. +func (r *Record) validate() error { + var msg string + + switch { + case r.Domain == "": + msg = "record.domain cannot be empty" + case r.Page == "": + msg = "record.page cannot be empty" + case r.Visits < 0: + msg = "record.visits cannot be negative" + case r.Uniques < 0: + msg = "record.uniques cannot be negative" + } + + if msg != "" { + return errors.New(msg) + } + return nil +} diff --git a/logparser/v6/logly/report/json.go b/logparser/v6/logly/report/json.go new file mode 100644 index 0000000..84c16c8 --- /dev/null +++ b/logparser/v6/logly/report/json.go @@ -0,0 +1,29 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package report + +import ( + "encoding/json" + "io" + + "github.com/inancgumus/learngo/logparser/v6/logly/record" +) + +// JSON generates a json report. +func JSON(w io.Writer, rs []record.Record) error { + enc := json.NewEncoder(w) + + for _, r := range rs { + err := enc.Encode(&r) + + if err != nil { + return err + } + } + return nil +} diff --git a/logparser/v6/logly/report/text.go b/logparser/v6/logly/report/text.go new file mode 100644 index 0000000..7bcb8a5 --- /dev/null +++ b/logparser/v6/logly/report/text.go @@ -0,0 +1,49 @@ +// For more tutorials: https://brecord.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package report + +import ( + "fmt" + "io" + "text/tabwriter" + + "github.com/inancgumus/learngo/logparser/v6/logly/record" +) + +// Text generates a text report. +func Text(w io.Writer, rs []record.Record) error { + tw := tabwriter.NewWriter( + w, + 0, // minWidth + 4, // tabWidth + 4, // padding + ' ', // padChar + 0, // flags + ) + + fmt.Fprintf(tw, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n") + fmt.Fprintf(tw, "-------\t-----\t------\t-------\n") + + var total record.Record + for _, r := range rs { + total.Sum(r) + + fmt.Fprintf(tw, "%s\t%s\t%d\t%d\n", + r.Domain, r.Page, + r.Visits, r.Uniques, + ) + } + + fmt.Fprintf(tw, "\t\t\t\n") + fmt.Fprintf(tw, "%s\t%s\t%d\t%d\n", + "TOTAL", "", + total.Visits, total.Uniques, + ) + + return tw.Flush() +} diff --git a/logparser/v6/main.go b/logparser/v6/main.go index f90936e..88d6084 100644 --- a/logparser/v6/main.go +++ b/logparser/v6/main.go @@ -8,49 +8,28 @@ package main import ( - "fmt" "log" "os" - "github.com/inancgumus/learngo/logparser/v6/parse" - "github.com/inancgumus/learngo/logparser/v6/report" + "github.com/inancgumus/learngo/logparser/v6/logly/parse" + "github.com/inancgumus/learngo/logparser/v6/logly/record" + "github.com/inancgumus/learngo/logparser/v6/logly/report" ) func main() { - // trace.Start(os.Stderr) - // defer trace.Stop() + var ( + p = parse.CountRecords(parse.Text(os.Stdin)) + g = record.SumGroup() + ) - var p parse.Parser - // p = parse.Text(os.Stdin) - p = parse.JSON(os.Stdin) - p = parse.CountRecords(p) - - r := report.Text(os.Stdout) - - var out []parse.Record for p.Parse() { - r := p.Value() - - // if !parse.Filter(r) { - // continue - // } - - // sum.group(r) - out = append(out, r) + g.Group(p.Value()) } - if err := p.Err(); err != nil { log.Fatalln(err) } - // var out []parse.Record - // for sum.More() { - - // } - - if err := r.Generate(out); err != nil { + if err := report.Text(os.Stdout, g.Records()); err != nil { log.Fatalln(err) } - - fmt.Println(p.(*parse.Count).Last(), "records are processed.") } diff --git a/logparser/v6/parse/record.go b/logparser/v6/parse/record.go deleted file mode 100644 index e8c3d5e..0000000 --- a/logparser/v6/parse/record.go +++ /dev/null @@ -1,88 +0,0 @@ -package parse - -import ( - "encoding/json" - "errors" - "fmt" - "strconv" - "strings" -) - -const fieldsLength = 4 - -// Record stores fields of a log line. -type Record struct { - Domain string - Page string - Visits int - Uniques int -} - -// Sum the numeric fields with another record. -func (r *Record) Sum(other Record) { - r.Visits += other.Visits - r.Uniques += other.Uniques -} - -// Reset all the fields of this record. -func (r *Record) Reset() { - r.Domain = "" - r.Page = "" - r.Visits = 0 - r.Uniques = 0 -} - -// FromText unmarshals the log line into this record. -func (r *Record) FromText(p []byte) (err error) { - fields := strings.Fields(string(p)) - - if len(fields) != fieldsLength { - return fmt.Errorf("wrong number of fields %q", fields) - } - - r.Domain = fields[0] - r.Page = fields[1] - - const msg = "record.UnmarshalText %q: %v" - if r.Visits, err = strconv.Atoi(fields[2]); err != nil { - return fmt.Errorf(msg, "visits", err) - } - if r.Uniques, err = strconv.Atoi(fields[3]); err != nil { - return fmt.Errorf(msg, "uniques", err) - } - - return r.validate() -} - -// UnmarshalJSON to a record. -func (r *Record) UnmarshalJSON(data []byte) error { - type rjson Record - - err := json.Unmarshal(data, (*rjson)(r)) - if err != nil { - return err - } - - return r.validate() -} - -// validate whether the current record is valid or not. -func (r *Record) validate() error { - var msg string - - switch { - case r.Domain == "": - msg = "record.domain cannot be empty" - case r.Page == "": - msg = "record.page cannot be empty" - case r.Visits < 0: - msg = "record.visits cannot be negative" - case r.Uniques < 0: - msg = "record.uniques cannot be negative" - } - - if msg != "" { - return errors.New(msg) - } - return nil -} diff --git a/logparser/v6/report/json.go b/logparser/v6/report/json.go deleted file mode 100644 index 4d1686d..0000000 --- a/logparser/v6/report/json.go +++ /dev/null @@ -1,37 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package report - -import ( - "encoding/json" - "io" - - "github.com/inancgumus/learngo/logparser/v6/parse" -) - -// JSONReport generates a JSON report. -type JSONReport struct { - w *json.Encoder -} - -// JSON returns a JSON report generator. -func JSON(w io.Writer) *JSONReport { - return &JSONReport{ - w: json.NewEncoder(w), - } -} - -// Generate the report from the records. -func (jr *JSONReport) Generate(rs []parse.Record) error { - for _, r := range rs { - if err := jr.w.Encode(&r); err != nil { - return err - } - } - return nil -} diff --git a/logparser/v6/report/text.go b/logparser/v6/report/text.go deleted file mode 100644 index c347841..0000000 --- a/logparser/v6/report/text.go +++ /dev/null @@ -1,60 +0,0 @@ -// For more tutorials: https://blog.learngoprogramming.com -// -// Copyright © 2018 Inanc Gumus -// Learn Go Programming Course -// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ -// - -package report - -import ( - "fmt" - "io" - "text/tabwriter" - - "github.com/inancgumus/learngo/logparser/v6/parse" -) - -// TextReport report generator. -type TextReport struct { - w *tabwriter.Writer - total parse.Record -} - -// Text creates a report generator. -func Text(w io.Writer) *TextReport { - tw := tabwriter.NewWriter(w, - 0, // minWidth - 4, // tabWidth - 4, // padding - ' ', // padChar - 0, // flags - ) - - return &TextReport{w: tw} -} - -// Generate the report from the records. -// tabwriter caches, the memory usage will be high -// if you send a large number of records to Generate. -func (tr *TextReport) Generate(rs []parse.Record) error { - fmt.Fprintf(tr.w, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n") - fmt.Fprintf(tr.w, "-------\t-----\t------\t-------\n") - - for _, r := range rs { - tr.total.Sum(r) - - fmt.Fprintf(tr.w, "%s\t%s\t%d\t%d\n", - r.Domain, r.Page, - r.Visits, r.Uniques, - ) - } - - fmt.Fprintf(tr.w, "\t\t\t\n") - fmt.Fprintf(tr.w, "%s\t%s\t%d\t%d\n", - "TOTAL", "", - tr.total.Visits, tr.total.Uniques, - ) - - return tr.w.Flush() -}