diff --git a/29-methods/logparser-funcs/log.txt b/29-methods/logparser-funcs/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/29-methods/logparser-funcs/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/29-methods/logparser-funcs/log_err_missing.txt b/29-methods/logparser-funcs/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/29-methods/logparser-funcs/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/29-methods/logparser-funcs/log_err_negative.txt b/29-methods/logparser-funcs/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/29-methods/logparser-funcs/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/29-methods/logparser-funcs/log_err_str.txt b/29-methods/logparser-funcs/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/29-methods/logparser-funcs/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/29-methods/logparser-funcs/main.go b/29-methods/logparser-funcs/main.go new file mode 100644 index 0000000..2ff0a0b --- /dev/null +++ b/29-methods/logparser-funcs/main.go @@ -0,0 +1,26 @@ +// 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() { + res := parse(p, in.Text()) + updateSummary(p.summary, res) + } + + summarize(summarizeParse(p)) + dumpErrs(errParse(p), in.Err()) +} diff --git a/29-methods/logparser-funcs/parser.go b/29-methods/logparser-funcs/parser.go new file mode 100644 index 0000000..478a1af --- /dev/null +++ b/29-methods/logparser-funcs/parser.go @@ -0,0 +1,70 @@ +// 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" +) + +// 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 parse(p *parser, line string) (r result) { + // if there was an error do not continue + if p.lerr != nil { + return + } + + // chain the parser's error to the result's + r = parseLine(p, line) + if p.lines++; p.lerr != nil { + p.lerr = fmt.Errorf("line #%d: %s", p.lines, p.lerr) + } + return +} + +// parse parses a single log line +func parseLine(p *parser, line string) (r result) { + fields := strings.Fields(line) + if len(fields) != 2 { + // p.lerr = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) + p.lerr = fmt.Errorf("missing fields: %v", fields) + return + } + + r.domain = fields[0] + r.visits, p.lerr = strconv.Atoi(fields[1]) + + if r.visits < 0 || p.lerr != nil { + p.lerr = fmt.Errorf("incorrect visits: %q", fields[1]) + } + return +} + +// summarizeParse summarizes the parsing results. +// Only use it after the parsing is done. +func summarizeParse(p *parser) *summary { + return p.summary +} + +// errParse returns the last error encountered +func errParse(p *parser) error { + return p.lerr +} diff --git a/29-methods/logparser-funcs/result.go b/29-methods/logparser-funcs/result.go new file mode 100644 index 0000000..fac18dc --- /dev/null +++ b/29-methods/logparser-funcs/result.go @@ -0,0 +1,27 @@ +// 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 + +// 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 addResult(r result, other result) result { + return result{ + domain: r.domain, + visits: r.visits + other.visits, + } +} diff --git a/29-methods/logparser-funcs/summarize.go b/29-methods/logparser-funcs/summarize.go new file mode 100644 index 0000000..9a10929 --- /dev/null +++ b/29-methods/logparser-funcs/summarize.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 main + +import ( + "fmt" + "strings" +) + +// summarize prints the parsing results. +func summarize(s *summary) { + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + for next, cur := iteratorSummary(s); next(); { + r := cur() + fmt.Printf("%-30s %10d\n", r.domain, r.visits) + } + + t := totalsSummary(s) + fmt.Printf("\n"+"%-30s %10d\n", "TOTAL", t.visits) +} + +// 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) + } + } +} diff --git a/29-methods/logparser-funcs/summary.go b/29-methods/logparser-funcs/summary.go new file mode 100644 index 0000000..b312ea8 --- /dev/null +++ b/29-methods/logparser-funcs/summary.go @@ -0,0 +1,70 @@ +// 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)} +} + +// updateSummary updates the report for the given parsing result +func updateSummary(s *summary, 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 = addResult(s.total, r) + s.sum[domain] = addResult(r, s.sum[domain]) +} + +// iteratorSummary returns `next()` to detect when the iteration ends, +// and a `cur()` to return the current result. +// iterator iterates sorted by domains. +func iteratorSummary(s *summary) (next func() bool, cur func() result) { + sort.Strings(s.domains) + + // remember the last iterated result + var last int + + next = func() bool { + // done := len(s.domains) > last + // last++ + // return done + 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 +} + +// totalsSummary returns the total metrics +func totalsSummary(s *summary) result { + return s.total +}