โ– humdrum codex / glint v1.0.2
license AGPL-3.0
2.7 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package export

import (
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
)

// OutputPath is where an export of srcPath lands: always the OS temp dir, never
// beside the source. A named file keeps its basename (extension swapped for
// .html); an unnamed buffer (empty srcPath) is named from a slug of the title.
// Exports are transient artifacts for the Print โ†’ Save as PDF flow, so keeping
// them out of the source tree stops a vault from filling up with stray HTML.
func OutputPath(srcPath, title string) string {
	var name string
	if srcPath != "" {
		base := filepath.Base(srcPath)
		name = strings.TrimSuffix(base, filepath.Ext(base))
	} else {
		name = slug(title)
	}
	if name == "" {
		name = "document"
	}
	return filepath.Join(os.TempDir(), name+".html")
}

// Write renders markdown to a self-contained HTML document and writes it to
// the export path for srcPath, returning that path.
func Write(srcPath, markdown string, opts Options) (string, error) {
	doc, err := Document(markdown, opts)
	if err != nil {
		return "", err
	}
	out := OutputPath(srcPath, opts.Title)
	if err := os.WriteFile(out, []byte(doc), 0o644); err != nil {
		return "", err
	}
	return out, nil
}

var atxH1RE = regexp.MustCompile(`(?m)^#\s+(.+?)\s*#*\s*$`)

// Title is the document title for an export: the first `# H1` in the markdown,
// else the source filename without extension, else "Untitled".
func Title(srcPath, markdown string) string {
	if m := atxH1RE.FindStringSubmatch(stripFrontmatter(markdown)); m != nil {
		if t := strings.TrimSpace(m[1]); t != "" {
			return t
		}
	}
	if srcPath != "" {
		base := filepath.Base(srcPath)
		return strings.TrimSuffix(base, filepath.Ext(base))
	}
	return "Untitled"
}

// OpenInBrowser opens path in the user's default browser, best-effort. It
// returns any error starting the program; the browser then runs detached.
func OpenInBrowser(path string) error {
	name, args := browserCommand(path)
	return exec.Command(name, args...).Start()
}

// browserCommand returns the platform program (and args) that opens path in
// the user's default browser.
func browserCommand(path string) (string, []string) {
	switch runtime.GOOS {
	case "darwin":
		return "open", []string{path}
	case "windows":
		return "cmd", []string{"/c", "start", "", path}
	default: // linux, *bsd
		return "xdg-open", []string{path}
	}
}

// slug turns a title into a filename-safe lowercase token.
func slug(title string) string {
	var b strings.Builder
	prevDash := false
	for _, r := range strings.ToLower(title) {
		switch {
		case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
			b.WriteRune(r)
			prevDash = false
		default:
			if !prevDash && b.Len() > 0 {
				b.WriteByte('-')
				prevDash = true
			}
		}
	}
	return strings.Trim(b.String(), "-")
}