Artifact c8e6b2974fad805ad78ef1378a5fbb6b591852fd8a8a2e2be9edaba6ea854886:
- File
internal/deck/encode.go
— part of check-in
[e9ec7390c7]
at
2026-10-18 16:44:03
on branch feat/submanifest-reader
— feat(deck): canonical encoding and sub-manifest serializers
draft-fossil-submanifests-00 §6.2 fixes minimal escaping for every filename a tree construction emits: exactly space, newline and backslash are escaped, every other byte raw. CanonicalEncode is that encoding; S cards and a sub-manifest's F cards now marshal with it, while a flat manifest keeps §4.3's, so no existing artifact changes name.
MarshalLeaf and MarshalInterior serialize the two sub-manifest shapes, and LeafCard gives one leaf F card's bytes for the §6.3 cost. A test reproduces the Appendix A.2 leaf byte for byte. (user: [email protected], size: 2587) [annotate] [blame] [check-ins using] [more...]
package deck import "strings" // FossilEncode escapes a string for use as an artifact/sync card field value, // per §4.3 of the format spec. It mirrors canonical fossil's fossilize(): the // eight bytes newline, space, tab, carriage return, vertical tab, form feed, // NUL and backslash are each written as a two-character backslash escape; all // other bytes pass through unchanged. func FossilEncode(s string) string { var b strings.Builder b.Grow(len(s)) for i := 0; i < len(s); i++ { switch s[i] { case '\n': b.WriteString(`\n`) case ' ': b.WriteString(`\s`) case '\t': b.WriteString(`\t`) case '\r': b.WriteString(`\r`) case '\v': b.WriteString(`\v`) case '\f': b.WriteString(`\f`) case 0: b.WriteString(`\0`) case '\\': b.WriteString(`\\`) default: b.WriteByte(s[i]) } } return b.String() } // FossilDecode reverses FossilEncode, per §4.3. It mirrors canonical fossil's // defossilize(): each recognized escape maps back to its raw byte, and an // unrecognized escape drops the backslash and keeps the following byte literally. // A lone trailing backslash (which a conforming encoder never emits) is left // as-is rather than consuming a nonexistent following byte. func FossilDecode(s string) string { var b strings.Builder b.Grow(len(s)) for i := 0; i < len(s); i++ { if s[i] != '\\' || i+1 >= len(s) { b.WriteByte(s[i]) continue } i++ switch s[i] { case 'n': b.WriteByte('\n') case 's': b.WriteByte(' ') case 't': b.WriteByte('\t') case 'r': b.WriteByte('\r') case 'v': b.WriteByte('\v') case 'f': b.WriteByte('\f') case '0': b.WriteByte(0) case '\\': b.WriteByte('\\') default: // §4.3: an unrecognized escape drops the backslash and keeps the byte. b.WriteByte(s[i]) } } return b.String() } // CanonicalEncode is the minimal escaping draft-fossil-submanifests-00 §6.2 // fixes for every filename a tree construction emits -- S card filenames and // a leaf's F card filename and old-name tokens: exactly space, newline and // backslash are escaped; every other byte, the decoder's other escape bytes // included, is written raw. FossilDecode reverses it. A flat manifest keeps // FossilEncode, so no artifact written before trees existed changes name. func CanonicalEncode(s string) string { var b strings.Builder b.Grow(len(s)) for i := 0; i < len(s); i++ { switch s[i] { case '\n': b.WriteString(`\n`) case ' ': b.WriteString(`\s`) case '\\': b.WriteString(`\\`) default: b.WriteByte(s[i]) } } return b.String() }