User:Tokenzero/tinfoboxTemplateData.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Tokenzero/tinfoboxTemplateData. |
// <nowiki>
/**
* @module tinfoboxTemplateData
* Classes wrapping TemplateData.
* Usage: see User:Tokenzero/infoboxJournal.js for how to load a module.
* import { TemplateData, TemplateDataParam } from '/w/index.php?title=User:Tokenzero/tinfoboxTemplateData.js&action=raw&ctype=text%2Fjavascript';
* (async function() {
* templateData = await m.TemplateData.fetch('Template:Infobox journal');
* console.log(templateData.param('title').deprecated);
* wikicode = templateData.build({title: 'Foo'});
* console.log(wikicode);
* })();
* For documentation of TemplateData in general (not this wrapper module):
* - https://www.mediawiki.org/wiki/Extension:TemplateData
* - https://www.mediawiki.org/wiki/Help:TemplateData
* - example JSON: https://wiki.riteme.site/w/api.php?action=templatedata&titles=Template:Infobox%20journal&format=jsonfm&formatversion=2&lang=en
*/
/** Configuration of a template parameter, see {@link TemplateData}. */
export class TemplateDataParam {
/** @param {string} key */
constructor(key) {
/** @type {string} - The canonical name of the parameter. */
this.key = key;
/** @type {string} - A short human label like "Former name". */
this.label = '';
/** @type {string} */
this.description = '';
/**
* @type {string}
* One of: unknown/number/boolean/string (any text)/line (short label text)/
* date (in ISO format e.g. "2014-05-09" or "2014-05-09T16:01:12Z") /
* content (wikitext) / unbalanced-wikitext /
* wiki-page-name / wiki-file-name (without File:) /
* wiki-template-name / wiki-user-name (without User:)
*/
this.type = 'unknown';
/** @type {string} - Default value assumed if none is given. */
this.default = null;
/** @type {string} - Initially suggested value, often like '2019'. */
this.autovalue = null;
/** @type {string} */
this.example = null;
/** @type {boolean} */
this.required = false;
/** @type {boolean} */
this.suggested = false;
/**
* @type {boolean}
* Suggested, but not added as empty by default; situational.
* This is tinfobox-specific. At most one of suggested/weaklySuggested should be true.
*/
this.weaklySuggested = false;
/** @type {(boolean|string)} - May be an instruction of what to use in place of it. */
this.deprecated = false;
/** @type {Array<string>} - Other names for the parameter. */
this.aliases = [];
}
/**
* Serialize to simple json object, called by JSON.stringify.
*
* @returns {object}
*/
toJSON() {
return {
key: this.key,
label: this.label,
description: this.description,
default: this.default,
autovalue: this.autovalue,
example: this.example,
required: this.required,
suggested: this.suggested,
weaklySuggested: this.weaklySuggested,
deprecated: this.deprecated,
aliases: this.aliases
};
}
/**
* Deserialize from simple object returned by JSON.parse or MW API.
* We assume mediawiki API json formatversion=2 with the lang param set.
*
* @param {object} jsonObject
* @param {string} [key] - canonical key to identify the param, if not already in jsonObject.
* @returns {TemplateDataParam}
*/
static fromJSON(jsonObject, key) {
console.assert(!jsonObject.inherits); // Inherits should be handled by API.
const canonicalKey = jsonObject.key || key;
const result = Object.assign(new TemplateDataParam(canonicalKey), jsonObject);
result.aliases = result.aliases || [];
return result;
}
}
/**
* Configuration of a template.
* See https://www.mediawiki.org/wiki/Help:TemplateData#Description_and_parameters
* or https://www.mediawiki.org/wiki/Extension:TemplateData
*/
export class TemplateData {
/** @param {object} jsonObject - from mediawiki API json formatversion=2 with lang= set. */
constructor(jsonObject) {
/** @type {string} */
this.title = jsonObject.title; // Added by API.
/** @type {boolean} */
this.notemplatedata = jsonObject.notemplatedata; // Added by API.
/** @type {string} */
this.description = jsonObject.description;
/**
* @type {string}
* 'inline', 'block', or a string like '\n{{_\n|_______________ = _\n}}\n'.
*/
this.format = jsonObject.format;
/**
* @type {Object<string, Object<string,(string|Array<string>|Array<Array<string>>)>>}
* Maps names of consumers to maps from consumer-parameters to our-parameters.
*/
this.maps = jsonObject.maps;
/**
* @type {Array<{label: string, params: Array<string>}>}
* Sets (groups) of parameters. A parameter may be in multiple sets.
* Labels are short, 20-ish characters.
*/
this.sets = jsonObject.sets;
/** @type {Map<string, TemplateDataParam>} */
this.params = new Map();
for (const [k, v] of Object.entries(jsonObject.params))
this.params.set(k, TemplateDataParam.fromJSON(v, k));
/** @type {!Array<string>} */
this.paramOrder = jsonObject.paramOrder;
// Should always be filled by API but apparently it's not.
if (!this.paramOrder || !this.paramOrder.length)
this.paramOrder = Object.keys(jsonObject.params);
/** @private {Map<string, string>} - map from alias key to canonical key. */
this.canonicalMap_ = new Map();
for (const [canonicalKey, param] of this.params.entries()) {
for (const aliasKey of param.aliases) {
console.assert(!this.canonicalMap_.get(aliasKey));
this.canonicalMap_.set(aliasKey, canonicalKey);
}
}
}
/**
* Serialize to simple json object, called by JSON.stringify.
*
* @returns {object}
*/
toJSON() {
// Convert Map to Object (avoid importing polyfills just for three lines).
const jsonParams = {};
for (const [key, value] of this.params.entries())
jsonParams[key] = value;
return {
title: this.title,
notemplatedata: this.notemplatedata,
description: this.description,
format: this.format,
maps: this.maps,
sets: this.sets,
params: jsonParams,
paramOrder: this.paramOrder
};
}
/**
* Deserialize from simple object returned by JSON.parse or MW API.
* We assume mediawiki API json formatversion=2 with the lang param set.
*
* @param {object} jsonObject
* @returns {TemplateData}
*/
static fromJSON(jsonObject) {
return new TemplateData(jsonObject);
}
/**
* Fetch given template's TemplateData via API.
*
* @param {string} name
* @returns {Promise<TemplateData>}
*/
static async fetch(name) {
if (!name.startsWith('Template'))
name = 'Template:' + name;
const r = await (new mw.Api()).get({
action: 'templatedata',
titles: name,
redirects: true,
lang: mw.config.get('wgUserLanguage'),
formatversion: 2
});
return new TemplateData(Object.values(r.pages)[0]);
}
/**
* Map an alias key to the canonical parameter name.
*
* @param {string|number} key
* @returns {string}
*/
toCanonicalKey(key) {
return this.canonicalMap_.get(key.toString().trim()) || key.toString().trim();
}
/**
* Give TemplateDataParam for given canonical key or return default.
*
* @param {string} canonicalKey
* @returns {TemplateDataParam}
*/
param(canonicalKey) {
// Don't save default param, because we check and report when a param has no TemplateData.
// Freeze to avoid mistaken attempts to make a new param starting from default values.
if (!this.params.has(canonicalKey))
return Object.freeze(new TemplateDataParam(canonicalKey));
return this.params.get(canonicalKey);
}
/** Reorder the keys of a Map according to this.paramOrder.
* Keys not in this.paramOrder are then appended lexicographically.
*
* @param {Map<string, T>} map
* @returns {Map<string, T>}
* @template T
*/
reorder(map) {
const result = new Map();
// First add keys in paramOrder.
for (const key of this.paramOrder) {
if (map.has(key))
result.set(key, map.get(key));
}
// Then take remaining keys, sort, and add.
const toBeSorted = [];
for (const [key, value] of map.entries()) {
if (!this.paramOrder.includes(key))
toBeSorted.push([key, value]);
}
for (const [key, value] of toBeSorted.sort())
result.set(key, value);
return result;
}
/**
* Format given key-value map as wikicode of the template.
*
* @param {Map<string, [(number|string), string]>} map - from canonicalKey to final key, value.
* @param {string=} templateName - alias template name to use (defaults to this.title).
* @param {boolean=} doReorder - whether to apply this.reorder(map); defaults to true.
* @returns {string} wikicode, currently from '{{' to '}}' inclusive, no final endline.
*/
build(map, templateName, doReorder) {
// TODO Use this.format; make it easy to handle pre and post newlines.
// As in https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/TemplateWizard/+/master/resources/ext.TemplateWizard.TemplateFormatter.js
if (doReorder == null)
doReorder = true;
if (doReorder)
map = this.reorder(map);
if (!templateName)
templateName = this.title;
let result = '{{' + templateName.trim() + '\n';
for (let [_canonicalKey, [key, value]] of map.entries()) {
if (typeof key === 'string') {
key = key.trim();
} else if (typeof key === 'number') {
key = key.toString();
} else {
console.log(`Error: unexpected key type "${typeof key}"`);
continue;
}
if (typeof value === 'string')
value = value.trim();
else if (!value)
value = '';
result += '| ' + key.padEnd(14) + '= ' + value + '\n';
}
result += '}}';
return result;
}
}
// </nowiki>