...

Source file src/code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml/convert.go

Documentation: code.rocketnine.space/tslocum/gmitohtml/pkg/gmitohtml

     1  package gmitohtml
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"html"
     9  	"net/url"
    10  	"path"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  // ErrInvalidURL is the error returned when the URL is invalid.
    16  var ErrInvalidURL = errors.New("invalid URL")
    17  
    18  var daemonAddress string
    19  
    20  var assetLock sync.Mutex
    21  
    22  var imageExtensions = []string{"png", "jpg", "jpeg", "gif", "svg", "webp"}
    23  
    24  func rewriteURL(u string, loc *url.URL) string {
    25  	if daemonAddress == "" {
    26  		return u
    27  	}
    28  
    29  	if loc.Path == "" {
    30  		loc.Path = "/"
    31  	}
    32  
    33  	scheme := "gemini"
    34  	if strings.HasPrefix(loc.Path, "/file/") {
    35  		scheme = "file"
    36  	}
    37  
    38  	if strings.HasPrefix(u, "file://") {
    39  		if !allowFileAccess {
    40  			return "http://" + daemonAddress + "/?FileAccessNotAllowed"
    41  		}
    42  		return "http://" + daemonAddress + "/file/" + u[7:]
    43  	}
    44  
    45  	offset := 0
    46  	if strings.HasPrefix(u, "gemini://") {
    47  		offset = 9
    48  	}
    49  	firstSlash := strings.IndexRune(u[offset:], '/')
    50  	if firstSlash != -1 {
    51  		u = strings.ToLower(u[:firstSlash+offset]) + u[firstSlash+offset:]
    52  	}
    53  
    54  	if strings.HasPrefix(u, "gemini://") {
    55  		return "http://" + daemonAddress + "/gemini/" + u[9:]
    56  	} else if strings.Contains(u, "://") {
    57  		return u
    58  	} else if loc != nil && len(u) > 0 && !strings.HasPrefix(u, "//") {
    59  		if u[0] != '/' {
    60  			if loc.Path[len(loc.Path)-1] == '/' {
    61  				u = path.Join("/", loc.Path, u)
    62  			} else {
    63  				u = path.Join("/", path.Dir(loc.Path), u)
    64  			}
    65  		}
    66  		return "http://" + daemonAddress + "/" + scheme + "/" + strings.ToLower(loc.Host) + u
    67  	}
    68  	return "http://" + daemonAddress + "/" + scheme + "/" + u
    69  }
    70  
    71  func newPage() []byte {
    72  	data := []byte(pageHeader)
    73  	if daemonAddress != "" {
    74  		data = append(data, navHeader...)
    75  	}
    76  	return append(data, contentHeader...)
    77  }
    78  
    79  // Convert converts text/gemini to text/html.
    80  func Convert(page []byte, u string) []byte {
    81  	var result []byte
    82  
    83  	var preformatted bool
    84  
    85  	parsedURL, err := url.Parse(u)
    86  	if err != nil {
    87  		parsedURL = nil
    88  		err = nil
    89  	}
    90  
    91  	scanner := bufio.NewScanner(bytes.NewReader(page))
    92  	for scanner.Scan() {
    93  		line := scanner.Bytes()
    94  		l := len(line)
    95  		if l >= 3 && string(line[0:3]) == "```" {
    96  			preformatted = !preformatted
    97  			if preformatted {
    98  				result = append(result, []byte("<pre>\n")...)
    99  			} else {
   100  				result = append(result, []byte("</pre>\n")...)
   101  			}
   102  			continue
   103  		}
   104  
   105  		if preformatted {
   106  			result = append(result, html.EscapeString(string(line))...)
   107  			result = append(result, []byte("\n")...)
   108  			continue
   109  		}
   110  
   111  		if l >= 6 && bytes.HasPrefix(line, []byte("=>")) {
   112  			splitStart := 2
   113  			if line[splitStart] == ' ' || line[splitStart] == '\t' {
   114  				splitStart++
   115  			}
   116  
   117  			var split [][]byte
   118  			firstSpace := bytes.IndexRune(line[splitStart:], ' ')
   119  			firstTab := bytes.IndexRune(line[splitStart:], '\t')
   120  			if firstSpace != -1 && (firstTab == -1 || firstSpace < firstTab) {
   121  				split = bytes.SplitN(line[splitStart:], []byte(" "), 2)
   122  			} else if firstTab != -1 {
   123  				split = bytes.SplitN(line[splitStart:], []byte("\t"), 2)
   124  			}
   125  
   126  			var linkURL []byte
   127  			var linkLabel []byte
   128  			if len(split) == 2 {
   129  				linkURL = split[0]
   130  				linkLabel = split[1]
   131  			} else {
   132  				linkURL = line[splitStart:]
   133  				linkLabel = line[splitStart:]
   134  			}
   135  
   136  			parts := strings.Split(string(linkURL), ".")
   137  			extension := parts[len(parts)-1]
   138  			isImage := false
   139  			for _, ext := range imageExtensions {
   140  				if extension == ext {
   141  					isImage = true
   142  				}
   143  			}
   144  
   145  			uri := html.EscapeString(rewriteURL(string(linkURL), parsedURL))
   146  			title := html.EscapeString(string(linkLabel))
   147  
   148  			// If link ends with gif/png/jpg, add a image instead of a link
   149  			if isImage && Config.ConvertImages {
   150  				result = append(result, []byte("<img src=\""+uri+"\" alt=\""+title+"\">")...)
   151  			} else {
   152  				result = append(result, []byte("<a href=\""+uri+"\">"+title+"</a><br>")...)
   153  			}
   154  
   155  			continue
   156  		}
   157  
   158  		heading := 0
   159  		for i := 0; i < l; i++ {
   160  			if line[i] == '#' {
   161  				heading++
   162  			} else {
   163  				break
   164  			}
   165  		}
   166  		if heading > 0 {
   167  			result = append(result, []byte(fmt.Sprintf("<h%d>%s</h%d>", heading, html.EscapeString(string(line[heading:])), heading))...)
   168  			continue
   169  		}
   170  
   171  		result = append(result, html.EscapeString(string(line))...)
   172  		result = append(result, []byte("<br>")...)
   173  	}
   174  
   175  	if preformatted {
   176  		result = append(result, []byte("</pre>\n")...)
   177  	}
   178  
   179  	data := newPage()
   180  	data = append(data, result...)
   181  	data = append(data, []byte(pageFooter)...)
   182  	return fillTemplateVariables(data, u, false)
   183  }
   184  

View as plain text