move: log parser
This commit is contained in:
5
interfaces/log-parser/oop/Makefile
Normal file
5
interfaces/log-parser/oop/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
r:
|
||||
go run . < ../../logs/log.txt
|
||||
|
||||
t:
|
||||
time go run . < ../../logs/log.txt
|
||||
60
interfaces/log-parser/oop/analysis.go
Normal file
60
interfaces/log-parser/oop/analysis.go
Normal 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 "sort"
|
||||
|
||||
type analysis struct {
|
||||
sum map[string]result // metrics per domain
|
||||
keys []string // unique keys
|
||||
groupKey groupFunc
|
||||
filter filterFunc
|
||||
}
|
||||
|
||||
func newAnalysis() *analysis {
|
||||
return &analysis{
|
||||
sum: make(map[string]result),
|
||||
groupKey: domainGrouper,
|
||||
filter: noopFilter,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) groupBy(g groupFunc) {
|
||||
if g != nil {
|
||||
a.groupKey = g
|
||||
}
|
||||
}
|
||||
|
||||
func (a *analysis) filterBy(f filterFunc) {
|
||||
if f != nil {
|
||||
a.filter = f
|
||||
}
|
||||
}
|
||||
|
||||
// analyse the given result
|
||||
func (a *analysis) analyse(r result) {
|
||||
if !a.filter(r) {
|
||||
return
|
||||
}
|
||||
|
||||
key := a.groupKey(r)
|
||||
if _, ok := a.sum[key]; !ok {
|
||||
a.keys = append(a.keys, key)
|
||||
}
|
||||
|
||||
a.sum[key] = r.add(a.sum[key])
|
||||
}
|
||||
|
||||
// each sends an analysis result to `handle`
|
||||
func (a *analysis) each(handle resultFn) {
|
||||
sort.Strings(a.keys)
|
||||
|
||||
for _, domain := range a.keys {
|
||||
handle(a.sum[domain])
|
||||
}
|
||||
}
|
||||
45
interfaces/log-parser/oop/chartsummary.go
Normal file
45
interfaces/log-parser/oop/chartsummary.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
c "github.com/wcharczuk/go-chart"
|
||||
)
|
||||
|
||||
// You need to run:
|
||||
// go get -u github.com/wcharczuk/go-chart
|
||||
|
||||
type chartSummary struct {
|
||||
title string
|
||||
width, height int
|
||||
}
|
||||
|
||||
func (s *chartSummary) summarize(results iterator) error {
|
||||
w := os.Stdout
|
||||
|
||||
donut := c.DonutChart{
|
||||
Title: s.title,
|
||||
TitleStyle: c.Style{
|
||||
FontSize: 35,
|
||||
Show: true,
|
||||
FontColor: c.ColorAlternateGreen,
|
||||
},
|
||||
Width: s.width,
|
||||
Height: s.height,
|
||||
}
|
||||
|
||||
results.each(func(r result) {
|
||||
v := c.Value{
|
||||
Label: r.domain + r.page + ": " + strconv.Itoa(r.visits),
|
||||
Value: float64(r.visits),
|
||||
Style: c.Style{
|
||||
FontSize: 14,
|
||||
},
|
||||
}
|
||||
|
||||
donut.Values = append(donut.Values, v)
|
||||
})
|
||||
|
||||
return donut.Render(c.SVG, w)
|
||||
}
|
||||
36
interfaces/log-parser/oop/filters.go
Normal file
36
interfaces/log-parser/oop/filters.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
type filterFunc func(result) bool
|
||||
|
||||
func noopFilter(r result) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func notUsing(filter filterFunc) filterFunc {
|
||||
return func(r result) bool {
|
||||
return !filter(r)
|
||||
}
|
||||
}
|
||||
|
||||
func domainExtFilter(domains ...string) filterFunc {
|
||||
return func(r result) bool {
|
||||
for _, domain := range domains {
|
||||
if strings.HasSuffix(r.domain, "."+domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func domainFilter(domain string) filterFunc {
|
||||
return func(r result) bool {
|
||||
return strings.Contains(r.domain, domain)
|
||||
}
|
||||
}
|
||||
|
||||
func orgDomainsFilter(r result) bool {
|
||||
return strings.HasSuffix(r.domain, ".org")
|
||||
}
|
||||
15
interfaces/log-parser/oop/groupers.go
Normal file
15
interfaces/log-parser/oop/groupers.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
type groupFunc func(result) string
|
||||
|
||||
// domainGrouper groups by domain.
|
||||
// but it keeps the other fields.
|
||||
// for example: it returns pages as well, but you shouldn't use them.
|
||||
// exercise: write a function that erases the unnecessary data.
|
||||
func domainGrouper(r result) string {
|
||||
return r.domain
|
||||
}
|
||||
|
||||
func pageGrouper(r result) string {
|
||||
return r.domain + r.page
|
||||
}
|
||||
61
interfaces/log-parser/oop/jsonparser.go
Normal file
61
interfaces/log-parser/oop/jsonparser.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// For more tutorials: https://bp.learngoprogramming.com
|
||||
//
|
||||
// Copyright © 2018 Inanc Gumus
|
||||
// Learn Go Programming Course
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type jsonParser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func newJSONParser(r io.Reader) *jsonParser {
|
||||
return &jsonParser{r: r}
|
||||
}
|
||||
|
||||
func (p *jsonParser) parse(handle resultFn) error {
|
||||
defer readClose(p.r)
|
||||
|
||||
bytes, err := ioutil.ReadAll(p.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return parseJSON(bytes, handle)
|
||||
}
|
||||
|
||||
func parseJSON(bytes []byte, handle resultFn) error {
|
||||
var rs []struct {
|
||||
Domain string
|
||||
Page string
|
||||
Visits int
|
||||
Uniques int
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bytes, &rs); err != nil {
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
return fmt.Errorf("%v %q", serr, bytes[:serr.Offset])
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
handle(result{
|
||||
domain: r.Domain,
|
||||
page: r.Page,
|
||||
visits: r.Visits,
|
||||
uniques: r.Uniques,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
37
interfaces/log-parser/oop/main.go
Normal file
37
interfaces/log-parser/oop/main.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 (
|
||||
"log"
|
||||
// "fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := newAnalysis()
|
||||
// a.filterBy(notUsing(domainExtFilter("io", "com")))
|
||||
// a.groupBy(domainGrouper)
|
||||
|
||||
p := newTextParser(os.Stdin)
|
||||
s := newTextSummary()
|
||||
|
||||
// s := &chartSummary{
|
||||
// title: "visits per domain",
|
||||
// width: 1920,
|
||||
// height: 800,
|
||||
// }
|
||||
|
||||
if err := report(p, a, s); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// if err := reportFromFile(os.Args[1]); err != nil {
|
||||
// log.Fatalln(err)
|
||||
// }
|
||||
}
|
||||
18
interfaces/log-parser/oop/readclose.go
Normal file
18
interfaces/log-parser/oop/readclose.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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 (
|
||||
"io"
|
||||
)
|
||||
|
||||
func readClose(r io.Reader) {
|
||||
if rc, ok := r.(io.Closer); ok {
|
||||
rc.Close()
|
||||
}
|
||||
}
|
||||
63
interfaces/log-parser/oop/report.go
Normal file
63
interfaces/log-parser/oop/report.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type resultFn func(result)
|
||||
|
||||
type parser interface {
|
||||
parse(resultFn) error
|
||||
}
|
||||
|
||||
type analyser interface {
|
||||
analyse(result)
|
||||
}
|
||||
|
||||
type iterator interface {
|
||||
each(resultFn)
|
||||
}
|
||||
|
||||
type summarizer interface {
|
||||
summarize(iterator) error
|
||||
}
|
||||
|
||||
func report(p parser, a analyser, s summarizer) error {
|
||||
if err := p.parse(a.analyse); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
it, ok := a.(iterator)
|
||||
if !ok {
|
||||
return errors.New("cannot iterate on analyser")
|
||||
}
|
||||
|
||||
return s.summarize(it)
|
||||
}
|
||||
|
||||
// reportFromFile generates a default report
|
||||
func reportFromFile(path string) (err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p parser
|
||||
switch {
|
||||
case strings.HasSuffix(path, ".txt"):
|
||||
p = newTextParser(f)
|
||||
case strings.HasSuffix(path, ".json"):
|
||||
p = newJSONParser(f)
|
||||
}
|
||||
|
||||
return report(p, newAnalysis(), newTextSummary())
|
||||
}
|
||||
16
interfaces/log-parser/oop/result.go
Normal file
16
interfaces/log-parser/oop/result.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
type result struct {
|
||||
domain string
|
||||
page string
|
||||
visits int
|
||||
uniques int
|
||||
}
|
||||
|
||||
func (r result) add(other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
66
interfaces/log-parser/oop/textparser.go
Normal file
66
interfaces/log-parser/oop/textparser.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// For more tutorials: https://bp.learngoprogramming.com
|
||||
//
|
||||
// Copyright © 2018 Inanc Gumus
|
||||
// Learn Go Programming Course
|
||||
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type textParser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func newTextParser(r io.Reader) *textParser {
|
||||
return &textParser{r: r}
|
||||
}
|
||||
|
||||
func (p *textParser) parse(handle resultFn) error {
|
||||
defer readClose(p.r)
|
||||
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(p.r)
|
||||
)
|
||||
|
||||
for in.Scan() {
|
||||
r, err := parseFields(in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
handle(r)
|
||||
l++
|
||||
}
|
||||
|
||||
return in.Err()
|
||||
}
|
||||
|
||||
func parseFields(s string) (r result, err error) {
|
||||
fields := strings.Fields(s)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong number of fields %q", fields)
|
||||
}
|
||||
|
||||
r.domain, r.page = fields[0], fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
49
interfaces/log-parser/oop/textsummary.go
Normal file
49
interfaces/log-parser/oop/textsummary.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// TODO: make this configurable? or exercise?
|
||||
const (
|
||||
minWidth = 0
|
||||
tabWidth = 4
|
||||
padding = 4
|
||||
flags = 0
|
||||
)
|
||||
|
||||
type textSummary struct{}
|
||||
|
||||
func newTextSummary() *textSummary {
|
||||
return new(textSummary)
|
||||
}
|
||||
|
||||
func (s *textSummary) summarize(results iterator) error {
|
||||
w := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, ' ', flags)
|
||||
|
||||
write := fmt.Fprintf
|
||||
|
||||
write(w, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n")
|
||||
write(w, "-------\t-----\t------\t-------\n")
|
||||
|
||||
var total result
|
||||
results.each(func(r result) {
|
||||
total = total.add(r)
|
||||
|
||||
write(w, "%s\t%s\t%d\t%d\n", r.domain, r.page, r.visits, r.uniques)
|
||||
})
|
||||
|
||||
write(w, "\t\t\t\n")
|
||||
write(w, "%s\t%s\t%d\t%d\n", "TOTAL", "", total.visits, total.uniques)
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
Reference in New Issue
Block a user