From 714164165575eb9a42d7bbb240e2e126a19d3d50 Mon Sep 17 00:00:00 2001 From: techxplorer Date: Mon, 23 Sep 2024 22:33:57 +0930 Subject: [PATCH] Add a class to replace tags Intended to replace lower case tags with their equivalent camel case version. If the specified tag replacement is null, remove the tag from the list --- src/lib/tag-replacer.js | 113 ++++++++++++++++ tests/artefacts/tag-replacer/tag-mapping.yml | 5 + tests/lib/tag-replacer.test.js | 134 +++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 src/lib/tag-replacer.js create mode 100644 tests/artefacts/tag-replacer/tag-mapping.yml create mode 100644 tests/lib/tag-replacer.test.js diff --git a/src/lib/tag-replacer.js b/src/lib/tag-replacer.js new file mode 100644 index 0000000..be7ef3c --- /dev/null +++ b/src/lib/tag-replacer.js @@ -0,0 +1,113 @@ +/** + * @file The defintition of the TagReplacer class. + */ + +import { lstatSync, readFileSync } from "node:fs"; + +import { parse } from "yaml"; + +/** + * Replace a list of known tags with alternates. + */ +class TagReplacer { + + /** + * The path to the YAML file that contains the tag mapping list. + * @type {string} + */ + yamlFilePath = null; + + /** + * The tag map list. + * @type {object} + */ + tagMappings = null; + + + /** + * A class to automate the replacement of known tags with alternatives. + * @param {string} yamlFilePath The path to a YAML file with the mapping list. + */ + constructor( yamlFilePath ) { + + let syncStatus = null; + + try { + syncStatus = lstatSync( yamlFilePath ); + // eslint-disable-next-line no-unused-vars + } catch ( err ) { + throw new TypeError( "YAML file not found" ); + + } + + if ( !syncStatus.isFile() ) { + throw new TypeError( "Path must be to a file" ); + } + + this.yamlFilePath = yamlFilePath; + + } + + /** + * Load the tag mapping list from the YAML file. + */ + loadMappingList() { + + if ( this.tagMappings !== null ) { + return; + } + + const yamlContent = readFileSync( this.yamlFilePath, "utf8" ); + + this.tagMappings = parse( yamlContent ); + + } + + /** + * Count the number of keys in the mapping list. + * @returns {number} The number of keys in the mapping list. + */ + getMappingCount() { + + this.loadMappingList(); + + return Object.keys( this.tagMappings ).length; + } + + /** + * Find matching tags and replace them with new tags from the mapping list. + * @param {Array} originalTags An array containing a list of tags. + * @returns {Array} A list of tags with matching ones replaced. + */ + replaceTags( originalTags ) { + + if ( !Array.isArray( originalTags ) ) { + throw new TypeError( "originalTags parameter must be an array" ); + } + + this.loadMappingList(); + + const newTags = []; + const keys = Object.keys( this.tagMappings ); + + for ( const tag of originalTags ) { + if ( keys.indexOf( tag ) !== -1 ) { + if ( this.tagMappings[ tag ] !== null ) { + newTags.push( + this.tagMappings[ tag ] + ); + } else { + continue; + } + } else { + newTags.push( tag ); + } + } + + return newTags; + + } + +} + +export default TagReplacer; diff --git a/tests/artefacts/tag-replacer/tag-mapping.yml b/tests/artefacts/tag-replacer/tag-mapping.yml new file mode 100644 index 0000000..1241736 --- /dev/null +++ b/tests/artefacts/tag-replacer/tag-mapping.yml @@ -0,0 +1,5 @@ +flindersuniversity: "FlindersUniversity" +australiannatives: "AustralianNatives" +crossstitch: "CrossStitch" +teddybear: "TeddyBear" +weather: null diff --git a/tests/lib/tag-replacer.test.js b/tests/lib/tag-replacer.test.js new file mode 100644 index 0000000..abbd005 --- /dev/null +++ b/tests/lib/tag-replacer.test.js @@ -0,0 +1,134 @@ +import assert from "node:assert/strict"; +import path from "node:path"; +import { describe, it } from "node:test"; + +import TagReplacer from "../../src/lib/tag-replacer.js"; + +const testFailPathOne = path.resolve( "tests/artefacts/not-found" ); +const testFailPathTwo = path.resolve( "tests/artefacts/" ); +const testPassPath = path.resolve( "tests/artefacts/tag-replacer/tag-mapping.yml" ); +const testPassMappingCount = 5; + +const testTagList = [ + "australiannatives", + "teddybear", + "SouthAustralia", + "weather" +]; + +const expectedTagList = [ + "AustralianNatives", + "TeddyBear", + "SouthAustralia" +]; + +describe( "TagReplacer", () => { + + describe( "Constructor", () => { + + it( "should throw a TypeError when the YAML file cannot be found", () => { + + assert.throws( + () => { + new TagReplacer( + testFailPathOne + ); + }, + { + name: "TypeError", + message: /not found/ + } + ); + + } ); + + it( "should throw a TypeError when the path is to a directory", () => { + + assert.throws( + () => { + new TagReplacer( + testFailPathTwo + ); + }, + { + name: "TypeError", + message: /must .* a file/ + } + ); + } ); + + it( "should not throw a TypeError when the path valid", () => { + + assert.doesNotThrow( + () => { + new TagReplacer( + testPassPath + ); + } + ); + } ); + + } ); + + describe( "loadMappingList", () => { + + it( "should successfully load the list of mappings", () => { + + const tagReplacer = new TagReplacer( testPassPath ); + + tagReplacer.loadMappingList(); + + } ); + } ); + + describe( "getMappingCount", () => { + + it( "should return the expected number of mappings", () => { + + const tagReplacer = new TagReplacer( testPassPath ); + + const mappingCount = tagReplacer.getMappingCount(); + + assert.equal( + mappingCount, + testPassMappingCount + ); + + } ); + + } ); + + describe( "replaceTags", () => { + + it( "should thrown a TypeError if the parameter is not an array", () => { + + const tagReplacer = new TagReplacer( testPassPath ); + + assert.throws( + () => { + tagReplacer.replaceTags( "fail" ); + }, + { + name: "TypeError", + message: /parameter must .* an array/ + } + ); + + } ); + + it( "should replace the required tags", () => { + + const tagReplacer = new TagReplacer( testPassPath ); + + const actualTagList = tagReplacer.replaceTags( testTagList ); + + assert.deepEqual( + actualTagList, + expectedTagList + ); + + } ); + + } ); + +} );