add: methods log parser no pkg

This commit is contained in:
Inanc Gumus
2019-05-10 15:51:19 +03:00
parent 500f264d8b
commit ca0a9b4f6b
11 changed files with 286 additions and 8 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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,