package gmi import ( "bufio" "errors" "fmt" "io" "net/url" "path" "path/filepath" "regexp" "strings" ) func hasImgExt(p string) bool { switch path.Ext(strings.ToLower(p)) { case ".jpg", ".jpeg", ".png", ".gif", ".svg": return true default: return false } } // matches `=> dstURL [optional description]` var linkRegexp = regexp.MustCompile(`^=>\s+(\S+)\s*(.*?)\s*$`) // GemtextToMarkdown reads a gemtext formatted body from the Reader and writes // the markdown version of that body to the Writer. // // gmiGateway, if given, is used for all `gemini://` links. The `gemini://` // prefix will be stripped, and replaced with the given URL. func GemtextToMarkdown(dst io.Writer, src io.Reader, gmiGateway *url.URL) error { bufSrc := bufio.NewReader(src) for { line, err := bufSrc.ReadString('\n') if err != nil && !errors.Is(err, io.EOF) { return fmt.Errorf("reading: %w", err) } last := err == io.EOF if match := linkRegexp.FindStringSubmatch(line); len(match) > 0 { u, err := url.Parse(match[1]) if err != nil { return fmt.Errorf("link to invalid url %q: %w", match[1], err) } if u.Scheme == "gemini" && gmiGateway != nil { newU := *gmiGateway newU.Path = filepath.Join(newU.Path, u.Host, u.Path) newU.RawQuery = u.RawQuery u = &newU } isImg := hasImgExt(u.Path) descr := match[2] if descr != "" { // ok } else if isImg { descr = "Image" } else { descr = "Link" } line = fmt.Sprintf("[%s](%s)\n", descr, u.String()) if isImg { line = "!" + line } } if _, err := dst.Write([]byte(line)); err != nil { return fmt.Errorf("writing: %w", err) } if last { return nil } } }