forked from
tangled.org/core
this repo has no description
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}