From be9f20a2933df113c36b854de06ea531790b0293 Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Fri, 26 Apr 2019 10:11:17 +0300 Subject: [PATCH] add: packaged log parser for methods --- .../xxx-log-parser-methods/packaged/log.txt | 6 ++ .../packaged/log_err_missing.txt | 6 ++ .../packaged/log_err_negative.txt | 6 ++ .../packaged/log_err_str.txt | 6 ++ .../xxx-log-parser-methods/packaged/main.go | 29 ++++++ .../packaged/metrics/parser.go | 52 +++++++++++ .../packaged/metrics/report.go | 89 +++++++++++++++++++ .../packaged/metrics/result.go | 51 +++++++++++ .../packaged/summarize.go | 40 +++++++++ 9 files changed, 285 insertions(+) create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/log.txt create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_missing.txt create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_negative.txt create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_str.txt create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/main.go create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/parser.go create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/report.go create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/result.go create mode 100644 x-tba/2-methods/xxx-log-parser-methods/packaged/summarize.go diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/log.txt b/x-tba/2-methods/xxx-log-parser-methods/packaged/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_missing.txt b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_negative.txt b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org -100 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_str.txt b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org FOUR +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/main.go b/x-tba/2-methods/xxx-log-parser-methods/packaged/main.go new file mode 100644 index 0000000..a8040a4 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/main.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 main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + + "github.com/inancgumus/learngo/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics" +) + +func main() { + in := bufio.NewScanner(os.Stdin) + + parser, report := metrics.NewParser(), metrics.NewReport() + for in.Scan() { + report.Update(parser.Parse(in.Text())) + } + s, _ := json.Marshal(report) + fmt.Println(string(s)) + summarize(report, parser.Err(), in.Err()) +} diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/parser.go b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/parser.go new file mode 100644 index 0000000..c1681f5 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/parser.go @@ -0,0 +1,52 @@ +// 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 metrics + +import ( + "fmt" +) + +// Parser keep tracks of the parsing +type Parser struct { + lines int // number of parsed lines (for the error messages) + lerr error // the last error occurred +} + +// Parsed wraps a result for generating parser error +type Parsed struct { + result // use struct embedding + err error // inject an error +} + +// NewParser returns a new parser +func NewParser() *Parser { + return new(Parser) +} + +// Parse parses a log line and returns a result with an injected error +func (l *Parser) Parse(line string) (parsed Parsed) { + // always set the error + defer func() { parsed.err = l.lerr }() + + // if there was an error do not continue + if l.lerr != nil { + return + } + + // chain the parser's error to the result's + res, err := parse(line) + if l.lines++; err != nil { + l.lerr = fmt.Errorf("%s: (line #%d)", err, l.lines) + } + return Parsed{result: res} +} + +// Err returns the last error encountered +func (l *Parser) Err() error { + return l.lerr +} diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/report.go b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/report.go new file mode 100644 index 0000000..ecf1060 --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/report.go @@ -0,0 +1,89 @@ +// 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 metrics + +import ( + "encoding/json" + "sort" +) + +// Report aggregates the final report +type Report struct { + sum map[string]result // metrics per domain + domains []string // unique domain names + total result // total visits for all domains +} + +// NewReport constructs and initializes a new report +func NewReport() *Report { + return &Report{sum: make(map[string]result)} +} + +// Update updates the report for the given parsing result +func (r *Report) Update(parsed Parsed) { + // do not update the report if the result has an error + if parsed.err != nil { + return + } + + domain := parsed.Domain + if _, ok := r.sum[domain]; !ok { + r.domains = append(r.domains, domain) + } + + // let the result handle the addition + // this allows us to manage the result in once place + // and this way it becomes easily extendable + r.total = r.total.add(parsed.result) + r.sum[domain] = parsed.add(r.sum[domain]) +} + +// Iterator returns `next()` to detect when the iteration ends, +// and a `cur()` to return the current result. +// iterator iterates sorted by domains. +func (r *Report) Iterator() (next func() bool, cur func() result) { + sort.Strings(r.domains) + + // remember the last iterated result + var last int + + next = func() bool { + defer func() { last++ }() + return len(r.domains) > last + } + + cur = func() result { + // returns a copy so the caller cannot change it + name := r.domains[last-1] + return r.sum[name] + } + + return +} + +// Total returns the total metrics +func (r *Report) Total() Parsed { + return Parsed{result: r.total} +} + +// MarshalJSON marshals a report to JSON +// Alternative: unexported embedding +func (r *Report) MarshalJSON() ([]byte, error) { + type total struct { + *result + IgnoreDomain *string `json:"Domain,omitempty"` + } + + return json.Marshal(struct { + Sum map[string]result + Domains []string + Total total + }{ + Sum: r.sum, Domains: r.domains, Total: total{result: &r.total}, + }) +} diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/result.go b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/result.go new file mode 100644 index 0000000..7da44eb --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics/result.go @@ -0,0 +1,51 @@ +// 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 metrics + +import ( + "fmt" + "strconv" + "strings" +) + +// always put all the related things together as in here + +// result stores metrics for a domain +// it uses the value mechanics, +// because it doesn't have to update anything +type result struct { + Domain string + Visits int + // add more metrics if needed +} + +// add adds the metrics of another Result to itself and returns a new Result +func (r result) add(other result) result { + return result{ + Domain: r.Domain, + Visits: r.Visits + other.Visits, + } +} + +// parse parses a single log line +func parse(line string) (parsed result, err error) { + fields := strings.Fields(line) + if len(fields) != 2 { + err = fmt.Errorf("wrong input: %v", fields) + return + } + + parsed.Domain = fields[0] + + parsed.Visits, err = strconv.Atoi(fields[1]) + if parsed.Visits < 0 || err != nil { + err = fmt.Errorf("wrong input: %q", fields[1]) + } + + return +} diff --git a/x-tba/2-methods/xxx-log-parser-methods/packaged/summarize.go b/x-tba/2-methods/xxx-log-parser-methods/packaged/summarize.go new file mode 100644 index 0000000..2aaeb6d --- /dev/null +++ b/x-tba/2-methods/xxx-log-parser-methods/packaged/summarize.go @@ -0,0 +1,40 @@ +// 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 main + +import ( + "fmt" + "strings" + + "github.com/inancgumus/learngo/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics" +) + +// summarize prints the report and errors if any +func summarize(rep *metrics.Report, errs ...error) { + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + next, cur := rep.Iterator() + for next() { + rec := cur() + fmt.Printf("%-30s %10d\n", rec.Domain, rec.Visits) + } + fmt.Printf("\n%-30s %10d\n", "TOTAL", rep.Total().Visits) + + // only handle the errors once + dumpErrs(errs...) +} + +// this variadic func simplifies the multiple error handling +func dumpErrs(errs ...error) { + for _, err := range errs { + if err != nil { + fmt.Printf("> Err: %s\n", err) + } + } +}