all repos — searchix @ 1d518f42e04712c84dfc168cc7a286aabb56e2ed

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
131
132
133
134
package manpages

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"time"

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

const basename = "manpage-urls.json"

type URLMap struct {
	root *file.Root

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

type Options struct {
	Logger *log.Logger
	Root   *file.Root
}

func New(opts *Options) *URLMap {
	return &URLMap{
		logger: opts.Logger,
		root:   opts.Root,
	}
}

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", basename)

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

	data, err := m.root.ReadFile(basename)
	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", basename)

	f, err := m.root.Create(basename)
	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
}