diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-09-03 19:58:18 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-09-03 19:58:18 -0600 |
commit | 9a68591255747c82fd4ce99351bca6d43349cafa (patch) | |
tree | 06928eeb617b954a80bbdab2e6164952d47e13bf /gopher/gophermap/extended.go | |
parent | 5befdc9c851f285000c15abc01a08010c719b307 (diff) |
implement gophernicus extensions for gophermaps
Diffstat (limited to 'gopher/gophermap/extended.go')
-rw-r--r-- | gopher/gophermap/extended.go | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/gopher/gophermap/extended.go b/gopher/gophermap/extended.go new file mode 100644 index 0000000..d9fedd0 --- /dev/null +++ b/gopher/gophermap/extended.go @@ -0,0 +1,220 @@ +package gophermap + +import ( + "bufio" + "errors" + "io" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + "tildegit.org/tjp/sliderule/gopher" + "tildegit.org/tjp/sliderule/internal/types" +) + +// ExtendedMapDocument is a gophermap doc with gophernicus's extensions +// +// These are documented at: gopher://gopher.gophernicus.org/0/docs/README.Gophermap +type ExtendedMapDocument struct { + Lines []gopher.MapItem + Location *url.URL +} + +// ParseExtended parses a gophermap document including gophernicus extensions. +func ParseExtended(input io.Reader, location *url.URL) (ExtendedMapDocument, error) { + rdr := bufio.NewReader(input) + doc := ExtendedMapDocument{Location: location} + +outer: + for num := 1; ; num += 1 { + line, err := rdr.ReadString('\n') + isEOF := errors.Is(err, io.EOF) + if err != nil && !isEOF { + return doc, err + } + line = strings.TrimRight(line, "\r\n") + + if len(line) > 0 { + switch line[0] { + + case '#': + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: CommentType, + Display: strings.TrimPrefix(line[1:], " "), + }) + continue outer + case '!': + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: TitleType, + Display: line[1:], + }) + continue outer + case '-': + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: HiddenType, + Selector: line[1:], + }) + continue outer + case ':': + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: ExtensionType, + Display: line[1:], + }) + continue outer + case '=': + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: InclusionType, + Selector: line[1:], + }) + continue outer + } + } + + switch line { + case "~": + doc.Lines = append(doc.Lines, gopher.MapItem{Type: UserListType}) + continue outer + case "%": + doc.Lines = append(doc.Lines, gopher.MapItem{Type: VHostListType}) + continue outer + case ".": + doc.Lines = append(doc.Lines, gopher.MapItem{Type: EndDocType}) + break outer + case "*": + doc.Lines = append(doc.Lines, gopher.MapItem{Type: DirListType}) + break outer + } + + if !strings.Contains(line, "\t") { + doc.Lines = append(doc.Lines, gopher.MapItem{ + Type: gopher.InfoMessageType, + Display: line, + Selector: location.Path, + Hostname: location.Hostname(), + Port: location.Port(), + }) + continue + } + + item := gopher.MapItem{Type: types.Status(line[0])} + + spl := strings.Split(line[1:], "\t") + if len(spl) != 4 { + return doc, InvalidLine(num) + } + item.Display = string(spl[0]) + item.Selector = string(spl[1]) + item.Hostname = string(spl[2]) + item.Port = string(spl[3]) + if _, err = strconv.Atoi(item.Port); err != nil { + return doc, InvalidLine(num) + } + doc.Lines = append(doc.Lines, item) + + if isEOF { + break + } + } + + return doc, nil +} + +// Extensions to gopher types from Gophernicus. +const ( + // CommentType is omitted from generated compatible gophermaps. + CommentType types.Status = '#' + + // TitleType defines the title of a gophermap document. + TitleType types.Status = '!' + + // HiddenType hides a link from the generated compatible gophermap. + HiddenType types.Status = '-' + + // ExtensionType defines the gopher type to use for files in the current directory with a given extension. + ExtensionType types.Status = ':' + + // UserListType generates a list of users with valid ~/public_gopher directories. + UserListType types.Status = '~' + + // VHostListType generates a listing of virtual hosts. + VHostListType types.Status = '%' + + // InclusionType causes another gophermap to be included at this location. + InclusionType types.Status = '=' + + // DirListType stops parsing the current file and ends the generated gophermap with a listing of the current directory. + DirListType types.Status = '*' + + // EndDocType ends the current gophermap file. + EndDocType types.Status = '.' +) + +// Compatible builds a standards-compliant gophermap from the current extended menu. +func (edoc ExtendedMapDocument) Compatible(cwd string) (gopher.MapDocument, string, error) { + doc := gopher.MapDocument{} + + title := "" + hidden := map[string]struct{}{} + extensions := map[string]types.Status{} + + for num, item := range edoc.Lines { + switch item.Type { + case CommentType: + case TitleType: + title = item.Display + case HiddenType: + hidden[item.Selector] = struct{}{} + case ExtensionType: + from, to, found := strings.Cut(item.Display, "=") + if !found { + return nil, "", InvalidLine(num) + } + extensions[from] = types.Status(to[0]) + case UserListType: //TODO + return nil, "", errors.New("User listings '~' are not supported") + case VHostListType: //TODO + return nil, "", errors.New("Virtual host listings '%' are not supported") + case InclusionType: + location := filepath.Join(cwd, item.Selector) + subEdoc, err := openExtended(location, edoc.Location) + if err != nil { + return nil, "", err + } + + lines, _, err := subEdoc.Compatible(filepath.Dir(location)) + if err != nil { + return nil, "", err + } + doc = append(doc, lines...) + case DirListType: + dirlist, err := listDir(cwd, edoc.Location, hidden, extensions) + if err != nil { + return nil, "", err + } + + doc = append(doc, dirlist...) + + break + case EndDocType: + break + default: + doc = append(doc, item) + } + } + + return doc, title, nil +} + +func openExtended(path string, location *url.URL) (ExtendedMapDocument, error) { + file, err := os.Open(path) + if err != nil { + return ExtendedMapDocument{}, err + } + defer func() { + _ = file.Close() + }() + + return ParseExtended(file, location) +} |