Open robb-j opened 2 months ago
Another alternative could be to inferred from the JSON schema instead, maybe there could be Configuration.schema
which is the key to store a JSON schema on a value to get automatic documentation?
This would be more standards-inline and different JSON schema features could be implemented as needed.
An example of storing meta information in the spec:
/**
* What if the meta information was stored in the JSON schema under $gruber ?
*
* @param {Record<string,unknown>} schema
* @returns {import("./configuration.js").ConfigurationDescription | null}
*/
function _specify(schema, path = []) {
if (typeof schema !== "object") return null;
// Object
if (schema.type === "object") {
const fallback = {};
const fields = [];
for (const [field, subSchema] of Object.entries(schema.properties)) {
const subSpec = _specify(
subSchema,
path.length ? path.concat("." + field) : [field],
);
if (!subSpec) continue;
fallback[field] = subSpec.fallback;
fields.push(...subSpec.fields);
}
return { fallback, fields };
}
// Array
if (schema.type === "array") {
const subSpec = _specify(schema.items, path.concat("[]"));
return {
fallback: [],
fields: subSpec ? subSpec.fields : [],
};
}
// URL
if (schema.type === "string" && schema.format === "uri") {
return {
fallback: schema.default,
fields: [_degruber(path.join(""), "url", schema)],
};
}
// string
if (schema.type === "string" && schema.format !== "uri") {
return {
fallback: schema.default,
fields: [_degruber(path.join(""), "string", schema)],
};
}
// number
if (schema.type === "number") {
return {
fallback: schema.default,
fields: [_degruber(path.join(""), "number", schema)],
};
}
// boolean
if (schema.type === "boolean") {
return {
fallback: schema.default,
fields: [_degruber(path.join(""), "boolean", schema)],
};
}
return null;
}
function _degruber(name, type, schema) {
return {
name,
type,
...(schema.$gruber ?? {}),
fallback: schema.default?.toString(),
};
}
console.log(
_specify({
type: "object",
properties: {
name: {
type: "string",
default: "Geoff Testington",
$gruber: { flag: "--name", variable: "FULL_NAME" },
},
age: {
type: "number",
default: 42,
$gruber: { flag: "--age", variable: "AGE" },
},
website: {
type: "string",
format: "uri",
default: "https://example.com",
$gruber: { flag: "--website", variable: "WEBSITE_URL" },
},
pets: {
type: "array",
default: [],
items: {
type: "string",
},
},
},
}),
);
outputs:
{
fallback: {
name: 'Geoff Testington',
age: 42,
website: 'https://example.com',
pets: []
},
fields: [
{
name: 'name',
type: 'string',
flag: '--name',
variable: 'FULL_NAME',
fallback: 'Geoff Testington'
},
{
name: 'age',
type: 'number',
flag: '--age',
variable: 'AGE',
fallback: '42'
},
{
name: 'website',
type: 'url',
flag: '--website',
variable: 'WEBSITE_URL',
fallback: 'https://example.com'
},
{ name: 'pets[]', type: 'string', fallback: undefined }
]
}
My initial thought is to store a single function at
Configuration.describe
, this is more like the other JavaScript protocols/interfaces and the interface is only a method now