Refactoring ConfigMap

This won't mean much to anyone else today, before I've done more docs for #Transmissions. But It's a bit twisty, writing it done will help me figure it out.

TODO copy some of this across for Transmissions docs

A transmission is a pipeline of services, each of the form :


class Dave extends Service {
  constructor(config) {
    super(config)
  }

  async execute(message) {

    // process message

    this.emit('message', message)
  }

export default Dave

Each service should be relatively simple (for easy dev and to facilitate reuse). It will have a functional description (currently just in comments) called a #Signature.


The signature for ConfigMap (current source below) includes the description :

Maps RDF dataset contents to key-value pairs in the message object based on services-config.ttl

Ok...it is kinda doing that, but it's hardcoded for the #Postcraft flow. I need to look at the message before & after.

TODO message diff (premessage)->ServiceUnderTest->(postmessage)->Diff->(postmessage-premessage)

TODO setup Jena command-line tools (has rdfdiff)

Actually I need diff before that - check I haven't made significant changes to the RDF when playing with #FOAFRetro. Checking diff examples...

diff /home/danny/github-danny/transmissions/src/applications/postcraft/transmissions.ttl /home/danny/github-danny/foaf-retro/src/applications/postcraft/transmissions.ttl

Same.

diff /home/danny/github-danny/transmissions/src/applications/postcraft/services-config.ttl /home/danny/github-danny/foaf-retro/src/applications/postcraft/services-config.ttl

Same.

How to check the messages?

Handy, there's only one ConfigMap and it's early on. I can just pop in a couple of ShowMessage services followed by a DeadEnd:

   trm:pipe (:s10 :SM :s20 :SM2 :DE  :s30 :s40  :s50 :s60 :s70 :s80 :s90 :s100
              :s110 :s120 :s130 :s140 :s150  :s160 :s170 :s180) .

:s10 a :DatasetReader . # read the manifest
# trm:configKey trm:describe .

:s20 a :ConfigMap ; ### use services.ttl? - defer to RemapContext as possible
    trm:configKey :markdownToRawPosts .

Running -

danny@danny-desktop:~/github-danny/transmissions$ ./trans postcraft.render ../postcraft/danny.ayers.name

...

+ ***** Execute Transmission : render <http://hyperdata.it/transmissions/render>
| Running : http://hyperdata.it/transmissions/s10 a DatasetReader
| Running :  (s10) SM a ShowMessage
***************************
***  Message
Instance of Object with properties -
{
  "dataDir": "src/applications/postcraft/data",
  "rootDir": "../postcraft/danny.ayers.name",
  "tags": "s10.SM"
}
***************************
| Running :  (s10.SM) s20 a ConfigMap
| Running :  (s10.SM.s20) SM2 a ShowMessage
***************************
***  Message
Instance of Object with properties -
{
  "dataDir": "src/applications/postcraft/data",
  "rootDir": "../postcraft/danny.ayers.name",
  "tags": "s10.SM.s20.SM2",
  "filepath": "layouts/mediocre/templates/entry-content_template.njk",
  "template": "§§§ placeholer for debugging §§§",
  "entryContentMeta": {
    "sourceDir": "content-raw/entries",
    "targetDir": "cache/entries",
    "templateFilename": "layouts/mediocre/templates/entry-content_template.njk"
  },
  "entryContentToPage": {
    "targetDir": "public/home/entries",
    "templateFilename": "layouts/mediocre/templates/entry-page_template.njk"
  },
  "indexPage": {
    "filepath": "public/home/index.html",
    "templateFilename": "layouts/mediocre/templates/index-page_template.njk"
  }
}
***************************
| Running :  (s10.SM.s20.SM2) DE a DeadEnd
DeadEnd  at (s10.SM.s20.SM2.DE) DE

Ok, these are being pulled out of (as message.dataset) :

/home/danny/github-danny/postcraft/danny.ayers.name/manifest.ttl

# POST CONTENT
t:PostContent a pc:ContentGroup ;
    rdfs:label "entries" ;
    pc:site <https://danny.ayers.name> ;
    pc:subdir "home" ; # better property name?
    fs:sourceDirectory "content-raw/entries" ; # SOURCE DIR HERE journal, entries
    fs:targetDirectory "cache/entries" ;
    pc:template "layouts/mediocre/templates/entry-content_template.njk" .

# POST PAGES
t:PostPages a pc:ContentGroup ;
    pc:site <https://danny.ayers.name> ;
    fs:targetDirectory "public/home/entries" ;
    pc:template "layouts/mediocre/templates/entry-page_template.njk" .

# MAIN PAGE
t:IndexPage a pc:ContentGroup ; # TODO naming!
    pc:site <https://danny.ayers.name> ;
    fs:filepath "public/home/index.html" ;
    pc:template "layouts/mediocre/templates/index-page_template.njk" .

But the mapping are hardcoded in ConfigMap.js. They need to be declared in services-config.ttl.

I see a note in transmissions.ttl re. ConfigMap: 'defer to RemapContext as possible' but I'll ignore that for now, if necessary reconcile later. First,

 async execute(message) {
      logger.setLogLevel('debug')
      ...

The pc:ContentGroup bits are getting picked out with :

if (type.equals(ns.pc.ContentGroup)) {
  await this.processContentGroup(message, q.subject);
}

In need to generalise that in ConfigMap.js, replace ns.pc.ContentGroup with something specified in services-config.ttl.

That something needs to be pointed to in transmissions.turtle. Ok:

:s20 a :ConfigMap ;
    trm:configKey :postcraftMap .

Those should be addressable from service.config but how, where's a good example of where I was addressing those?

Found one, in FileCopy :

logger.debug(`FileCopy: Using configKey ${this.configKey.value}`);
source = this.getPropertyFromMyConfig(ns.trm.source);
destination = this.getPropertyFromMyConfig(ns.trm.destination);
source = path.join(message.rootDir, source);

That's derived from transmissions.ttl, eg :

:cp30 a :FileCopy ;
    trm:configKey :cssCopy .

and in services-config.ttl :

t:copyCSS a trm:ServiceConfig ;
    trm:key t:cssCopy ;
    trm:source "layouts/mediocre/css" ;
    trm:destination "public/home/css" .

So now I can make that part of the redirection for ConfigMap :

t:PostcraftMap a trm:ServiceConfig ;
    trm:key t:postcraftMap ;
    trm:group t:SampleGroup .

Next I need to grab that in ConfigMap :

logger.debug(`ConfigMap, Using configKey ${this.configKey.value}`);
const group = this.getPropertyFromMyConfig(ns.trm.group);
logger.debug(`ConfigMap, group =  ${group}`);

Yay!

| Running :  (s10.SM) s20 a ConfigMap
 api.logger debug
ConfigMap, Using configKey http://hyperdata.it/transmissions/postcraftMap
ConfigMap, group =  http://hyperdata.it/transmissions/SampleGroup

TODO while I'm here ... a little thing for transmissions.ttl : trm:match "string" so the service will be skipped unless string is matched. Thinking for XMPP agents.

ConfigMap.js as on 2024-09-06 morning :

// src/services/rdf/ConfigMap.js
/**
 * @class ConfigMap
 * @extends ProcessService
 * @classdesc
 * **a Transmissions Service**
 *
 * Maps RDF dataset contents to key-value pairs in the message object based on services-config.ttl
 *
 * ### Signature
 *
 * #### __*Input*__
 * * **`message.dataset`** - RDF dataset containing configuration
 *
 * #### __*Output*__
 * * **`message`** - Updated with mapped key-value pairs based on the dataset content
 *
 * #### __*Behavior*__
 * * Processes the RDF dataset in the message
 * * Identifies and processes different content groups (PostContent, PostPages, IndexPage)
 * * Maps relevant information to specific message properties
 *
 * #### __Tests__
 * * TODO: Add test information
 */

import ns from "../../utils/ns.js";
import rdf from "rdf-ext";
import grapoi from "grapoi";
import logger from "../../utils/Logger.js";
import ProcessService from "../base/ProcessService.js";

class ConfigMap extends ProcessService {
  constructor(config) {
    super(config);
  }

  /**
   * Executes the ConfigMap service
   * @param {Object} message - The message object containing the dataset
   * @todo Refactor for better generalization and maintainability
   */
  async execute(message) {
    //  logger.setLogLevel('debug')

    this.preProcess(message);
    const dataset = message.dataset;
    const poi = grapoi({ dataset, factory: rdf });
    const quads = await poi.out(ns.rdf.type).quads();

    for (const q of quads) {
      const type = q.object;
      if (type.equals(ns.pc.ContentGroup)) {
        await this.processContentGroup(message, q.subject);
      }
    }

    this.emit("message", message);
  }

  /**
   * Processes a content group based on its type
   * @param {Object} message - The message object
   * @param {Object} contentGroupID - The ID of the content group
   */
  async processContentGroup(message, contentGroupID) {
    switch (contentGroupID.value) {
      case ns.t.PostContent.value:
        await this.markdownToEntryContent(message, contentGroupID);
        break;
      case ns.t.PostPages.value:
        await this.entryContentToPostPage(message, contentGroupID);
        break;
      case ns.t.IndexPage.value:
        await this.indexPage(message, contentGroupID);
        break;
      default:
        logger.log("Group not found in dataset: " + contentGroupID.value);
    }
  }

  /**
   * Processes markdown to entry content
   * @param {Object} message - The message object
   * @param {Object} contentGroupID - The ID of the content group
   */
  async markdownToEntryContent(message, contentGroupID) {
    const postcraftConfig = message.dataset;
    const groupPoi = rdf.grapoi({
      dataset: postcraftConfig,
      term: contentGroupID,
    });

    // message.location = groupPoi.out(ns.pc.location).term.value
    // message.subdir = groupPoi.out(ns.pc.subdir).term.value
    message.filepath = groupPoi.out(ns.pc.template).term.value;
    message.template = "§§§ placeholer for debugging §§§";

    message.entryContentMeta = {
      sourceDir: groupPoi.out(ns.fs.sourceDirectory).term.value,
      targetDir: groupPoi.out(ns.fs.targetDirectory).term.value,
      templateFilename: groupPoi.out(ns.pc.template).term.value,
    };
  }

  /**
   * Processes entry content to post page
   * @param {Object} message - The message object
   * @param {Object} contentGroupID - The ID of the content group
   */
  async entryContentToPostPage(message, contentGroupID) {
    const postcraftConfig = message.dataset;
    const groupPoi = rdf.grapoi({
      dataset: postcraftConfig,
      term: contentGroupID,
    });

    message.entryContentToPage = {
      targetDir: groupPoi.out(ns.fs.targetDirectory).term.value,
      templateFilename: groupPoi.out(ns.pc.template).term.value,
    };
  }

  /**
   * Processes index page
   * @param {Object} message - The message object
   * @param {Object} contentGroupID - The ID of the content group
   */
  async indexPage(message, contentGroupID) {
    const postcraftConfig = message.dataset;
    const groupPoi = rdf.grapoi({
      dataset: postcraftConfig,
      term: contentGroupID,
    });

    message.indexPage = {
      filepath: groupPoi.out(ns.fs.filepath).term.value,
      templateFilename: groupPoi.out(ns.pc.template).term.value,
    };
  }
}

export default ConfigMap;

Refactoring ConfigMap