Skip to content

Commit

Permalink
flow command (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffhendrey authored Nov 19, 2024
1 parent 5470def commit 4da38e7
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stated-js",
"version": "0.1.45",
"version": "0.1.46",
"license": "Apache-2.0",
"description": "JSONata embedded in JSON",
"main": "./dist/src/index.js",
Expand Down
10 changes: 10 additions & 0 deletions src/CliCoreBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { exec } from 'child_process';
import http from 'http';
import * as child_process from "child_process";
import os from "os";
import {FlowOpt} from "./DataFlow.js";

/**
* Base class for building CLIs. By itself can be used for a CLI that does not support the tail command. Tail command
Expand Down Expand Up @@ -293,6 +294,15 @@ export class CliCoreBase {
return option === '--shallow' ? this.templateProcessor.getDependencies(jsonPtr) : this.templateProcessor.to(jsonPtr);
}

async flow(replCmdInputStr:string) {
if (!this.templateProcessor) {
throw new Error('Initialize the template first.');
}
const {level=0} = CliCoreBase.minimistArgs(replCmdInputStr); //--level=5
return this.templateProcessor.flow(level as FlowOpt);
}


async plan() {
if (!this.templateProcessor) {
throw new Error('Initialize the template first.');
Expand Down
129 changes: 129 additions & 0 deletions src/DataFlow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {JsonPointerString, MetaInfo} from "./MetaInfoProducer.js";
import TemplateProcessor from "./TemplateProcessor.js";
import {default as jp} from "./JsonPointer.js";
import {JsonPointer} from "./index.js";
//type TreeNode = {metaInfo:MetaInfo, dependees:TreeNode[]};

/**
* Represents a node in a data flow structure.
* A DataFlowNode can either be a JsonPointerString or an object with a location
* and an optional 'to' field which points to one or more subsequent DataFlowNodes or a JsonPointerString.
*
* @typedef {Object} DataFlowNode
* @property {JsonPointerString} location - The location of the data in a JSON structure.
* @property {DataFlowNode[]|DataFlowNode|JsonPointerString} [to] - Optional field that indicates the next node or nodes in the data flow.
*
* @typedef {string} JsonPointerString - A string that represents a JSON Pointer.
*/
export type DataFlowNode = {
location:JsonPointerString,
to?:DataFlowNode[]|DataFlowNode|JsonPointerString
} | JsonPointerString

export type FlowOpt = 0|1;


/**
* Class representing a DataFlow, managing dependencies and data flow nodes.
*/
export class DataFlow {
private templateProcessor: TemplateProcessor;
private visited:Map<MetaInfo, DataFlowNode>;
private roots:Set<DataFlowNode>;

constructor(templateProcessor: TemplateProcessor) {
this.visited = new Map<MetaInfo, DataFlowNode>();
this.roots = new Set<DataFlowNode>();
this.templateProcessor = templateProcessor;
}

/**
* Links the given metaInfo node with its dependees, creating and returning a new DataFlowNode.
*
* @param {MetaInfo} metaInfo - The metadata information object containing dependencies and location data.
* @return {DataFlowNode} - The created DataFlowNode with linked dependees.
*/
private linkDependees(metaInfo: MetaInfo): DataFlowNode {
let n:any = this.visited.get(metaInfo);
if(n){
return n;
}
const {absoluteDependencies__:deps, jsonPointer__:location} = metaInfo;
n = {location, to:[]};
this.visited.set(metaInfo, n);
for (const jsonPtr of metaInfo.dependees__) {
const dependeeMeta: MetaInfo = jp.get(this.templateProcessor.templateMeta, jsonPtr) as MetaInfo;
const dependeeTreeNode = this.linkDependees(dependeeMeta);
const dependees = n.to;
dependees.push(dependeeTreeNode);
}

//a root has no dependencies and at least one dependee (todo: degenerate case of expression like ${42} that has np dependencies)
if(metaInfo.absoluteDependencies__.length === 0 && metaInfo.dependees__.length > 0){
this.roots.add(n);
}

return n;
}


/**
* Recursively compacts the given DataFlowNode, reducing the 'to' field to a single object
* if there is only one child node, or omitting it if there are no child nodes.
*
* @param {DataFlowNode} node - The node to compact. This node may have a 'to' field
* that is an array of child nodes.
* @return {DataFlowNode} The compacted node, which may have a simplified 'to' field or none at all.
*/
private level1Compact(node:DataFlowNode):DataFlowNode {
const kids = (node as any).to? (node as any).to as DataFlowNode[]:[];
const compactKids = kids.map((kid) => this.level1Compact(kid));
const noKids = compactKids.length === 0;
const oneKid = compactKids.length === 1;
let compactNode;
let location: JsonPointerString;
if (typeof node === "string"){
location = node as JsonPointerString;
}else{
location = node.location;
}
if(noKids){
compactNode = location; //no 'to' at all, just a raw location
}else if (oneKid) {
const onlyKid = compactKids[0];
compactNode = {location, to: onlyKid}; //'to' is just a single thing, not an array
}else{
compactNode = {location, to:compactKids}
}
return compactNode as DataFlowNode;

}

/**
* Retrieves the roots of the data flow nodes based on the specified option.
*
* @param {string} level - The option for retrieving roots, it can be either "l0" or "l1".
* "l0" returns the roots as they are.
* "l1" returns the roots in a compacted form.
* @return {DataFlowNode[]} - An array of data flow node roots.
* @throws {Error} - Throws an error if the specified option is unknown.
*/
getRoots(level:FlowOpt=0):DataFlowNode[] {
this.visited = new Map<MetaInfo, DataFlowNode>();
this.roots = new Set<DataFlowNode>();
const metas = this.templateProcessor.metaInfoByJsonPointer["/"];
for (const m of metas) {
if(m.jsonPointer__ !== "") { //skip root json pointer
this.linkDependees(m);
}
}
if(level===0){
return Array.from(this.roots);
}else if (level===1){
return Array.from(this.roots).map(root=>this.level1Compact(root));
}else{
throw new Error(`Unknown option ${level}`);
}

}
}
1 change: 1 addition & 0 deletions src/StatedREPL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class StatedREPL {
["state", 'Show the current state of the templateMeta'],
["from", 'Show the dependents of a given JSON pointer'],
["to", 'Show the dependencies of a given JSON pointer'],
["flow", 'return an array or tree structures showing the data flow from sources to destinations'],
["plan", 'Show the evaluation plan'],
["note", "returns ═══ ... for creating documentation"],
["log", "set the log level [debug, info, warn, error]"],
Expand Down
13 changes: 12 additions & 1 deletion src/TemplateProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {LifecycleManager} from "./LifecycleManager.js";
import {accumulate} from "./utils/accumulate.js";
import {defaulter} from "./utils/default.js";
import {CliCoreBase} from "./CliCoreBase.js";
import {DataFlow, DataFlowNode, FlowOpt} from "./DataFlow.js";


declare const BUILD_TARGET: string | undefined;
Expand Down Expand Up @@ -986,7 +987,6 @@ export default class TemplateProcessor {
}
}


private topologicalSort(metaInfos:MetaInfo[], exprsOnly = true, fanout=true):JsonPointerString[] {
const visited = new Set();
const recursionStack:Set<JsonPointerString> = new Set(); //for circular dependency detection
Expand Down Expand Up @@ -1878,6 +1878,17 @@ export default class TemplateProcessor {
return [];
}


/**
* Controls the flow of data and retrieves root nodes based on the specified level.
*
* @param {FlowOpt} level - The level specifying the granularity of the data flow.
* @return {DataFlowNode[]} An array of root nodes that are computed based on the specified level.
*/
flow(level:FlowOpt):DataFlowNode[]{
return new DataFlow(this).getRoots(level);
}

/**
* Sets a data change callback function that will be called whenever the value at the json pointer has changed
* @param jsonPtr
Expand Down
Loading

0 comments on commit 4da38e7

Please sign in to comment.