From ca0a9b4f6b86d220df3ebae961e89b12f6715ab8 Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Fri, 10 May 2019 15:51:19 +0300 Subject: [PATCH] add: methods log parser no pkg --- 28-methods/xxx-log-parser-no-pkg/log.txt | 6 ++ .../xxx-log-parser-no-pkg/log_err_missing.txt | 6 ++ .../log_err_negative.txt | 6 ++ .../xxx-log-parser-no-pkg/log_err_str.txt | 6 ++ 28-methods/xxx-log-parser-no-pkg/main.go | 24 +++++++ 28-methods/xxx-log-parser-no-pkg/parser.go | 52 ++++++++++++++ 28-methods/xxx-log-parser-no-pkg/result.go | 60 +++++++++++++++++ 28-methods/xxx-log-parser-no-pkg/summarize.go | 51 ++++++++++++++ 28-methods/xxx-log-parser-no-pkg/summary.go | 67 +++++++++++++++++++ 28-methods/xxx-log-parser/main.go | 8 +-- 28-methods/xxx-log-parser/report/summary.go | 8 +-- 11 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 28-methods/xxx-log-parser-no-pkg/log.txt create mode 100644 28-methods/xxx-log-parser-no-pkg/log_err_missing.txt create mode 100644 28-methods/xxx-log-parser-no-pkg/log_err_negative.txt create mode 100644 28-methods/xxx-log-parser-no-pkg/log_err_str.txt create mode 100644 28-methods/xxx-log-parser-no-pkg/main.go create mode 100644 28-methods/xxx-log-parser-no-pkg/parser.go create mode 100644 28-methods/xxx-log-parser-no-pkg/result.go create mode 100644 28-methods/xxx-log-parser-no-pkg/summarize.go create mode 100644 28-methods/xxx-log-parser-no-pkg/summary.go diff --git a/28-methods/xxx-log-parser-no-pkg/log.txt b/28-methods/xxx-log-parser-no-pkg/log.txt new file mode 100644 index 0000000..b449a83 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 200 +learngoprogramming.com 10 300 +golang.org 4 50 +golang.org 6 100 +blog.golang.org 20 25 +blog.golang.org 10 1 \ No newline at end of file diff --git a/28-methods/xxx-log-parser-no-pkg/log_err_missing.txt b/28-methods/xxx-log-parser-no-pkg/log_err_missing.txt new file mode 100644 index 0000000..8fbe528 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 200 +learngoprogramming.com 10 +golang.org 4 50 +golang.org 6 100 +blog.golang.org 20 25 +blog.golang.org 10 1 \ No newline at end of file diff --git a/28-methods/xxx-log-parser-no-pkg/log_err_negative.txt b/28-methods/xxx-log-parser-no-pkg/log_err_negative.txt new file mode 100644 index 0000000..b099716 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 200 +learngoprogramming.com 10 300 +golang.org -100 50 +golang.org 6 100 +blog.golang.org 20 25 +blog.golang.org 10 1 \ No newline at end of file diff --git a/28-methods/xxx-log-parser-no-pkg/log_err_str.txt b/28-methods/xxx-log-parser-no-pkg/log_err_str.txt new file mode 100644 index 0000000..4ccb676 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 200 +learngoprogramming.com 10 THREE-HUNDRED +golang.org FOUR 50 +golang.org 6 100 +blog.golang.org 20 25 +blog.golang.org 10 1 \ No newline at end of file diff --git a/28-methods/xxx-log-parser-no-pkg/main.go b/28-methods/xxx-log-parser-no-pkg/main.go new file mode 100644 index 0000000..b53bc82 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/main.go @@ -0,0 +1,24 @@ +// 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" + "os" +) + +func main() { + p := newParser() + + in := bufio.NewScanner(os.Stdin) + for in.Scan() { + p.parse(in.Text()) + } + + summarize(p.summarize(), p.err(), in.Err()) +} diff --git a/28-methods/xxx-log-parser-no-pkg/parser.go b/28-methods/xxx-log-parser-no-pkg/parser.go new file mode 100644 index 0000000..46d31ab --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/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 main + +import ( + "fmt" +) + +// parser parses the log file and generates a summary report. +type parser struct { + summary *summary // summarizes the parsing results + lines int // number of parsed lines (for the error messages) + lerr error // the last error occurred +} + +// new returns a new parsing state. +func newParser() *parser { + return &parser{summary: newSummary()} +} + +// parse parses a log line and adds it to the summary. +func (p *parser) parse(line string) { + // if there was an error do not continue + if p.lerr != nil { + return + } + + // chain the parser's error to the result's + res, err := parseLine(line) + if p.lines++; err != nil { + p.lerr = fmt.Errorf("line #%d: %s", p.lines, err) + return + } + + p.summary.update(res) +} + +// Summarize summarizes the parsing results. +// Only use it after the parsing is done. +func (p *parser) summarize() *summary { + return p.summary +} + +// Err returns the last error encountered +func (p *parser) err() error { + return p.lerr +} diff --git a/28-methods/xxx-log-parser-no-pkg/result.go b/28-methods/xxx-log-parser-no-pkg/result.go new file mode 100644 index 0000000..eefeca1 --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/result.go @@ -0,0 +1,60 @@ +// 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" + "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 + timeSpent 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, + timeSpent: r.timeSpent + other.timeSpent, + } +} + +// parse parses a single log line +func parseLine(line string) (r result, err error) { + fields := strings.Fields(line) + if len(fields) != 3 { + return r, fmt.Errorf("missing fields: %v", fields) + } + + f := new(field) + r.domain = fields[0] + r.visits = f.atoi("visits", fields[1]) + r.timeSpent = f.atoi("time spent", fields[2]) + return r, f.err +} + +// field helps for field parsing +type field struct{ err error } + +func (f *field) atoi(name, val string) int { + n, err := strconv.Atoi(val) + if n < 0 || err != nil { + f.err = fmt.Errorf("incorrect %s: %q", name, val) + } + return n +} diff --git a/28-methods/xxx-log-parser-no-pkg/summarize.go b/28-methods/xxx-log-parser-no-pkg/summarize.go new file mode 100644 index 0000000..ee61cec --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/summarize.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 main + +import ( + "fmt" + "strings" +) + +// summarize prints the parsing results. +// +// it prints the errors and returns if there are any. +// +// --json flag encodes to json and prints. +func summarize(sum *summary, errors ...error) { + if errs(errors...) { + return + } + + const ( + head = "%-30s %10s %20s\n" + val = "%-30s %10d %20d\n" + ) + + fmt.Printf(head, "DOMAIN", "VISITS", "TIME SPENT") + fmt.Println(strings.Repeat("-", 65)) + + for next, cur := sum.iterator(); next(); { + r := cur() + fmt.Printf(val, r.domain, r.visits, r.timeSpent) + } + + t := sum.totals() + fmt.Printf("\n"+val, "TOTAL", t.visits, t.timeSpent) +} + +// this variadic func simplifies the multiple error handling +func errs(errs ...error) (wasErr bool) { + for _, err := range errs { + if err != nil { + fmt.Printf("> Err: %s\n", err) + wasErr = true + } + } + return +} diff --git a/28-methods/xxx-log-parser-no-pkg/summary.go b/28-methods/xxx-log-parser-no-pkg/summary.go new file mode 100644 index 0000000..5dd13ab --- /dev/null +++ b/28-methods/xxx-log-parser-no-pkg/summary.go @@ -0,0 +1,67 @@ +// 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 ( + "sort" +) + +// summary aggregates the parsing results +type summary struct { + sum map[string]result // metrics per domain + domains []string // unique domain names + total result // total visits for all domains +} + +// newSummary constructs and initializes a new summary +// You can't use its methods without pointer mechanics +func newSummary() *summary { + return &summary{sum: make(map[string]result)} +} + +// Update updates the report for the given parsing result +func (s *summary) update(r result) { + domain := r.domain + if _, ok := s.sum[domain]; !ok { + s.domains = append(s.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 + s.total = s.total.add(r) + s.sum[domain] = r.add(s.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 (s *summary) iterator() (next func() bool, cur func() result) { + sort.Strings(s.domains) + + // remember the last iterated result + var last int + + next = func() bool { + defer func() { last++ }() + return len(s.domains) > last + } + + cur = func() result { + // returns a copy so the caller cannot change it + name := s.domains[last-1] + return s.sum[name] + } + + return +} + +// totals returns the total metrics +func (s *summary) totals() result { + return s.total +} diff --git a/28-methods/xxx-log-parser/main.go b/28-methods/xxx-log-parser/main.go index 1ac29a7..b2d6c64 100644 --- a/28-methods/xxx-log-parser/main.go +++ b/28-methods/xxx-log-parser/main.go @@ -15,12 +15,12 @@ import ( ) func main() { - in := bufio.NewScanner(os.Stdin) + p := report.New() - r := report.New() + in := bufio.NewScanner(os.Stdin) for in.Scan() { - r.Parse(in.Text()) + p.Parse(in.Text()) } - summarize(r.Summarize(), r.Err(), in.Err()) + summarize(p.Summarize(), p.Err(), in.Err()) } diff --git a/28-methods/xxx-log-parser/report/summary.go b/28-methods/xxx-log-parser/report/summary.go index 1f79258..9860e5e 100644 --- a/28-methods/xxx-log-parser/report/summary.go +++ b/28-methods/xxx-log-parser/report/summary.go @@ -26,8 +26,8 @@ func newSummary() *Summary { } // Update updates the report for the given parsing result -func (s *Summary) update(parsed Result) { - domain := parsed.Domain +func (s *Summary) update(r Result) { + domain := r.Domain if _, ok := s.sum[domain]; !ok { s.domains = append(s.domains, domain) } @@ -35,8 +35,8 @@ func (s *Summary) update(parsed Result) { // let the result handle the addition // this allows us to manage the result in once place // and this way it becomes easily extendable - s.total = s.total.add(parsed) - s.sum[domain] = parsed.add(s.sum[domain]) + s.total = s.total.add(r) + s.sum[domain] = r.add(s.sum[domain]) } // Iterator returns `next()` to detect when the iteration ends,