User:Tokenzero/tinfoboxTemplateData.js

// /** * @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://en.wikipedia.org/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 } - 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'. */       this.format = jsonObject.format; /**        * @type {Object)>>} * Maps names of consumers to maps from consumer-parameters to our-parameters. */       this.maps = jsonObject.maps; /**        * @type {Array<{label: string, params: Array }>} * Sets (groups) of parameters. A parameter may be in multiple sets. * Labels are short, 20-ish characters. */       this.sets = jsonObject.sets; /** @type {Map} */ this.params = new Map; for (const [k, v] of Object.entries(jsonObject.params)) this.params.set(k, TemplateDataParam.fromJSON(v, k)); /** @type {!Array } */ 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} - 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} */   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} map * @returns {Map} * @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} 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 '' 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 = ''; return result; } } //