| 
									
										
										
										
											2024-01-26 19:40:38 -06:00
										 |  |  | package models | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2024-01-28 19:41:30 -06:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 	"regexp" | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2024-02-25 12:36:43 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"codeberg.org/danjones000/my-log/tools" | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-25 12:36:43 -06:00
										 |  |  | const DateFormat = tools.DateFormat | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | type Entry struct { | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 	Title  string | 
					
						
							|  |  |  | 	Date   time.Time | 
					
						
							|  |  |  | 	Fields Metas | 
					
						
							| 
									
										
										
										
											2024-02-11 13:50:27 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | type metaRes struct { | 
					
						
							|  |  |  | 	out []byte | 
					
						
							|  |  |  | 	err error | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e Entry) getFieldMarshalChan() chan metaRes { | 
					
						
							|  |  |  | 	size := len(e.Fields) | 
					
						
							|  |  |  | 	ch := make(chan metaRes, size) | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < size; i++ { | 
					
						
							|  |  |  | 		wg.Add(1) | 
					
						
							| 
									
										
										
										
											2024-01-28 12:41:55 -06:00
										 |  |  | 		go func(m Meta) { | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 			defer wg.Done() | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 			if m.Key == "json" { | 
					
						
							|  |  |  | 				if j, ok := m.Value.(json.RawMessage); ok { | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 					sub := Metas{} | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 					json.Unmarshal(j, &sub) | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 					for _, subM := range sub { | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 						o, er := subM.MarshalText() | 
					
						
							|  |  |  | 						ch <- metaRes{o, er} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				o, er := m.MarshalText() | 
					
						
							|  |  |  | 				ch <- metaRes{o, er} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-28 12:41:55 -06:00
										 |  |  | 		}(e.Fields[i]) | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		wg.Wait() | 
					
						
							|  |  |  | 		close(ch) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	return ch | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e Entry) MarshalText() ([]byte, error) { | 
					
						
							|  |  |  | 	e.Title = strings.TrimSpace(e.Title) | 
					
						
							|  |  |  | 	if e.Title == "" { | 
					
						
							| 
									
										
										
										
											2024-01-28 12:41:55 -06:00
										 |  |  | 		return []byte{}, ErrorMissingTitle | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if e.Date == (time.Time{}) { | 
					
						
							| 
									
										
										
										
											2024-01-28 12:41:55 -06:00
										 |  |  | 		return []byte{}, ErrorMissingDate | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	ch := e.getFieldMarshalChan() | 
					
						
							|  |  |  | 	buff := &bytes.Buffer{} | 
					
						
							| 
									
										
										
										
											2024-03-09 15:38:34 -06:00
										 |  |  | 	buff.WriteString("@begin ") | 
					
						
							| 
									
										
										
										
											2024-01-28 01:13:25 -06:00
										 |  |  | 	buff.WriteString(e.Date.Format(DateFormat)) | 
					
						
							|  |  |  | 	buff.WriteString(" - ") | 
					
						
							|  |  |  | 	buff.WriteString(e.Title) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for res := range ch { | 
					
						
							|  |  |  | 		if res.err == nil && len(res.out) > 0 { | 
					
						
							|  |  |  | 			buff.WriteString("\n") | 
					
						
							|  |  |  | 			buff.Write(res.out) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	buff.WriteString(" @end") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return buff.Bytes(), nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-28 08:56:34 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (m *Entry) UnmarshalText(in []byte) error { | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 	re := regexp.MustCompile("(?s)^@begin (.+) - (.+?)[ \n]@") | 
					
						
							|  |  |  | 	match := re.FindSubmatch(in) | 
					
						
							|  |  |  | 	if len(match) == 0 { | 
					
						
							|  |  |  | 		return newParsingError(errors.New("Failed to find title and date")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ch := m.getFieldUnarshalChan(in) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	title := bytes.TrimSpace(match[2]) | 
					
						
							|  |  |  | 	if len(title) == 0 { | 
					
						
							|  |  |  | 		return ErrorMissingTitle | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	m.Title = string(title) | 
					
						
							|  |  |  | 	date := string(bytes.TrimSpace(match[1])) | 
					
						
							|  |  |  | 	if date == "" { | 
					
						
							|  |  |  | 		return ErrorMissingDate | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-25 13:39:12 -06:00
										 |  |  | 	d, e := tools.ParseDate(date) | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 	if e != nil { | 
					
						
							| 
									
										
										
										
											2024-02-25 13:39:12 -06:00
										 |  |  | 		return newParsingError(e) | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	m.Date = d | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for meta := range ch { | 
					
						
							|  |  |  | 		m.Fields = append(m.Fields, meta) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-28 08:56:34 -06:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | func scanEntry(data []byte, atEOF bool) (advance int, token []byte, err error) { | 
					
						
							|  |  |  | 	if atEOF && len(data) == 0 { | 
					
						
							|  |  |  | 		return 0, nil, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if i := bytes.Index(data, []byte{10, 64}); i > 0 { | 
					
						
							|  |  |  | 		return i + 1, data[0:i], nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if atEOF { | 
					
						
							|  |  |  | 		end := []byte{32, 64, 101, 110, 100} | 
					
						
							|  |  |  | 		token = data | 
					
						
							|  |  |  | 		if i := bytes.Index(data, end); i >= 0 { | 
					
						
							|  |  |  | 			token = data[0:i] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return len(data), token, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Request more data. | 
					
						
							|  |  |  | 	return 0, nil, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *Entry) getFieldUnarshalChan(in []byte) chan Meta { | 
					
						
							|  |  |  | 	size := len(in) / 3 // rough estimation | 
					
						
							|  |  |  | 	ch := make(chan Meta, size) | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	read := bytes.NewReader(in) | 
					
						
							|  |  |  | 	scan := bufio.NewScanner(read) | 
					
						
							|  |  |  | 	scan.Split(scanEntry) | 
					
						
							|  |  |  | 	scan.Scan() // throw out first line | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for scan.Scan() { | 
					
						
							|  |  |  | 		wg.Add(1) | 
					
						
							|  |  |  | 		go func(field []byte) { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 			m := new(Meta) | 
					
						
							|  |  |  | 			err := m.UnmarshalText(field) | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 				if m.Key == "json" { | 
					
						
							|  |  |  | 					if j, ok := m.Value.(json.RawMessage); ok { | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 						ms := Metas{} | 
					
						
							|  |  |  | 						json.Unmarshal(j, &ms) | 
					
						
							|  |  |  | 						for _, subM := range ms { | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 							ch <- subM | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					ch <- *m | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-01-28 17:39:42 -06:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}(scan.Bytes()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		wg.Wait() | 
					
						
							|  |  |  | 		close(ch) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	return ch | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-28 19:23:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (e Entry) MarshalJSON() ([]byte, error) { | 
					
						
							| 
									
										
										
										
											2024-01-28 19:41:30 -06:00
										 |  |  | 	if e.Title == "" { | 
					
						
							|  |  |  | 		return []byte{}, ErrorMissingTitle | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if e.Date == (time.Time{}) { | 
					
						
							|  |  |  | 		return []byte{}, ErrorMissingDate | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	out := map[string]any{} | 
					
						
							|  |  |  | 	out["title"] = e.Title | 
					
						
							|  |  |  | 	out["date"] = e.Date.Format(time.RFC3339) | 
					
						
							| 
									
										
										
										
											2024-03-11 16:04:29 -05:00
										 |  |  | 	for k, v := range e.Fields.Map() { | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 		out[k] = v | 
					
						
							| 
									
										
										
										
											2024-01-28 19:41:30 -06:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return json.Marshal(out) | 
					
						
							| 
									
										
										
										
											2024-01-28 19:23:51 -06:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-28 22:02:57 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | func (e *Entry) unmarshalJsonChanHelper(m map[string]any, ch chan Meta, wg *sync.WaitGroup) { | 
					
						
							|  |  |  | 	for k, v := range m { | 
					
						
							|  |  |  | 		wg.Add(1) | 
					
						
							|  |  |  | 		go func(key string, value any) { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 			if key != "json" { | 
					
						
							|  |  |  | 				ch <- Meta{key, value} | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			subM := map[string]any{} | 
					
						
							|  |  |  | 			if s, ok := value.(string); ok { | 
					
						
							|  |  |  | 				dec := json.NewDecoder(strings.NewReader(s)) | 
					
						
							|  |  |  | 				dec.UseNumber() | 
					
						
							|  |  |  | 				dec.Decode(&subM) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				subM = value.(map[string]any) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			e.unmarshalJsonChanHelper(subM, ch, wg) | 
					
						
							|  |  |  | 		}(k, v) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *Entry) getUnmarshalJsonChan(m map[string]any) chan Meta { | 
					
						
							|  |  |  | 	ch := make(chan Meta, len(m)) | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e.unmarshalJsonChanHelper(m, ch, &wg) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		wg.Wait() | 
					
						
							|  |  |  | 		close(ch) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ch | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | func (e *Entry) UnmarshalJSON(in []byte) error { | 
					
						
							|  |  |  | 	out := map[string]any{} | 
					
						
							|  |  |  | 	dec := json.NewDecoder(bytes.NewReader(in)) | 
					
						
							|  |  |  | 	dec.UseNumber() | 
					
						
							|  |  |  | 	err := dec.Decode(&out) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return newParsingError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	title, ok := out["title"].(string) | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 	if !ok || title == "" { | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | 		return ErrorMissingTitle | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	e.Title = title | 
					
						
							|  |  |  | 	dates, ok := out["date"].(string) | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 	if !ok || dates == "" { | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | 		return ErrorMissingDate | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-25 13:39:12 -06:00
										 |  |  | 	date, err := tools.ParseDate(dates) | 
					
						
							| 
									
										
										
										
											2024-03-10 15:26:00 -05:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | 		return newParsingError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	e.Date = date | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 	ch := e.getUnmarshalJsonChan(out) | 
					
						
							|  |  |  | 	for m := range ch { | 
					
						
							|  |  |  | 		if m.Key == "title" || m.Key == "date" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} else if vs, ok := m.Value.(string); ok { | 
					
						
							| 
									
										
										
										
											2024-02-25 13:39:12 -06:00
										 |  |  | 			if vd, err := tools.ParseDate(vs); err == nil { | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | 				m.Value = vd | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				m.Value = vs | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-01-30 23:40:47 -06:00
										 |  |  | 		} else if n, ok := m.Value.(json.Number); ok { | 
					
						
							| 
									
										
										
										
											2024-01-29 22:07:18 -06:00
										 |  |  | 			it, _ := n.Int64() | 
					
						
							|  |  |  | 			fl, _ := n.Float64() | 
					
						
							|  |  |  | 			if float64(it) == fl { | 
					
						
							|  |  |  | 				m.Value = it | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				m.Value = fl | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		e.Fields = append(e.Fields, m) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-28 22:02:57 -06:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } |