Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

attempt 1 at childPaths rework #19

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 10 additions & 29 deletions src/childPaths.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getContext } from "./context.js";
import { enumerateChildPaths, getByPath } from "./util.js";

/**
* Get a node's children and the corresponding properties and indices
Expand All @@ -17,41 +18,21 @@ export default function childPaths (node) {
return [];
}

const childProperties = context.getChildProperties(node);
const paths = enumerateChildPaths(node, context.getChildProperties);

let children = [];

if (childProperties) {
for (const property of childProperties) {
const child = node[property];
// When the node is an array, we want to include the index in the result
if (Array.isArray(child)) {
let childPaths = child.map((c, index) => ({node: c, path: [property, index]}));
children.push(...childPaths);
}
else {
children.push({node: child, path: [property]});
}
for (const path of paths) {
const child = getByPath(node, path);
if (Array.isArray(child)) {
const childPaths = child.map((c, index) => context.isNode(c) ? ({node: c, path: [...path, index]}) : null).filter(Boolean);
children.push(...childPaths);
}
}
else {
for (let property in node) {
const child = node[property];

if (Array.isArray(child)) {
// Why not filter first? That would affect the index.
let childPaths = child.map((c, index) => (context.isNode(c) ? {node: c, path: [property, index]} : null)).filter(Boolean);
children.push(...childPaths);
}
else if (context.isNode(child)) {
children.push({node: child, path: [property]});
}
else if (context.isNode(child)) {
children.push({node: child, path});
}

}

return children;
}




22 changes: 2 additions & 20 deletions src/children.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
import { getContext } from "./context.js";
import childPaths from "./childPaths.js";

/**
* Get a node’s children as an array
* @param {object | object[]} node or nodes
* @returns {(object | string | number | boolean | null)[]}
*/
export default function children (node) {
if (Array.isArray(node)) {
// when node is an array, flatten to avoid nested arrays of children
return node.flatMap(node => children(node));
}

let context = getContext(this);

if (!context.isNode(node)) {
return [];
}

const childProperties = context.getChildProperties(node);

if (childProperties) {
return childProperties.flatMap(property => node[property] ?? []);
}
else {
return Object.values(node).flat();
}
return childPaths.call(this, node).map(({node}) => node);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is technically less efficient by a factor of 2, however since the amount of child is roughly constant I think it's well worth the tradeoff since we don't want this getting out of sync with childPaths

}
38 changes: 38 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,42 @@ export function setByPath (obj, path, value) {
const parent = getByPath(obj, parentPath);

return parent && (parent[lastKey] = value);
}

const wildcard = "*";

export function enumerateChildPaths (node, getChildProperties) {
const propertyPaths = Array.isArray(getChildProperties) ? getChildProperties : getChildProperties(node);
// if no properties are specified, return the keys of the object by default
if (!propertyPaths) {
return Object.keys(node).map(key => [key]);
}
const paths = [];
console.log(propertyPaths);
for (let propertyPath of propertyPaths) {
// if it's a flat single property, wrap it in an array
propertyPath = Array.isArray(propertyPath) ? propertyPath : [propertyPath];
// check for wildcard
if (propertyPath[propertyPath.length - 1] === wildcard) {
console.log("in here!", propertyPath)
// remove the wildcard
propertyPath.pop();
// get the value of the property path
const value = getByPath(node, propertyPath);
// if the value is an array, enumerate its items
if (Array.isArray(value)) {
value.forEach((_, index) => {
paths.push([...propertyPath, index]);
});
}
else {
// if the value is an object, enumerate its keys
paths.push(...Object.keys(value).map(key => [...propertyPath, key]));
}
}
else {
paths.push(propertyPath);
}
}
return paths;
}
53 changes: 46 additions & 7 deletions test/children.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,67 @@
import childPaths from "../src/childPaths.js";
import trees from "./utils/trees.js";
import Treecle from "../src/Treecle.js";

const tree = trees[0];
const basicTree = trees[0];
const complexTree = trees[2];

const treecle = new Treecle({
isNode: node => node && node.type,
getChildProperties: node => {
if (node.type === "single") {
return ["child"];
}
else if (node.type === "multi") {
return [["children", "*"]];
}
return [];
}
})

export default {
name: "children",
run (node) {
run (node, context) {
if (context) {
return childPaths.call(context, node);
}
return childPaths(node);
},
tests: [
{
name: "Root node",
args: [tree],
expect: [{node: tree.left, path: ["left"]}, {node: tree.right, path: ["right"]}]
args: [basicTree],
expect: [{node: basicTree.left, path: ["left"]}, {node: basicTree.right, path: ["right"]}]
},
{
name: "Non-root node with children",
args: [tree.right],
expect: [{node: tree.right.left, path: ["left"]}, {node: tree.right.right, path: ["right"]}],
args: [basicTree.right],
expect: [{node: basicTree.right.left, path: ["left"]}, {node: basicTree.right.right, path: ["right"]}],
},
{
name: "Leaf node",
args: [tree.left.left],
args: [basicTree.left.left],
expect: [],
},
{
name: "Custom settings root node",
args: [complexTree, treecle],
expect: [
{node: complexTree.children.one, path: ["children", "one"]},
{node: complexTree.children.two, path: ["children", "two"]},
{node: complexTree.children.three, path: ["children", "three"]}
]
},
{
name: "Custom settings non-root node",
args: [complexTree.children.two, treecle],
expect: [
{node: complexTree.children.two.child, path: ["child"]}
]
},
{
name: "Custom settings leaf node",
args: [complexTree.children.three.one, treecle],
expect: []
}
]
};
12 changes: 12 additions & 0 deletions test/utils/trees.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ const trees = [
{type: "single", child: {type: "leaf", name: "4"}}
]}
]
},
// tree with object children
{
type: "multi",
children: {
one: {type: "single", child: {type: "leaf", name: "1"}},
two: {type: "single", child: {type: "leaf", name: "2"}},
three: {type: "multi", children: {
one: {type: "single", child: {type: "leaf", name: "3"}},
two: {type: "single", child: {type: "leaf", name: "4"}}
}}
}
}
];

Expand Down