« get me outta code hell

urls.js « util « src - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/util/urls.js
blob: e15c018b5404b47a79ceadebc5108d0f9d113d4d (plain)
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
// Code that deals with URLs (really the pathnames that get referenced all
// throughout the gener8ted HTML). Most nota8ly here is generateURLs, which
// is in charge of pre-gener8ting a complete network of template strings
// which can really quickly take su8stitute parameters to link from any one
// place to another; 8ut there are also a few other utilities, too.
//
// Nota8ly, everything here is string-8ased, for gener8ting and transforming
// actual path strings. More a8stract operations using wiki data o8jects is
// the domain of link.js.

import * as path from 'path';
import { withEntries } from './sugar.js';

export function generateURLs(urlSpec) {
    const getValueForFullKey = (obj, fullKey, prop = null) => {
        const [ groupKey, subKey ] = fullKey.split('.');
        if (!groupKey || !subKey) {
            throw new Error(`Expected group key and subkey (got ${fullKey})`);
        }

        if (!obj.hasOwnProperty(groupKey)) {
            throw new Error(`Expected valid group key (got ${groupKey})`);
        }

        const group = obj[groupKey];

        if (!group.hasOwnProperty(subKey)) {
            throw new Error(`Expected valid subkey (got ${subKey} for group ${groupKey})`);
        }

        return {
            value: group[subKey],
            group
        };
    };

    // This should be called on values which are going to be passed to
    // path.relative, because relative will resolve a leading slash as the root
    // directory of the working device, which we aren't looking for here.
    const trimLeadingSlash = P => P.startsWith('/') ? P.slice(1) : P;

    const generateTo = (fromPath, fromGroup) => {
        const A = trimLeadingSlash(fromPath);

        const rebasePrefix = '../'.repeat((fromGroup.prefix || '').split('/').filter(Boolean).length);

        const pathHelper = (toPath, toGroup) => {
            let B = trimLeadingSlash(toPath);

            let argIndex = 0;
            B = B.replaceAll('<>', () => `<${argIndex++}>`);

            if (toGroup.prefix !== fromGroup.prefix) {
                // TODO: Handle differing domains in prefixes.
                B = rebasePrefix + (toGroup.prefix || '') + B;
            }

            const suffix = (toPath.endsWith('/') ? '/' : '');

            return {
                posix: path.posix.relative(A, B) + suffix,
                device: path.relative(A, B) + suffix
            };
        };

        const groupSymbol = Symbol();

        const groupHelper = urlGroup => ({
            [groupSymbol]: urlGroup,
            ...withEntries(urlGroup.paths, entries => entries
                .map(([key, path]) => [key, pathHelper(path, urlGroup)]))
        });

        const relative = withEntries(urlSpec, entries => entries
            .map(([key, urlGroup]) => [key, groupHelper(urlGroup)]));

        const toHelper = (delimiterMode) => (key, ...args) => {
            const {
                value: {[delimiterMode]: template}
            } = getValueForFullKey(relative, key);

            let missing = 0;
            let result = template.replaceAll(/<([0-9]+)>/g, (match, n) => {
                if (n < args.length) {
                    return args[n];
                } else {
                    missing++;
                }
            });

            if (missing) {
                throw new Error(`Expected ${missing + args.length} arguments, got ${args.length} (key ${key}, args [${args}])`);
            }

            return result;
        };

        return {
            to: toHelper('posix'),
            toDevice: toHelper('device')
        };
    };

    const generateFrom = () => {
        const map = withEntries(urlSpec, entries => entries
            .map(([key, group]) => [key, withEntries(group.paths, entries => entries
                .map(([key, path]) => [key, generateTo(path, group)])
            )]));

        const from = key => getValueForFullKey(map, key).value;

        return {from, map};
    };

    return generateFrom();
}

const thumbnailHelper = name => file =>
    file.replace(/\.(jpg|png)$/, name + '.jpg');

export const thumb = {
    medium: thumbnailHelper('.medium'),
    small: thumbnailHelper('.small')
};