all repos — searchix @ 2f2d86922ba23c4b7f92b115f4e8d26e5058bd23

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
package manpages

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

	"gitlab.com/tozd/go/errors"
	"go.alanpearce.eu/searchix/internal/config"
	"go.alanpearce.eu/searchix/internal/fetcher/http"
	"go.alanpearce.eu/x/log"
)

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,
) errors.E {
	if !source.Manpages.Enable {
		return errors.New("manpages not enabled for this source")
	}

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

	url, err := makeManpageURL(source)
	if err != nil {
		return errors.WithMessage(err, "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 errors.WithMessage(err, "failed to fetch manpages")
	}
	defer r.Close()

	if err := m.save(r); err != nil {
		return errors.WithMessage(err, "failed to save manpages")
	}

	m.mtime = mtime

	return nil
}

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

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

	data, err := os.ReadFile(m.path)
	if err != nil {
		return errors.WithMessage(err, "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 errors.WithMessage(err, "failed to parse manpages JSON")
	}

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

	return nil
}

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

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

	if _, err := io.Copy(f, r); err != nil {
		return errors.WithMessage(err, "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, errors.E) {
	url, err := source.Repo.GetRawFileURL(source.Manpages.Path)
	if err != nil {
		return "", errors.WithMessage(err, "failed to join manpage URL")
	}

	return url, nil
}