this repo has no description
at sl/knotmirror 116 lines 2.2 kB view raw
1package models 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "strings" 9 "time" 10 "unicode/utf8" 11 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 "tangled.org/core/api/tangled" 14) 15 16type String struct { 17 Did syntax.DID 18 Rkey string 19 20 Filename string 21 Description string 22 Contents string 23 Created time.Time 24 Edited *time.Time 25} 26 27func (s *String) AtUri() syntax.ATURI { 28 return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey)) 29} 30 31func (s *String) AsRecord() tangled.String { 32 return tangled.String{ 33 Filename: s.Filename, 34 Description: s.Description, 35 Contents: s.Contents, 36 CreatedAt: s.Created.Format(time.RFC3339), 37 } 38} 39 40var _ Validator = new(String) 41 42func (s *String) Validate() error { 43 var err error 44 if utf8.RuneCountInString(s.Filename) > 140 { 45 err = errors.Join(err, fmt.Errorf("filename too long")) 46 } 47 48 if utf8.RuneCountInString(s.Description) > 280 { 49 err = errors.Join(err, fmt.Errorf("description too long")) 50 } 51 52 if len(s.Contents) == 0 { 53 err = errors.Join(err, fmt.Errorf("contents is empty")) 54 } 55 56 return err 57} 58 59func StringFromRecord(did, rkey string, record tangled.String) String { 60 created, err := time.Parse(record.CreatedAt, time.RFC3339) 61 if err != nil { 62 created = time.Now() 63 } 64 return String{ 65 Did: syntax.DID(did), 66 Rkey: rkey, 67 Filename: record.Filename, 68 Description: record.Description, 69 Contents: record.Contents, 70 Created: created, 71 } 72} 73 74type StringStats struct { 75 LineCount uint64 76 ByteCount uint64 77} 78 79func (s String) Stats() StringStats { 80 lineCount, err := countLines(strings.NewReader(s.Contents)) 81 if err != nil { 82 // non-fatal 83 // TODO: log this? 84 } 85 86 return StringStats{ 87 LineCount: uint64(lineCount), 88 ByteCount: uint64(len(s.Contents)), 89 } 90} 91 92func countLines(r io.Reader) (int, error) { 93 buf := make([]byte, 32*1024) 94 bufLen := 0 95 count := 0 96 nl := []byte{'\n'} 97 98 for { 99 c, err := r.Read(buf) 100 if c > 0 { 101 bufLen += c 102 } 103 count += bytes.Count(buf[:c], nl) 104 105 switch { 106 case err == io.EOF: 107 /* handle last line not having a newline at the end */ 108 if bufLen >= 1 && buf[(bufLen-1)%(32*1024)] != '\n' { 109 count++ 110 } 111 return count, nil 112 case err != nil: 113 return 0, err 114 } 115 } 116}