diff --git a/data/doveadm.js b/data/doveadm.js index 79d41d14f..b6b715a0a 100644 --- a/data/doveadm.js +++ b/data/doveadm.js @@ -3,7 +3,7 @@ import { doveadm_arg_types, doveadm_args_human_timestamp, doveadm_args_query, doveadm_args_usermask, - doveadm_flag_types } from '../lib/doveadm.js' + doveadm_flag_types } from '../lib/constants/doveadm.js' export const doveadm = { diff --git a/data/settings.js b/data/settings.js index 16e75ab45..cbad03c2d 100644 --- a/data/settings.js +++ b/data/settings.js @@ -1,6 +1,6 @@ /* Dovecot settings. */ -import { setting_types } from '../lib/settings.js' +import { setting_types } from '../lib/constants/settings.js' export const settings = { diff --git a/lib/constants/doveadm.js b/lib/constants/doveadm.js new file mode 100644 index 000000000..6decfcc41 --- /dev/null +++ b/lib/constants/doveadm.js @@ -0,0 +1,40 @@ +/* List of Doveadm argument value types. */ +export const doveadm_arg_types = { + ARRAY: 1, + BOOL: 2, + INTEGER: 3, + STRING: 4, + SEARCH_QUERY: 5, // Search query is an ARG_ARRAY + ISTREAM: 6, +} + +/* List of Doveadm flag value types. */ +export const doveadm_flag_types = { + NONE: 0, + USER: 1, + USERFILE: 2, + USERMASK: 4, +} + +export const doveadm_args_query = { + example: ['mailbox', 'INBOX/myfoldertoo', 'savedbefore', 'since', '30d'], + positional: true, + type: doveadm_arg_types.SEARCH_QUERY, + text: `Search query to apply.`, +} + +export const doveadm_args_usermask = { + example: 'username', + positional: true, + type: doveadm_arg_types.STRING, + text: `User Mask.` +} + +export const doveadm_args_human_timestamp = ` +Allowable formats: + +* yyyy-mm-dd (non-UTC), +* IMAP date ("dd-mm-yyyy"; see [[rfc,3501]]) (non-UTC) +* IMAP date-time ("dd-mm-yyy HH:MM:SS +0000; see [[rfc,3501]]) (UTC supported) +* Unix timestamp (UTC supported) +* Interval ("n days", UTC supported)` diff --git a/lib/constants/settings.js b/lib/constants/settings.js new file mode 100644 index 000000000..27a5ff65c --- /dev/null +++ b/lib/constants/settings.js @@ -0,0 +1,93 @@ +/* List of Dovecot settings value types. */ +export const setting_types = { + BOOLEAN: { + label: 'Boolean', + url: '[[link,settings_types_boolean]]', + default_required: true, + }, + IPADDR: { + label: 'IP Address(es)', + url: '[[link,settings_types_ip]]', + // Default: empty + }, + SIZE: { + label: 'Size', + url: '[[link,settings_types_size]]', + default_required: true, + }, + STRING: { + label: 'String', + url: '[[link,settings_types_string]]' + // Default: empty + }, + STRING_NOVAR: { + label: 'String Without Variables', + url: '[[link,settings_types_string_novar]]' + // Default: empty + }, + /* This is a String entry, with specific allowable values. */ + ENUM: { + label: 'String', + url: '[[link,settings_types_string]]', + enum_required: true, + }, + TIME: { + label: 'Time', + url: '[[link,settings_types_time]]', + default_required: true, + }, + TIME_MSECS: { + label: 'Time (milliseconds)', + url: '[[link,settings_types_time_msecs]]', + default_required: true, + }, + UINT: { + label: 'Unsigned Integer', + url: '[[link,settings_types_uint]]', + default_required: true, + }, + OCTAL_UINT: { + label: 'Octal Unsigned Integer', + url: '[[link,settings_types_octal_uint]]', + default_required: true, + }, + URL: { + label: 'URL', + url: '[[link,settings_types_url]]' + // Default: empty + }, + FILE: { + label: 'File', + url: '[[link,settings_types_file]]' + // Default: empty + }, + NAMED_FILTER: { + label: 'Named Filter', + url: '[[link,settings_types_named_filter]]', + no_default: true, + }, + NAMED_LIST_FILTER: { + label: 'Named List Filter', + url: '[[link,settings_types_named_list_filter]]' + // Default: empty + }, + STRLIST: { + label: 'String List', + url: '[[link,settings_types_strlist]]' + // Default: empty + }, + BOOLLIST: { + label: 'Boolean List', + url: '[[link,settings_types_boollist]]' + // Default: empty + }, + IN_PORT: { + label: 'Port Number', + url: '[[link,settings_types_in_port]]', + default_required: true, + }, + GROUP: { + label: 'Settings Group', + url: '[[link,settings_groups_includes]]' + } +} diff --git a/lib/data/doveadm.data.js b/lib/data/doveadm.data.js index 63fd222af..326414dcf 100644 --- a/lib/data/doveadm.data.js +++ b/lib/data/doveadm.data.js @@ -1,160 +1,8 @@ -import { doveadm_arg_types, doveadm_flag_types, getDoveadmCmdLine } from '../doveadm.js' -import { getVitepressMd } from '../markdown.js' -import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js' -import camelCase from 'camelcase' -import slugify from '@sindresorhus/slugify' - -const doveadm_userargs = { - 'all-users': { - cli: 'A', - example: false, - type: doveadm_arg_types.BOOL, - text: `Apply operation to all users.` - }, - 'socket-path': { - cli: 'S', - example: '/var/run/dovecot/doveadm-server', - type: doveadm_arg_types.STRING, - text: `Path to doveadm socket.` - }, - user: { - cli: 'u', - example: 'username', - type: doveadm_arg_types.STRING, - text: `UID of user to apply operation to.`, - }, -} - -const doveadm_userfileargs = { - /* Hidden from documentation. - 'trans-flags': { - type: doveadm_arg_types.INTEGER, - text: `Transaction flags.` - }, - */ - 'user-file': { - cli: 'F', - type: doveadm_arg_types.STRING, - text: `A filename. If set, fetch usernames from file. One username per line.` - }, -} - -function typeToString(type) { - switch (type) { - case doveadm_arg_types.ARRAY: - return 'array' - case doveadm_arg_types.BOOL: - return 'boolean' - case doveadm_arg_types.INTEGER: - return 'integer' - case doveadm_arg_types.STRING: - return 'string' - case doveadm_arg_types.SEARCH_QUERY: - return 'search_query' - case doveadm_arg_types.ISTREAM: - return 'istream' - } -} - -function argToHttpParam(arg) { - return arg.split('-').reduce((s, c) => - s + (c.charAt(0).toUpperCase() + c.slice(1))) -} - -async function normalizeDoveadm(doveadm) { - const md = await getVitepressMd() - - for (const [k, v] of Object.entries(doveadm)) { - if (!v) { - delete doveadm[k] - continue - } - - if (v.flags && (v.flags & doveadm_flag_types.USER)) { - v.args = { ...v.args, ...doveadm_userargs } - } - - if (v.flags && (v.flags & doveadm_flag_types.USERFILE)) { - v.args = { ...v.args, ...doveadm_userfileargs } - } - - /* Convert 'man' entry into a markdown link to man page. - * We will add the hash to all URLs for simplicity; for those man - * pages that don't have individual command names, this will just - * be ignored by the browser. */ - if (v.man) { - v.man_link = md.renderInline('[[man,' + v.man + ',' + slugify(k) + ']]') - } - - /* Change entries. */ - for (const k2 of ['added', 'changed', 'deprecated', 'removed']) { - if (v[k2]) { - const changes = [] - for (const[k3, v3] of Object.entries(v[k2])) { - changes.push({ - text: v3 ? md.render(v3.trim()) : null, - version: md.renderInline('[[' + k2 + ',' + k3 + ']]') - }) - } - v[k2] = changes - } - } - - /* Response values. */ - if (v.response) { - if (v.response.text) { - v.response.text = md.render(String(v.response.text)) - } - } else { - delete v['response'] - } - - /* Text Description. */ - if (v.text) { - v.text = md.render(v.text) - } - - /* Cmd line arguments. */ - v.usage = k + (v.args ? ' ' + getDoveadmCmdLine(v.args) : '') - - if (v.args) { - const args = [] - for (const [k2, v2] of Object.entries(v.args)) { - if (!v2.hidden) { - args.push({ - /* Undefined examples will resolve to undefined. */ - cli_only: v2.cli_only, - example: v2.example, - flag: v2.cli ? '-' + v2.cli : (v2.positional ? k2 : '--' + k2), - param: argToHttpParam(k2), - type: typeToString(v2.type), - text: v2.text ? md.render(v2.text) : null - }) - } - } - v.args = args - } - if (!v.args || !v.args.length) { - delete v['args'] - } - - /* HTTP API info. */ - v.http_cmd = camelCase(k) - } - - return { - doveadm: normalizeArrayData( - doveadm, - ['tags'] - ), - http_api_link: md.renderInline('[[link,doveadm_http_api,HTTP API]]') - } -} +import { addWatchPaths } from '../utility.js' +import { loadDoveadm } from '../doveadm.js' export default addWatchPaths({ async load() { - return await normalizeDoveadm( - structuredClone(loadData('doveadm').doveadm) - ) + return await loadDoveadm() } }) diff --git a/lib/data/event_categories.data.js b/lib/data/event_categories.data.js index 7a72fdbb3..c543f006f 100644 --- a/lib/data/event_categories.data.js +++ b/lib/data/event_categories.data.js @@ -1,20 +1,8 @@ -import { getVitepressMd } from '../markdown.js' -import { addWatchPaths, loadData } from '../utility.js' - -async function normalizeEventCategories(categories) { - const md = await getVitepressMd() - - for (const [k, v] of Object.entries(categories)) { - v.description = md.renderInline(v.description) - } - - return categories -} +import { loadEventCategories } from '../event_categories.js' +import { addWatchPaths } from '../utility.js' export default addWatchPaths({ async load() { - return await normalizeEventCategories( - structuredClone(loadData('event_categories').categories) - ) + return await loadEventCategories() } }) diff --git a/lib/data/event_reasons.data.js b/lib/data/event_reasons.data.js index 15f8f2abf..2ab1c327b 100644 --- a/lib/data/event_reasons.data.js +++ b/lib/data/event_reasons.data.js @@ -1,20 +1,8 @@ -import { getVitepressMd } from '../markdown.js' -import { addWatchPaths, loadData } from '../utility.js' - -async function normalizeEventReasons(reasons) { - const md = await getVitepressMd() - - for (const [k, v] of Object.entries(reasons)) { - v.description = md.renderInline(v.description) - } - - return reasons -} +import { addWatchPaths } from '../utility.js' +import { loadEventReasons } from '../event_reasons.js' export default addWatchPaths({ async load() { - return await normalizeEventReasons( - structuredClone(loadData('event_reasons').reasons) - ) + return await loadEventReasons() } }) diff --git a/lib/data/lua.data.js b/lib/data/lua.data.js index e42f2ee86..24b7f2ef0 100644 --- a/lib/data/lua.data.js +++ b/lib/data/lua.data.js @@ -1,95 +1,8 @@ -import { getVitepressMd } from '../markdown.js' -import { addWatchPaths, loadData } from '../utility.js' - -async function normalizeLuaConstants(lua) { - const md = await getVitepressMd() - const out = {} - - for (const v of lua.values()) { - if (v.text) { - v.text = md.render(v.text) - } - - for (const tag of v.tags) { - const v2 = structuredClone(v) - v2.tags = tag - out[tag + '.' + v.name] = v2 - } - } - - return out -} - -async function normalizeLuaFunctions(lua) { - const md = await getVitepressMd() - let set = false - const out = {} - - for (const v of lua.values()) { - if (v.args) { - for (const [k2, v2] of Object.entries(v.args)) { - /* Merge information from Dovecot settings. */ - if (v2.dovecot_setting) { - if (!set) { - set = structuredClone(loadData('settings').settings) - } - - if (!v2.type) { - v2.type = set[v2.dovecot_setting].values?.label - } - - if (!v2.text) { - v2.text = set[v2.dovecot_setting].text.trim() - } - - if (v2.default === undefined) { - v2.default = set[v2.dovecot_setting].default - } - } - - v2.text = md.render(v2.text) - } - } - - v.text = md.render(v.text) - - for (const tag of v.tags) { - const v2 = structuredClone(v) - v2.tags = tag - out[tag + '.' + v.name] = v2 - } - } - - return out -} - -async function normalizeLuaVariables(lua) { - const md = await getVitepressMd() - const out = {} - - for (const v of lua.values()) { - if (v.text) { - v.text = md.render(v.text) - } - - for (const tag of v.tags) { - const v2 = structuredClone(v) - v2.tags = tag - out[tag + '.' + v.name] = v2 - } - } - - return out -} +import { addWatchPaths } from '../utility.js' +import { loadLua } from '../lua.js' export default addWatchPaths({ async load() { - const data = loadData('lua') - - return { - constants: await normalizeLuaConstants(data.lua_constants), - functions: await normalizeLuaFunctions(data.lua_functions), - variables: await normalizeLuaVariables(data.lua_variables) - } + return await loadLua() } }) diff --git a/lib/data/settings.data.js b/lib/data/settings.data.js index a3c67aa44..ad7adf2a9 100644 --- a/lib/data/settings.data.js +++ b/lib/data/settings.data.js @@ -1,137 +1,8 @@ -import { addWatchPaths, loadData, normalizeArrayData } from '../utility.js' -import { getVitepressMd } from '../markdown.js' - -function wrapInTag(str, tag) { - if (tag) - return `<${tag}>${str}` - return str -} - -/* Resolve links in given parameter. If no singular link is detected, it is - * rendered with the provided tag surrounding the value. */ -function normalizeString(md, str, tag = null) { - let out = '' - - if (str) { - /* FIXME: This makes the following .startsWith() call work, but might - * lead to type-specific errors, e.g. String({}) yields - * '[object Object]'. This still needs to be verified manually. */ - out = String(str) - if (!out.startsWith('[[')) { - out = wrapInTag(out, tag) - } - return md.renderInline(out) - } - - return str -} - -/* Mark a plain item as an inter-settings dovecot-specific link, i.e. - * [[setting,]]. Don't process already marked links. */ -function normalizeArray(md, arr) { - if (arr) { - return arr.map(entry => ( - md.renderInline( - entry.startsWith('[[') - ? entry - : `[[setting,${entry}]]` - ) - )) - } - - return arr -} - -async function normalizeSettings(settings) { - const data = normalizeArrayData( - settings, - ['dependencies', 'seealso', 'tags', 'values_enum'] - ) - - const md = await getVitepressMd() - - for (const [k, v] of Object.entries(data)) { - if (!v) { - delete data[k] - continue - } - - /* Style default entry. */ - if (!!v.default) { - if (['string', 'number'].includes(typeof v.default) || - v.default instanceof String) - v.default = normalizeString(md, v.default, 'code') - else { - let out = normalizeString(md, v.default.value ?? '', 'code') - if (out.length > 0) - out += '
' - if (!!v.default.text) - out += `${normalizeString(md, v.default.text ?? '')}` - v.default = out - } - } - - /* Add list of dependencies. */ - v.dependencies = normalizeArray(md, v.dependencies) - - /* Add markdown to seealso settings. */ - v.seealso = normalizeArray(md, v.seealso) - - /* Plugin. */ - if (v.plugin) { - v.plugin = [ v.plugin ].flat() - v.plugin_link = v.plugin.map((x) => - md.renderInline('[[plugin,' + x + ']]') - ).join(', ') - } - - /* There can be multiple value entries. */ - if (!Array.isArray(v.values)) { - v.values = [ v.values ] - } - - for (const v2 of v.values) { - if (!v2) { - throw new Error("Incorrect value type for " + k) - } - - if (v2.default_required && (v.default === undefined)) { - throw new Error("Default value missing for " + k) - } - if (v2.enum_required && !v.values_enum) { - throw new Error("Enum array missing for " + k) - } - - v2.url = md.renderInline(v2.url) - - if (v2.no_default) { - v.no_default = true - } - } - - for (const k2 of ['added', 'changed', 'deprecated', 'removed']) { - if (v[k2]) { - const changes = [] - for (const[k3, v3] of Object.entries(v[k2])) { - changes.push({ - text: v3 ? md.render(v3.trim()) : null, - version: md.renderInline('[[' + k2 + ',' + k3 + ']]') - }) - } - v[k2] = changes - } - } - - v.text = md.render(v.text.trim()) - } - - return data -} +import { addWatchPaths } from '../utility.js' +import { loadSettings } from '../settings.js' export default addWatchPaths({ async load() { - return await normalizeSettings( - structuredClone(loadData('settings').settings) - ) + return await loadSettings() } }) diff --git a/lib/doveadm.js b/lib/doveadm.js index fadac3b96..554b1540f 100644 --- a/lib/doveadm.js +++ b/lib/doveadm.js @@ -1,43 +1,8 @@ -/* List of Doveadm argument value types. */ -export const doveadm_arg_types = { - ARRAY: 1, - BOOL: 2, - INTEGER: 3, - STRING: 4, - SEARCH_QUERY: 5, // Search query is an ARG_ARRAY - ISTREAM: 6, -} - -/* List of Doveadm flag value types. */ -export const doveadm_flag_types = { - NONE: 0, - USER: 1, - USERFILE: 2, - USERMASK: 4, -} - -export const doveadm_args_query = { - example: ['mailbox', 'INBOX/myfoldertoo', 'savedbefore', 'since', '30d'], - positional: true, - type: doveadm_arg_types.SEARCH_QUERY, - text: `Search query to apply.`, -} - -export const doveadm_args_usermask = { - example: 'username', - positional: true, - type: doveadm_arg_types.STRING, - text: `User Mask.` -} - -export const doveadm_args_human_timestamp = ` -Allowable formats: - -* yyyy-mm-dd (non-UTC), -* IMAP date ("dd-mm-yyyy"; see [[rfc,3501]]) (non-UTC) -* IMAP date-time ("dd-mm-yyy HH:MM:SS +0000; see [[rfc,3501]]) (UTC supported) -* Unix timestamp (UTC supported) -* Interval ("n days", UTC supported)` +import { doveadm_arg_types, doveadm_flag_types } from './constants/doveadm.js' +import { getVitepressMd } from './markdown.js' +import { loadData, normalizeArrayData } from './utility.js' +import camelCase from 'camelcase' +import slugify from '@sindresorhus/slugify' /* Generate command line string for doveadm. */ export function getDoveadmCmdLine(args) { @@ -68,3 +33,157 @@ export function getDoveadmCmdLine(args) { return ret.trim() } + +const doveadm_userargs = { + 'all-users': { + cli: 'A', + example: false, + type: doveadm_arg_types.BOOL, + text: `Apply operation to all users.` + }, + 'socket-path': { + cli: 'S', + example: '/var/run/dovecot/doveadm-server', + type: doveadm_arg_types.STRING, + text: `Path to doveadm socket.` + }, + user: { + cli: 'u', + example: 'username', + type: doveadm_arg_types.STRING, + text: `UID of user to apply operation to.`, + }, +} + +const doveadm_userfileargs = { + /* Hidden from documentation. + 'trans-flags': { + type: doveadm_arg_types.INTEGER, + text: `Transaction flags.` + }, + */ + 'user-file': { + cli: 'F', + type: doveadm_arg_types.STRING, + text: `A filename. If set, fetch usernames from file. One username per line.` + }, +} + +function typeToString(type) { + switch (type) { + case doveadm_arg_types.ARRAY: + return 'array' + case doveadm_arg_types.BOOL: + return 'boolean' + case doveadm_arg_types.INTEGER: + return 'integer' + case doveadm_arg_types.STRING: + return 'string' + case doveadm_arg_types.SEARCH_QUERY: + return 'search_query' + case doveadm_arg_types.ISTREAM: + return 'istream' + } +} + +function argToHttpParam(arg) { + return arg.split('-').reduce((s, c) => + s + (c.charAt(0).toUpperCase() + c.slice(1))) +} + +async function normalizeDoveadm(doveadm) { + const md = await getVitepressMd() + + for (const [k, v] of Object.entries(doveadm)) { + if (!v) { + delete doveadm[k] + continue + } + + if (v.flags && (v.flags & doveadm_flag_types.USER)) { + v.args = { ...v.args, ...doveadm_userargs } + } + + if (v.flags && (v.flags & doveadm_flag_types.USERFILE)) { + v.args = { ...v.args, ...doveadm_userfileargs } + } + + /* Convert 'man' entry into a markdown link to man page. + * We will add the hash to all URLs for simplicity; for those + * man pages that don't have individual command names, this + * will just be ignored by the browser. */ + if (v.man) { + v.man_link = md.renderInline('[[man,' + v.man + ',' + slugify(k) + ']]') + } + + /* Change entries. */ + for (const k2 of ['added', 'changed', 'deprecated', 'removed']) { + if (v[k2]) { + const changes = [] + for (const[k3, v3] of Object.entries(v[k2])) { + changes.push({ + text: v3 ? md.render(v3.trim()) : null, + version: md.renderInline('[[' + k2 + ',' + k3 + ']]') + }) + } + v[k2] = changes + } + } + + /* Response values. */ + if (v.response) { + if (v.response.text) { + v.response.text = md.render(String(v.response.text)) + } + } else { + delete v['response'] + } + + /* Text Description. */ + if (v.text) { + v.text = md.render(v.text) + } + + /* Cmd line arguments. */ + v.usage = k + (v.args ? ' ' + getDoveadmCmdLine(v.args) : '') + + if (v.args) { + const args = [] + for (const [k2, v2] of Object.entries(v.args)) { + if (!v2.hidden) { + args.push({ + cli_only: v2.cli_only, + /* Undefined examples will + * resolve to undefined. */ + example: v2.example, + flag: v2.cli ? '-' + v2.cli : (v2.positional ? k2 : '--' + k2), + param: argToHttpParam(k2), + type: typeToString(v2.type), + text: v2.text ? md.render(v2.text) : null + }) + } + } + v.args = args + } + if (!v.args || !v.args.length) { + delete v['args'] + } + + /* HTTP API info. */ + v.http_cmd = camelCase(k) + } + + return { + doveadm: normalizeArrayData( + doveadm, + ['tags'] + ), + http_api_link: md.renderInline('[[link,doveadm_http_api,HTTP API]]') + } +} + +export async function loadDoveadm() { + return await normalizeDoveadm( + structuredClone(loadData('doveadm').doveadm) + ) +} diff --git a/lib/event_categories.js b/lib/event_categories.js new file mode 100644 index 000000000..596a271cc --- /dev/null +++ b/lib/event_categories.js @@ -0,0 +1,18 @@ +import { getVitepressMd } from './markdown.js' +import { loadData } from './utility.js' + +async function normalizeEventCategories(categories) { + const md = await getVitepressMd() + + for (const [k, v] of Object.entries(categories)) { + v.description = md.renderInline(v.description) + } + + return categories +} + +export async function loadEventCategories() { + return await normalizeEventCategories( + structuredClone(loadData('event_categories').categories) + ) +} diff --git a/lib/event_reasons.js b/lib/event_reasons.js new file mode 100644 index 000000000..73651458e --- /dev/null +++ b/lib/event_reasons.js @@ -0,0 +1,18 @@ +import { getVitepressMd } from './markdown.js' +import { loadData } from './utility.js' + +async function normalizeEventReasons(reasons) { + const md = await getVitepressMd() + + for (const [k, v] of Object.entries(reasons)) { + v.description = md.renderInline(v.description) + } + + return reasons +} + +export async function loadEventReasons() { + return await normalizeEventReasons( + structuredClone(loadData('event_reasons').reasons) + ) +} diff --git a/lib/lua.js b/lib/lua.js new file mode 100644 index 000000000..bc5e93345 --- /dev/null +++ b/lib/lua.js @@ -0,0 +1,93 @@ +import { getVitepressMd } from './markdown.js' +import { loadData } from './utility.js' + +async function normalizeLuaConstants(lua) { + const md = await getVitepressMd() + const out = {} + + for (const v of lua.values()) { + if (v.text) { + v.text = md.render(v.text) + } + + for (const tag of v.tags) { + const v2 = structuredClone(v) + v2.tags = tag + out[tag + '.' + v.name] = v2 + } + } + + return out +} + +async function normalizeLuaFunctions(lua) { + const md = await getVitepressMd() + let set = false + const out = {} + + for (const v of lua.values()) { + if (v.args) { + for (const [k2, v2] of Object.entries(v.args)) { + /* Merge information from Dovecot settings. */ + if (v2.dovecot_setting) { + if (!set) { + set = structuredClone(loadData('settings').settings) + } + + if (!v2.type) { + v2.type = set[v2.dovecot_setting].values?.label + } + + if (!v2.text) { + v2.text = set[v2.dovecot_setting].text.trim() + } + + if (v2.default === undefined) { + v2.default = set[v2.dovecot_setting].default + } + } + + v2.text = md.render(v2.text) + } + } + + v.text = md.render(v.text) + + for (const tag of v.tags) { + const v2 = structuredClone(v) + v2.tags = tag + out[tag + '.' + v.name] = v2 + } + } + + return out +} + +async function normalizeLuaVariables(lua) { + const md = await getVitepressMd() + const out = {} + + for (const v of lua.values()) { + if (v.text) { + v.text = md.render(v.text) + } + + for (const tag of v.tags) { + const v2 = structuredClone(v) + v2.tags = tag + out[tag + '.' + v.name] = v2 + } + } + + return out +} + +export async function loadLua() { + const data = loadData('lua') + + return { + constants: await normalizeLuaConstants(data.lua_constants), + functions: await normalizeLuaFunctions(data.lua_functions), + variables: await normalizeLuaVariables(data.lua_variables) + } +} diff --git a/lib/settings.js b/lib/settings.js index 27a5ff65c..8b9156960 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -1,93 +1,137 @@ -/* List of Dovecot settings value types. */ -export const setting_types = { - BOOLEAN: { - label: 'Boolean', - url: '[[link,settings_types_boolean]]', - default_required: true, - }, - IPADDR: { - label: 'IP Address(es)', - url: '[[link,settings_types_ip]]', - // Default: empty - }, - SIZE: { - label: 'Size', - url: '[[link,settings_types_size]]', - default_required: true, - }, - STRING: { - label: 'String', - url: '[[link,settings_types_string]]' - // Default: empty - }, - STRING_NOVAR: { - label: 'String Without Variables', - url: '[[link,settings_types_string_novar]]' - // Default: empty - }, - /* This is a String entry, with specific allowable values. */ - ENUM: { - label: 'String', - url: '[[link,settings_types_string]]', - enum_required: true, - }, - TIME: { - label: 'Time', - url: '[[link,settings_types_time]]', - default_required: true, - }, - TIME_MSECS: { - label: 'Time (milliseconds)', - url: '[[link,settings_types_time_msecs]]', - default_required: true, - }, - UINT: { - label: 'Unsigned Integer', - url: '[[link,settings_types_uint]]', - default_required: true, - }, - OCTAL_UINT: { - label: 'Octal Unsigned Integer', - url: '[[link,settings_types_octal_uint]]', - default_required: true, - }, - URL: { - label: 'URL', - url: '[[link,settings_types_url]]' - // Default: empty - }, - FILE: { - label: 'File', - url: '[[link,settings_types_file]]' - // Default: empty - }, - NAMED_FILTER: { - label: 'Named Filter', - url: '[[link,settings_types_named_filter]]', - no_default: true, - }, - NAMED_LIST_FILTER: { - label: 'Named List Filter', - url: '[[link,settings_types_named_list_filter]]' - // Default: empty - }, - STRLIST: { - label: 'String List', - url: '[[link,settings_types_strlist]]' - // Default: empty - }, - BOOLLIST: { - label: 'Boolean List', - url: '[[link,settings_types_boollist]]' - // Default: empty - }, - IN_PORT: { - label: 'Port Number', - url: '[[link,settings_types_in_port]]', - default_required: true, - }, - GROUP: { - label: 'Settings Group', - url: '[[link,settings_groups_includes]]' +import { getVitepressMd } from './markdown.js' +import { loadData, normalizeArrayData } from './utility.js' +import { setting_types } from './constants/settings.js' + +function wrapInTag(str, tag) { + if (tag) + return `<${tag}>${str}` + return str +} + +/* Resolve links in given parameter. If no singular link is detected, it is + * rendered with the provided tag surrounding the value. */ +function normalizeString(md, str, tag = null) { + let out = '' + + if (str) { + /* FIXME: This makes the following .startsWith() call work, + * but might lead to type-specific errors, e.g. String({}) + * yields '[object Object]'. This still needs to be verified + * manually. */ + out = String(str) + if (!out.startsWith('[[')) { + out = wrapInTag(out, tag) + } + return md.renderInline(out) + } + + return str +} + +/* Mark a plain item as an inter-settings dovecot-specific link, i.e. + * [[setting,]]. Don't process already marked links. */ +function normalizeArray(md, arr) { + if (arr) { + return arr.map(entry => ( + md.renderInline( + entry.startsWith('[[') + ? entry + : `[[setting,${entry}]]` + ) + )) } + + return arr +} + +async function normalizeSettings(settings) { + const data = normalizeArrayData( + settings, + ['dependencies', 'seealso', 'tags', 'values_enum'] + ) + + const md = await getVitepressMd() + + for (const [k, v] of Object.entries(data)) { + if (!v) { + delete data[k] + continue + } + + /* Style default entry. */ + if (!!v.default) { + if (['string', 'number'].includes(typeof v.default) || + v.default instanceof String) + v.default = normalizeString(md, v.default, 'code') + else { + let out = normalizeString(md, v.default.value ?? '', 'code') + if (out.length > 0) + out += '
' + if (!!v.default.text) + out += `${normalizeString(md, v.default.text ?? '')}` + v.default = out + } + } + + /* Add list of dependencies. */ + v.dependencies = normalizeArray(md, v.dependencies) + + /* Add markdown to seealso settings. */ + v.seealso = normalizeArray(md, v.seealso) + + /* Plugin. */ + if (v.plugin) { + v.plugin = [ v.plugin ].flat() + v.plugin_link = v.plugin.map((x) => + md.renderInline('[[plugin,' + x + ']]') + ).join(', ') + } + + /* There can be multiple value entries. */ + if (!Array.isArray(v.values)) { + v.values = [ v.values ] + } + + for (const v2 of v.values) { + if (!v2) { + throw new Error("Incorrect value type for " + k) + } + + if (v2.default_required && (v.default === undefined)) { + throw new Error("Default value missing for " + k) + } + if (v2.enum_required && !v.values_enum) { + throw new Error("Enum array missing for " + k) + } + + v2.url = md.renderInline(v2.url) + + if (v2.no_default) { + v.no_default = true + } + } + + for (const k2 of ['added', 'changed', 'deprecated', 'removed']) { + if (v[k2]) { + const changes = [] + for (const[k3, v3] of Object.entries(v[k2])) { + changes.push({ + text: v3 ? md.render(v3.trim()) : null, + version: md.renderInline('[[' + k2 + ',' + k3 + ']]') + }) + } + v[k2] = changes + } + } + + v.text = md.render(v.text.trim()) + } + + return data +} + +export async function loadSettings() { + return await normalizeSettings( + structuredClone(loadData('settings').settings) + ) }