Documentation ¶
Overview ¶
Package sauce is a Go module that parses SAUCE metadata.
What is SAUCE? ¶
The Standard Architecture for Universal Comment Extensions is an architecture and protocol for attaching meta data and comments to files. While intended for ANSI art files, SAUCE has always had provisions for many different file types.
Why SAUCE? ¶
From the original SAUCE specification:
In the early 1990s there was a growing popularity in ANSI artwork. The ANSI art groups regularly released the works of their members over a certain period. Some of the bigger groups also included specialised viewers in each ‘artpack’. One of the problems with these artpacks was a lack of standardized way to provide meta data to the art, such as the title of the artwork, the author, the group, ... Some of the specialised viewers provided such information for a specific artpack either by encoding it as part of the executable, or by having some sort of database or list. However every viewer did it their own way. This meant you either had to use the viewer included with the artpack, or had to make do without the extra info. SAUCE was created to address that need. So if you wanted to, you could use your preferred viewer to view the art in a certain artpack, or even store the art files you liked in a separate folder while retaining the meta data.
The goal was simple, but the way to get there certainly was not. Logistically, we wanted as many art groups as possible to support it. Technically, we wanted a system that was easy to implement and – if at all possible – manage to provide this meta data while still being compatible with all the existing software such as ANSI viewers, and Bulletin Board Software.
Example ¶
package main import ( "embed" "fmt" "log" "time" "github.com/bengarrett/sauce" ) //go:embed static/* var static embed.FS func main() { //nolint:funlen // open a file file, err := static.Open("static/sauce.txt") if err != nil { log.Fatal(err) } defer file.Close() // read the file and create a SAUCE record sr, err := sauce.Read(file) if err != nil { log.Println(err) return } if !sr.Valid() { fmt.Println("no sauce metadata found") return } // print specific SAUCE fields fmt.Printf("%q\n", sr.Title) fmt.Printf("Author,\t%s.\n", sr.Author) fmt.Printf("Group,\t%s.\n", sr.Group) fmt.Printf("Date,\t%s.\n", sr.Date.Time.Format(time.ANSIC)) // return the SAUCE data as indented JSON js, err := sr.JSONIndent(" ") if err != nil { log.Println(err) return } fmt.Printf("%s", js) }
Output: "Sauce title" Author, Sauce author. Group, Sauce group. Date, Sat Nov 26 00:00:00 2016. { "id": "SAUCE", "version": "00", "title": "Sauce title", "author": "Sauce author", "group": "Sauce group", "date": { "value": "20161126", "iso": "2016-11-26T00:00:00Z", "epoch": 1480118400 }, "filesize": { "bytes": 3741, "decimal": "3.7 kB", "binary": "3.7 KiB" }, "dataType": { "type": 1, "name": "text or character stream" }, "fileType": { "type": 0, "name": "ASCII text" }, "typeInfo": { "1": { "value": 977, "info": "character width" }, "2": { "value": 9, "info": "number of lines" }, "3": { "value": 0, "info": "" }, "flags": { "decimal": 19, "binary": "10011", "nonBlinkMode": { "flag": "1", "interpretation": "non-blink mode" }, "letterSpacing": { "flag": "00", "interpretation": "no preference" }, "aspectRatio": { "flag": "11", "interpretation": "invalid value" } }, "fontName": "IBM VGA" }, "comments": { "id": "COMNT", "count": 1, "lines": [ "Any comments go here. " ] } }
Index ¶
Examples ¶
Constants ¶
const ( ID = "SAUCE" Version = "00" // the version is always 00 )
SAUCE identifier and version.
const Date = "20060102"
Date format layout.
const EOF byte = 26
EOF is the end-of-file marker, otherwise known as SUB, the substitute character.
Variables ¶
This section is empty.
Functions ¶
func Contains ¶
Contains reports whether a valid SAUCE record is within b.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } fmt.Print(sauce.Contains(b))
Output: true
Example (None) ¶
package main import ( "fmt" "github.com/bengarrett/sauce" ) func main() { b := []byte("This string of text does not contain any SAUCE.") fmt.Print(sauce.Contains(b)) }
Output: false
func Index ¶
Index returns the index of the instance of the SAUCE ID in b, or -1 if it is not present in b.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } i := sauce.Index(b) fmt.Printf("SAUCE metadata position index: %v", i)
Output: SAUCE metadata position index: 1190
func Trim ¶
Trim returns b without any SAUCE metadata and the optional end-of-file marker.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } t := sauce.Trim(b) fmt.Printf("The original size of the file is %d bytes and contains sauce? %v\n", len(b), sauce.Contains(b)) fmt.Printf("The trimmed size of the file is %d bytes and contains sauce? %v\n", len(t), sauce.Contains(t))
Output: The original size of the file is 1318 bytes and contains sauce? true The trimmed size of the file is 1119 bytes and contains sauce? false
Example (Comnt) ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } // normalize line endings b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n")) // print the raw sauce binary data const index = 7 if x := bytes.Split(b, []byte("\n")); len(x) >= index+1 { fmt.Printf("\nSAUCE: %q\n\n", x[index]) } // print the trimmed text fmt.Printf("%s", string(sauce.Trim(b)))
Output: SAUCE: "\x1aCOMNTAny comments go here. SAUCE00Sauce title Sauce author Sauce group 20161126\x9d\x0e\x00\x00\x01\x00\xd1\x03\t\x00\x00\x00\x00\x00\x01\x13IBM VGA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" File: static/sauce.txt This must be opened and saved as a Windows 1252 document. SAUCE package test with complete data and comment. Cras sit amet purus urna. Phasellus in dapibus ex. Proin pretium eget leo ut gravida. Praesent egestas urna at tincidunt mollis. Vivamus nec urna lorem. Vestibulum molestie accumsan lectus, in egestas metus facilisis eget. Nam consectetur, metus et sodales aliquam, mi dui dapibus metus, non cursus libero felis ac nunc. Nulla euismod, turpis sed mollis faucibus, orci elit dapibus leo, vitae placerat diam eros sed velit. Fusce convallis, lorem ut vulputate suscipit, tortor risus rhoncus mauris, a mattis metus magna at lorem. Sed molestie velit ipsum, in vulputate metus consequat eget. Fusce quis dui lacinia, laoreet lectus et, luctus velit. Pellentesque ut nisi quis orci pulvinar placerat vel ac lorem. Maecenas finibus fermentum erat, a pulvinar augue dictum mattis. Aenean vulputate consectetur velit at dictum. Donec vehicula ante quis ante venenatis, eu ultrices lectus egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
Example (Nocomnt) ¶
b, err := static.ReadFile("static/sauce-nocomnt.txt") if err != nil { fmt.Print(err) } // normalize line endings b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n")) // print the raw sauce binary data const index = 7 if x := bytes.Split(b, []byte("\n")); len(x) >= index+1 { fmt.Printf("\nSAUCE: %q\n\n", x[index]) } // print the trimmed text fmt.Printf("%s", string(sauce.Trim(b)))
Output: SAUCE: "\x1aSAUCE00Sauce title Sauce author Sauce group 20161126\x9d\x0e\x00\x00\x01\x00\xd1\x03\t\x00\x00\x00\x00\x00\x01\x13IBM VGA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" File: static/sauce-nocomnt.txt This must be opened and saved as a Windows 1252 document. SAUCE package test with complete data and comment. Cras sit amet purus urna. Phasellus in dapibus ex. Proin pretium eget leo ut gravida. Praesent egestas urna at tincidunt mollis. Vivamus nec urna lorem. Vestibulum molestie accumsan lectus, in egestas metus facilisis eget. Nam consectetur, metus et sodales aliquam, mi dui dapibus metus, non cursus libero felis ac nunc. Nulla euismod, turpis sed mollis faucibus, orci elit dapibus leo, vitae placerat diam eros sed velit. Fusce convallis, lorem ut vulputate suscipit, tortor risus rhoncus mauris, a mattis metus magna at lorem. Sed molestie velit ipsum, in vulputate metus consequat eget. Fusce quis dui lacinia, laoreet lectus et, luctus velit. Pellentesque ut nisi quis orci pulvinar placerat vel ac lorem. Maecenas finibus fermentum erat, a pulvinar augue dictum mattis. Aenean vulputate consectetur velit at dictum. Donec vehicula ante quis ante venenatis, eu ultrices lectus egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
Types ¶
type Record ¶
type Record struct { ID string `json:"id" xml:"id,attr"` // SAUCE identification. Version string `json:"version" xml:"version,attr"` // version must equal "00". Title string `json:"title" xml:"title"` // title of the file. Author string `json:"author" xml:"author"` // author of the file. Group string `json:"group" xml:"group"` // author employer or membership. Date layout.Dates `json:"date" xml:"date"` // date of creation or release. FileSize layout.Sizes `json:"filesize" xml:"filesize"` // size of file in bytes without SAUCE. Data layout.Datas `json:"dataType" xml:"data_type"` // data type of file. File layout.Files `json:"fileType" xml:"file_type"` // file type of file. Info layout.Infos `json:"typeInfo" xml:"type_info"` // file type dependant information. Desc string `json:"-" xml:"-"` // description of the file. Comnt layout.Comment `json:"comments" xml:"comments"` // comment block or notes. }
Record is the SAUCE data structure that corresponds with the SAUCE Layout fields.
func Decode ¶
Decode the SAUCE data contained within b.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) fmt.Printf("%+v", sr)
Output: {ID:SAUCE Version:00 Title:Sauce title Author:Sauce author Group:Sauce group Date:{Value:20161126 Time:2016-11-26 00:00:00 +0000 UTC Epoch:1480118400} FileSize:{Bytes:3741 Decimal:3.7 kB Binary:3.7 KiB} Data:{Type:text or character stream Name:text or character stream} File:{Type:0 Name:ASCII text} Info:{Info1:{Value:977 Info:character width} Info2:{Value:9 Info:number of lines} Info3:{Value:0 Info:} Flags:{Decimal:19 Binary:10011 B:{Flag:non-blink mode Info:non-blink mode} LS:{Flag:no preference Info:no preference} AR:{Flag:invalid value Info:invalid value} Interpretations:} Font:IBM VGA} Desc:ASCII text file with no formatting codes or color codes. Comnt:{ID:COMNT Count:1 Index:1121 Comment:[Any comments go here. ]}}
Example (None) ¶
package main import ( "fmt" "github.com/bengarrett/sauce" ) func main() { b := []byte("This string of text does not contain any SAUCE.") sr := sauce.Decode(b) fmt.Printf("%+v", sr) }
Output: {ID: Version: Title: Author: Group: Date:{Value: Time:0001-01-01 00:00:00 +0000 UTC Epoch:0} FileSize:{Bytes:0 Decimal: Binary:} Data:{Type:undefined Name:} File:{Type:0 Name:} Info:{Info1:{Value:0 Info:} Info2:{Value:0 Info:} Info3:{Value:0 Info:} Flags:{Decimal:0 Binary: B:{Flag:invalid value Info:} LS:{Flag:invalid value Info:} AR:{Flag:invalid value Info:} Interpretations:} Font:} Desc: Comnt:{ID: Count:0 Index:-1 Comment:[]}}
func Read ¶ added in v1.2.1
Read and return the SAUCE record in r.
Example ¶
// open file file, err := static.Open("static/sauce.txt") if err != nil { log.Print(err) return } defer file.Close() // create a new sauce record sr, err := sauce.Read(file) if err != nil { log.Print(err) return } // encode and print the sauce record as JSON js, err := sr.JSON() if err != nil { log.Print(err) return } fmt.Print(string(js))
Output: {"id":"SAUCE","version":"00","title":"Sauce title","author":"Sauce author","group":"Sauce group","date":{"value":"20161126","iso":"2016-11-26T00:00:00Z","epoch":1480118400},"filesize":{"bytes":3741,"decimal":"3.7 kB","binary":"3.7 KiB"},"dataType":{"type":1,"name":"text or character stream"},"fileType":{"type":0,"name":"ASCII text"},"typeInfo":{"1":{"value":977,"info":"character width"},"2":{"value":9,"info":"number of lines"},"3":{"value":0,"info":""},"flags":{"decimal":19,"binary":"10011","nonBlinkMode":{"flag":"1","interpretation":"non-blink mode"},"letterSpacing":{"flag":"00","interpretation":"no preference"},"aspectRatio":{"flag":"11","interpretation":"invalid value"}},"fontName":"IBM VGA"},"comments":{"id":"COMNT","count":1,"lines":["Any comments go here. "]}}
Example (None) ¶
package main import ( "bytes" "fmt" "log" "github.com/bengarrett/sauce" ) func main() { b := []byte("This string of text does not contain any SAUCE.") // create a new sauce record sr, err := sauce.Read(bytes.NewReader(b)) if err != nil { fmt.Printf("error: %v", err) return } // encode and print the sauce record as JSON js, err := sr.JSON() if err != nil { log.Print(err) return } fmt.Print(string(js)) }
Output: {"id":"","version":"","title":"","author":"","group":"","date":{"value":"","iso":"0001-01-01T00:00:00Z","epoch":0},"filesize":{"bytes":0,"decimal":"","binary":""},"dataType":{"type":0,"name":""},"fileType":{"type":0,"name":""},"typeInfo":{"1":{"value":0,"info":""},"2":{"value":0,"info":""},"3":{"value":0,"info":""},"flags":{"decimal":0,"binary":"","nonBlinkMode":{"flag":"","interpretation":""},"letterSpacing":{"flag":"","interpretation":""},"aspectRatio":{"flag":"","interpretation":""}},"fontName":""},"comments":{"id":"","count":0,"lines":[]}}
func (*Record) JSON ¶
JSON returns the JSON encoding of the r SAUCE record.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) js, err := sr.JSON() if err != nil { fmt.Print(err) } fmt.Print(string(js))
Output: {"id":"SAUCE","version":"00","title":"Sauce title","author":"Sauce author","group":"Sauce group","date":{"value":"20161126","iso":"2016-11-26T00:00:00Z","epoch":1480118400},"filesize":{"bytes":3741,"decimal":"3.7 kB","binary":"3.7 KiB"},"dataType":{"type":1,"name":"text or character stream"},"fileType":{"type":0,"name":"ASCII text"},"typeInfo":{"1":{"value":977,"info":"character width"},"2":{"value":9,"info":"number of lines"},"3":{"value":0,"info":""},"flags":{"decimal":19,"binary":"10011","nonBlinkMode":{"flag":"1","interpretation":"non-blink mode"},"letterSpacing":{"flag":"00","interpretation":"no preference"},"aspectRatio":{"flag":"11","interpretation":"invalid value"}},"fontName":"IBM VGA"},"comments":{"id":"COMNT","count":1,"lines":["Any comments go here. "]}}
func (*Record) JSONIndent ¶
JSONIndent is like JSON but applies Indent to format the output. Each JSON element in the output will begin on a new line beginning with one or more copies of indent according to the indentation nesting.
Example ¶
{ //nolint:funlen b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) const indent = " " js, err := sr.JSONIndent(indent) if err != nil { fmt.Print(err) } fmt.Print(string(js))
Output: { "id": "SAUCE", "version": "00", "title": "Sauce title", "author": "Sauce author", "group": "Sauce group", "date": { "value": "20161126", "iso": "2016-11-26T00:00:00Z", "epoch": 1480118400 }, "filesize": { "bytes": 3741, "decimal": "3.7 kB", "binary": "3.7 KiB" }, "dataType": { "type": 1, "name": "text or character stream" }, "fileType": { "type": 0, "name": "ASCII text" }, "typeInfo": { "1": { "value": 977, "info": "character width" }, "2": { "value": 9, "info": "number of lines" }, "3": { "value": 0, "info": "" }, "flags": { "decimal": 19, "binary": "10011", "nonBlinkMode": { "flag": "1", "interpretation": "non-blink mode" }, "letterSpacing": { "flag": "00", "interpretation": "no preference" }, "aspectRatio": { "flag": "11", "interpretation": "invalid value" } }, "fontName": "IBM VGA" }, "comments": { "id": "COMNT", "count": 1, "lines": [ "Any comments go here. " ] } }
func (*Record) Valid ¶
Valid reports the completeness of the r SAUCE record.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) fmt.Print(sr.Valid())
Output: true
Example (Invalid) ¶
package main import ( "fmt" "github.com/bengarrett/sauce" ) func main() { b := []byte("This string of text does not contain any SAUCE.") sr := sauce.Decode(b) fmt.Print(sr.Valid()) }
Output: false
func (*Record) XML ¶
XML returns the XML encoding of the r SAUCE record.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) xm, err := sr.XML() if err != nil { fmt.Print(err) } fmt.Print(string(xm))
Output: <Record id="SAUCE" version="00"><title>Sauce title</title><author>Sauce author</author><group>Sauce group</group><date epoch="1480118400"><value>20161126</value><date>2016-11-26T00:00:00Z</date></date><filesize decimal="3.7 kB" binary="3.7 KiB"><bytes>3741</bytes></filesize><data_type><type>1</type><name>text or character stream</name></data_type><file_type><type>0</type><name>ASCII text</name></file_type><type_info><type1 type="character width"><value>977</value></type1><type2 type="number of lines"><value>9</value></type2><type3 type=""><value>0</value></type3><flags decimal="19" binary="10011"><non_blink_mode interpretation="non-blink mode"><flag>1</flag></non_blink_mode><letter_spacing interpretation="no preference"><flag>00</flag></letter_spacing><aspect_ratio interpretation="invalid value"><flag>11</flag></aspect_ratio></flags><fontname>IBM VGA</fontname></type_info><comments id="COMNT" count="1"><line>Any comments go here. </line></comments></Record>
func (*Record) XMLIndent ¶
XMLIndent is like XML but applies Indent to format the output. Each XML element in the output will begin on a new line beginning with one or more copies of indent according to the indentation nesting.
Example ¶
b, err := static.ReadFile("static/sauce.txt") if err != nil { fmt.Print(err) } sr := sauce.Decode(b) const indent = " " xm, err := sr.XMLIndent(indent) if err != nil { fmt.Print(err) } fmt.Print(string(xm))
Output: <Record id="SAUCE" version="00"> <title>Sauce title</title> <author>Sauce author</author> <group>Sauce group</group> <date epoch="1480118400"> <value>20161126</value> <date>2016-11-26T00:00:00Z</date> </date> <filesize decimal="3.7 kB" binary="3.7 KiB"> <bytes>3741</bytes> </filesize> <data_type> <type>1</type> <name>text or character stream</name> </data_type> <file_type> <type>0</type> <name>ASCII text</name> </file_type> <type_info> <type1 type="character width"> <value>977</value> </type1> <type2 type="number of lines"> <value>9</value> </type2> <type3 type=""> <value>0</value> </type3> <flags decimal="19" binary="10011"> <non_blink_mode interpretation="non-blink mode"> <flag>1</flag> </non_blink_mode> <letter_spacing interpretation="no preference"> <flag>00</flag> </letter_spacing> <aspect_ratio interpretation="invalid value"> <flag>11</flag> </aspect_ratio> </flags> <fontname>IBM VGA</fontname> </type_info> <comments id="COMNT" count="1"> <line>Any comments go here. </line> </comments> </Record>
Directories ¶
Path | Synopsis |
---|---|
Package humanize is parses some limited time and byte sizes data to human readable formats.
|
Package humanize is parses some limited time and byte sizes data to human readable formats. |
internal
|
|
layout
ANSiFlags allow an author of ANSi and similar files to provide a clue to a viewer / editor how to render the image.
|
ANSiFlags allow an author of ANSi and similar files to provide a clue to a viewer / editor how to render the image. |