refactor: oop log parser
This commit is contained in:
@ -10,8 +10,8 @@ package main
|
|||||||
import "sort"
|
import "sort"
|
||||||
|
|
||||||
type analysis struct {
|
type analysis struct {
|
||||||
sum map[string]result // metrics per domain
|
sum map[string]result // metrics per group key
|
||||||
keys []string // unique keys
|
keys []string // unique group keys
|
||||||
groupKey groupFunc
|
groupKey groupFunc
|
||||||
filter filterFunc
|
filter filterFunc
|
||||||
}
|
}
|
||||||
@ -24,6 +24,31 @@ func newAnalysis() *analysis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transform the result
|
||||||
|
func (a *analysis) transform(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 yields an analysis result
|
||||||
|
func (a *analysis) each(yield resultFn) error {
|
||||||
|
sort.Strings(a.keys)
|
||||||
|
|
||||||
|
for _, key := range a.keys {
|
||||||
|
yield(a.sum[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *analysis) groupBy(g groupFunc) {
|
func (a *analysis) groupBy(g groupFunc) {
|
||||||
if g != nil {
|
if g != nil {
|
||||||
a.groupKey = g
|
a.groupKey = g
|
||||||
@ -35,26 +60,3 @@ func (a *analysis) filterBy(f filterFunc) {
|
|||||||
a.filter = f
|
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,12 +10,12 @@ import (
|
|||||||
// You need to run:
|
// You need to run:
|
||||||
// go get -u github.com/wcharczuk/go-chart
|
// go get -u github.com/wcharczuk/go-chart
|
||||||
|
|
||||||
type chartSummary struct {
|
type chartReport struct {
|
||||||
title string
|
title string
|
||||||
width, height int
|
width, height int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *chartSummary) summarize(results iterator) error {
|
func (s *chartReport) report(results iterator) error {
|
||||||
w := os.Stdout
|
w := os.Stdout
|
||||||
|
|
||||||
donut := c.DonutChart{
|
donut := c.DonutChart{
|
@ -1,4 +1,4 @@
|
|||||||
// For more tutorials: https://bp.learngoprogramming.com
|
// For more tutorials: https://bj.learngoprogramming.com
|
||||||
//
|
//
|
||||||
// Copyright © 2018 Inanc Gumus
|
// Copyright © 2018 Inanc Gumus
|
||||||
// Learn Go Programming Course
|
// Learn Go Programming Course
|
||||||
@ -14,26 +14,26 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type jsonParser struct {
|
type jsonLog struct {
|
||||||
r io.Reader
|
reader io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJSONParser(r io.Reader) *jsonParser {
|
func newJSONLog(r io.Reader) *jsonLog {
|
||||||
return &jsonParser{r: r}
|
return &jsonLog{reader: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *jsonParser) parse(handle resultFn) error {
|
func (j *jsonLog) each(yield resultFn) error {
|
||||||
defer readClose(p.r)
|
defer readClose(j.reader)
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(p.r)
|
bytes, err := ioutil.ReadAll(j.reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseJSON(bytes, handle)
|
return extractJSON(bytes, yield)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseJSON(bytes []byte, handle resultFn) error {
|
func extractJSON(bytes []byte, yield resultFn) error {
|
||||||
var rs []struct {
|
var rs []struct {
|
||||||
Domain string
|
Domain string
|
||||||
Page string
|
Page string
|
||||||
@ -49,7 +49,7 @@ func parseJSON(bytes []byte, handle resultFn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range rs {
|
for _, r := range rs {
|
||||||
handle(result{
|
yield(result{
|
||||||
domain: r.Domain,
|
domain: r.Domain,
|
||||||
page: r.Page,
|
page: r.Page,
|
||||||
visits: r.Visits,
|
visits: r.Visits,
|
@ -14,20 +14,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
a := newAnalysis()
|
an := newAnalysis()
|
||||||
// a.filterBy(notUsing(domainExtFilter("io", "com")))
|
// an.filterBy(notUsing(domainExtFilter("io", "com")))
|
||||||
// a.groupBy(domainGrouper)
|
// an.filterBy(domainFilter("org"))
|
||||||
|
// an.groupBy(domainGrouper)
|
||||||
|
|
||||||
p := newTextParser(os.Stdin)
|
src := newTextLog(os.Stdin)
|
||||||
s := newTextSummary()
|
dst := newTextReport()
|
||||||
|
|
||||||
// s := &chartSummary{
|
// s := &chartReport{
|
||||||
// title: "visits per domain",
|
// title: "visits per domain",
|
||||||
// width: 1920,
|
// width: 1920,
|
||||||
// height: 800,
|
// height: 800,
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if err := report(p, a, s); err != nil {
|
pipe := newPipeline(src, dst, an)
|
||||||
|
|
||||||
|
if err := pipe.run(); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
interfaces/log-parser/oop/pipeline.go
Normal file
68
interfaces/log-parser/oop/pipeline.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resultFn func(result)
|
||||||
|
|
||||||
|
type iterator interface {
|
||||||
|
each(resultFn) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformer interface {
|
||||||
|
transform(result)
|
||||||
|
iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
type reporter interface {
|
||||||
|
report(iterator) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeline struct {
|
||||||
|
src iterator
|
||||||
|
dst reporter
|
||||||
|
tran transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPipeline(source iterator, r reporter, t transformer) *pipeline {
|
||||||
|
return &pipeline{
|
||||||
|
src: source,
|
||||||
|
dst: r,
|
||||||
|
tran: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromFile generates a default report
|
||||||
|
func fromFile(path string) (err error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var src iterator
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(path, ".txt"):
|
||||||
|
src = newTextLog(f)
|
||||||
|
case strings.HasSuffix(path, ".json"):
|
||||||
|
src = newJSONLog(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := newPipeline(src, newTextReport(), newAnalysis())
|
||||||
|
return p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) run() error {
|
||||||
|
if err := p.src.each(p.tran.transform); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.dst.report(p.tran)
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
// 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())
|
|
||||||
}
|
|
@ -15,36 +15,36 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type textParser struct {
|
type textLog struct {
|
||||||
r io.Reader
|
reader io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTextParser(r io.Reader) *textParser {
|
func newTextLog(r io.Reader) *textLog {
|
||||||
return &textParser{r: r}
|
return &textLog{reader: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *textParser) parse(handle resultFn) error {
|
func (p *textLog) each(yield resultFn) error {
|
||||||
defer readClose(p.r)
|
defer readClose(p.reader)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
l = 1
|
l = 1
|
||||||
in = bufio.NewScanner(p.r)
|
in = bufio.NewScanner(p.reader)
|
||||||
)
|
)
|
||||||
|
|
||||||
for in.Scan() {
|
for in.Scan() {
|
||||||
r, err := parseFields(in.Text())
|
r, err := extractFields(in.Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("line %d: %v", l, err)
|
return fmt.Errorf("line %d: %v", l, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
handle(r)
|
yield(r)
|
||||||
l++
|
l++
|
||||||
}
|
}
|
||||||
|
|
||||||
return in.Err()
|
return in.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFields(s string) (r result, err error) {
|
func extractFields(s string) (r result, err error) {
|
||||||
fields := strings.Fields(s)
|
fields := strings.Fields(s)
|
||||||
if len(fields) != fieldsLength {
|
if len(fields) != fieldsLength {
|
||||||
return r, fmt.Errorf("wrong number of fields %q", fields)
|
return r, fmt.Errorf("wrong number of fields %q", fields)
|
@ -21,13 +21,13 @@ const (
|
|||||||
flags = 0
|
flags = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
type textSummary struct{}
|
type textReport struct{}
|
||||||
|
|
||||||
func newTextSummary() *textSummary {
|
func newTextReport() *textReport {
|
||||||
return new(textSummary)
|
return new(textReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *textSummary) summarize(results iterator) error {
|
func (s *textReport) report(results iterator) error {
|
||||||
w := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, ' ', flags)
|
w := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, ' ', flags)
|
||||||
|
|
||||||
write := fmt.Fprintf
|
write := fmt.Fprintf
|
Reference in New Issue
Block a user