all repos — searchix @ 3dfbd8dd7212f74622ba4892efb34bf3487da09b

Search engine for NixOS, nix-darwin, home-manager and NUR users

internal/manpages/manpages.go (view 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package manpages

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"

	"alin.ovh/searchix/internal/config"
	"alin.ovh/searchix/internal/fetcher/http"
	"alin.ovh/x/log"
	"github.com/Southclaws/fault"
	"github.com/Southclaws/fault/fmsg"
)

const basename = "manpage-urls.json"

type URLMap struct {
	path string

	mtime  time.Time
	logger *log.Logger
	urlMap map[string]string
}

func New(cfg *config.Config, logger *log.Logger) *URLMap {
	return &URLMap{
		path:   filepath.Join(cfg.DataPath, basename),
		logger: logger,
	}
}

func (m *URLMap) Update(
	ctx context.Context,
	source *config.Source,
) error {
	if !source.Manpages.Enable {
		return fault.New("manpages not enabled for this source")
	}

	if source.Manpages.Path == "" {
		return fault.New("manpages repo source path not configured")
	}

	url, err := makeManpageURL(source)
	if err != nil {
		return fault.Wrap(err, fmsg.With("failed to join manpages URL"))
	}

	m.logger.Debug("fetching manpages URL map", "url", url)
	r, mtime, err := http.FetchFileIfNeeded(ctx, m.logger.Named("http"), m.mtime, url)
	if err != nil {
		return fault.Wrap(err, fmsg.With("failed to fetch manpages"))
	}
	defer r.Close()

	if err := m.save(r); err != nil {
		return fault.Wrap(err, fmsg.With("failed to save manpages"))
	}

	m.mtime = mtime

	return nil
}

// Open loads the manpage URLs from the JSON file
func (m *URLMap) Open() error {
	m.logger.Debug("opening manpages file", "path", m.path)

	stat, err := os.Stat(m.path)
	if err != nil {
		return fault.Wrap(err, fmsg.Withf("failed to stat manpages file: %s", m.path))
	}

	data, err := os.ReadFile(m.path)
	if err != nil {
		return fault.Wrap(err, fmsg.With("failed to read manpages file"))
	}

	m.mtime = stat.ModTime()

	m.urlMap = make(map[string]string)
	if err := json.Unmarshal(data, &m.urlMap); err != nil {
		return fault.Wrap(err, fmsg.With("failed to parse manpages JSON"))
	}

	m.logger.Debug("loaded manpages data", "urls", len(m.urlMap))

	return nil
}

func (m *URLMap) save(r io.Reader) error {
	m.logger.Debug("saving manpages file", "path", m.path)

	f, err := os.Create(m.path)
	if err != nil {
		return fault.Wrap(err, fmsg.With("failed to create manpages file"))
	}
	defer f.Close()

	if _, err := io.Copy(f, r); err != nil {
		return fault.Wrap(err, fmsg.With("failed to write manpages file"))
	}

	return nil
}

func (m *URLMap) Get(section string, page string) (string, bool) {
	key := fmt.Sprintf("%s(%s)", page, section)

	m.logger.Debug("getting manpage URL", "key", key)
	url, ok := m.urlMap[key]
	if !ok {
		return "", false
	}

	return url, true
}

func makeManpageURL(source *config.Source) (string, error) {
	url, err := source.Repo.GetRawFileURL(source.Manpages.Path)
	if err != nil {
		return "", fault.Wrap(err, fmsg.With("failed to join manpage URL"))
	}

	return url, nil
}