✨ Unmarshal log
This commit is contained in:
		
					parent
					
						
							
								44c9736535
							
						
					
				
			
			
				commit
				
					
						85abc8cb34
					
				
			
		
					 2 changed files with 180 additions and 0 deletions
				
			
		|  | @ -1 +1,69 @@ | ||||||
| package models | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var reg = regexp.MustCompile("(?sm)^@begin .+?(^| )@end") | ||||||
|  | 
 | ||||||
|  | type Log struct { | ||||||
|  | 	Name    string | ||||||
|  | 	Entries []Entry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Log) UnmarshalText(in []byte) error { | ||||||
|  | 	ch := l.getLogUnarshalChan(in) | ||||||
|  | 	for entry := range ch { | ||||||
|  | 		l.Entries = append(l.Entries, entry) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func scanLog(data []byte, atEOF bool) (advance int, token []byte, err error) { | ||||||
|  | 	if atEOF && len(data) == 0 { | ||||||
|  | 		// done | ||||||
|  | 		return 0, nil, nil | ||||||
|  | 	} | ||||||
|  | 	m := reg.FindIndex(data) | ||||||
|  | 	if len(m) == 0 && atEOF { | ||||||
|  | 		// all trash | ||||||
|  | 		return len(data), nil, nil | ||||||
|  | 	} else if len(m) == 0 && !atEOF { | ||||||
|  | 		// get more | ||||||
|  | 		return 0, nil, nil | ||||||
|  | 	} | ||||||
|  | 	return m[1], data[m[0]:m[1]], nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (l *Log) getLogUnarshalChan(in []byte) chan Entry { | ||||||
|  | 	size := len(in) / 10 // rough estimation | ||||||
|  | 	ch := make(chan Entry, size) | ||||||
|  | 	var wg sync.WaitGroup | ||||||
|  | 
 | ||||||
|  | 	read := bytes.NewReader(in) | ||||||
|  | 	scan := bufio.NewScanner(read) | ||||||
|  | 	scan.Split(scanLog) | ||||||
|  | 
 | ||||||
|  | 	for scan.Scan() { | ||||||
|  | 		wg.Add(1) | ||||||
|  | 		go func(field []byte) { | ||||||
|  | 			defer wg.Done() | ||||||
|  | 			f := new(Entry) | ||||||
|  | 			err := f.UnmarshalText(field) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			ch <- *f | ||||||
|  | 		}(scan.Bytes()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		wg.Wait() | ||||||
|  | 		close(ch) | ||||||
|  | 	}() | ||||||
|  | 	return ch | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										112
									
								
								models/log_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								models/log_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | ||||||
|  | package models | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _ encoding.TextUnmarshaler = new(Log) | ||||||
|  | 
 | ||||||
|  | const first = "@begin January 01, 2020 at 01:02:03AM -0000 - This is simple @end\n" | ||||||
|  | const second = `@begin January 01, 2020 at 01:02:05AM -0000 - We have one thing here | ||||||
|  | @foo bar @end | ||||||
|  | ` | ||||||
|  | const third = `@begin January 01, 2020 at 01:02:07AM -0000 - We have two things here | ||||||
|  | @num 42 | ||||||
|  | @newline true | ||||||
|  | @end | ||||||
|  | ` | ||||||
|  | const fourth = `@begin 2020-01-01T01:02:09Z - ISO-8601 date | ||||||
|  | @end | ||||||
|  | ` | ||||||
|  | const skip = "@ignoreme true\n" | ||||||
|  | const fifth = `@begin 2020-01-01T01:02:11+00:00 - ISO-8601 other date | ||||||
|  | @with-timezone yes @end | ||||||
|  | ` | ||||||
|  | const badEntry = "@begin bad date no title @end\n" | ||||||
|  | const all = first + second + third + fourth + skip + fifth | ||||||
|  | 
 | ||||||
|  | func TestLogUnmarshalBig(t *testing.T) { | ||||||
|  | 	l := &Log{Name: "test-log"} | ||||||
|  | 	err := l.UnmarshalText([]byte(all)) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	require.Len(t, l.Entries, 5) | ||||||
|  | 
 | ||||||
|  | 	var e Entry | ||||||
|  | 	var f bool | ||||||
|  | 	if e, f = findEntry(t, l, "This is simple", true); !f { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	assert.Len(t, e.Fields, 0) | ||||||
|  | 
 | ||||||
|  | 	for _, e := range l.Entries { | ||||||
|  | 		findMeta(t, e, "ignoreme", true, false) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestLogUnmarshalIgnoreGarbage(t *testing.T) { | ||||||
|  | 	l := &Log{Name: "test-log"} | ||||||
|  | 	in := "ignore this\n" + second + "some crap also skip -> " + third + skip | ||||||
|  | 	err := l.UnmarshalText([]byte(in)) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	require.Len(t, l.Entries, 1) | ||||||
|  | 
 | ||||||
|  | 	en := l.Entries[0] | ||||||
|  | 	assert.Equal(t, "We have one thing here", en.Title) | ||||||
|  | 	assert.Len(t, en.Fields, 1) | ||||||
|  | 	assert.Equal(t, "foo", en.Fields[0].Key) | ||||||
|  | 	assert.Equal(t, "bar", en.Fields[0].Value) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestLogUnmarshalEmpty(t *testing.T) { | ||||||
|  | 	l := &Log{Name: "test-log"} | ||||||
|  | 	err := l.UnmarshalText([]byte{}) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	require.Len(t, l.Entries, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestLogUnmarshalBad(t *testing.T) { | ||||||
|  | 	l := &Log{Name: "test-log"} | ||||||
|  | 	err := l.UnmarshalText([]byte(badEntry)) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	require.Len(t, l.Entries, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func findEntry(t *testing.T, log *Log, title string, shouldFind bool) (Entry, bool) { | ||||||
|  | 	var ret Entry | ||||||
|  | 	found := false | ||||||
|  | 	for _, e := range log.Entries { | ||||||
|  | 		if e.Title == title { | ||||||
|  | 			ret = e | ||||||
|  | 			found = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if shouldFind { | ||||||
|  | 		found = assert.Truef(t, found, "Unable to found entry %s", title) | ||||||
|  | 	} else { | ||||||
|  | 		found = assert.Falsef(t, found, "Entry %s should not have been found but was", title) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret, found | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func findMeta(t *testing.T, entry Entry, key string, value any, shouldFind bool) (Meta, bool) { | ||||||
|  | 	var ret Meta | ||||||
|  | 	found := false | ||||||
|  | 	for _, m := range entry.Fields { | ||||||
|  | 		if m.Key == key && m.Value == value { | ||||||
|  | 			ret = m | ||||||
|  | 			found = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if shouldFind { | ||||||
|  | 		found = assert.Truef(t, found, "Unable to found meta %s", key) | ||||||
|  | 	} else { | ||||||
|  | 		found = assert.Falsef(t, found, "Meta %s should not have been found but was", key) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret, found | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue