Browse Source

de{get,cow,comap}(x) => atPath(x).{get,put,map}

Herby Vojčík 6 years ago
parent
commit
f23b111c33
2 changed files with 58 additions and 84 deletions
  1. 37 49
      README.md
  2. 21 35
      index.js

+ 37 - 49
README.md

@@ -2,31 +2,35 @@
 
 Access and copy-on-modify JavaScript objects, including maps, using deep paths.
 
-### `deepGetOrNil(key, ...)`
-### `deget(key, ...)`
+### `atPath(key, ...)(key, ...)...`
 
-Creates an accessor function allowing
-to get specified key from any object.
+Returns set of accessing / modifying function for specified
+path of keys.
 
-Specify keys by passing a list of keys to `deepGetOrNil`.
+Specify keys by passing a list of keys to `atPath`.
 Key can be either:
  - number
  - `keyInMap(obj)`
  - array of Keys
  - anything else, which is `toString()`ed and dot-split.
 
+### `atPath(key, ...)(key, ...)... .get`
+
+An accessor function allowing
+to get specified key from any object.
+
 ```js
-const name = deget("name");
+const name = atPath("name").get;
   
 name({name: "Tom"});
 // => "Tom"
   
-const city = deget("address.city");
-const city2 = deget("address", "city");
+const city = atPath("address.city").get;
+const city2 = atPath("address", "city").get;
 // and other forms, like:
-// const city3 = deget(["address", "city"]);
-// const city4 = deget("address", [[], "city"]);
-// const city5 = deget([[], "address.city"]);
+// const city3 = atPath(["address", "city"]).get;
+// const city4 = atPath("address")([[], "city"]).get;
+// const city5 = atPath()([[], "address.city"])().get;
 // etc.
 const object = {address: {city: "New York"}};
   
@@ -55,13 +59,12 @@ an object will be treated as an array.
 If you put a `keyInMap(obj)` in a list of keys to use,
 an object will be treated as a `Map`.
 
-That way you can create eg. `const c = deget("person", 34, "name")`
+That way you can create eg. `const c = atPath("person", 34, "name").get`
 to access `obj.person[34].name` with `c(obj)`.
 
-### `deepCopyOnWrite(key, ...)(val)`
-### `decow(key, ...)(val)`
+### `atPath(key, ...)(key, ...)... .put(val)`
 
-Creates a modifier function allowing
+A modifier function allowing
 to "set" specified key to any object
 in an immutable fashion, eg. creating a modified copy
 when actual write happens.
@@ -69,28 +72,21 @@ when actual write happens.
 If properties that are to be parents of a sub-value
 are not present, they are created.
 
-Specify keys by passing a list of keys to `deepCopyOnWrite`.
-Key can be either:
- - number
- - `keyInMap(obj)`
- - array of Keys
- - anything else, which is `toString()`ed and dot-split.
-
 In case no change actually happens (same value is set which is
 already present), returns the original object.
 
 ```js
-const setName = decow("name");
+const setName = atPath("name").put;
   
 setName("Jerry")({name: "Tom"});
 // => {name: "Jerry"}
   
-const setCity = decow("address.city");
-const setCity2 = decow("address", "city");
+const setCity = atPath("address.city").put;
+const setCity2 = atPath("address", "city").put;
 // and other forms, like:
-// const setCity3 = decow(["address", "city"]);
-// const setCity4 = decow("address", [[], "city"]);
-// const setCity5 = decow([[], "address.city"]);
+// const setCity3 = atPath(["address", "city"]).put;
+// const setCity4 = atPath("address")([[], "city"]).put;
+// const setCity5 = atPath()([[], "address.city"])().put;
 // etc.
 const object = {address: {city: "New York"}};
   
@@ -102,7 +98,7 @@ object;
 // => {address: {city: "New York"}}
 setCity("New York")(object) === object;
 // => true
-setCity("New York")(object) === object;
+setCity2("New York")(object) === object;
 // => true
   
 const setCityLondon = setCity("London");
@@ -126,7 +122,7 @@ an object will be treated as an array (unlike the default string case,
 where it is treated as an object), so copy wil be created
 using `[...obj]`, not using `{...obj}`.
 
-That way you can create eg. `const c = decow("person", 34, "name")`
+That way you can create eg. `const c = atPath("person", 34, "name").put`
 to "set" `obj.person[34].name` with `c(val)(obj)`.
 
 If you put a `keyInMap(key)` in a list of keys to use,
@@ -134,10 +130,9 @@ an object will be treated as a `Map` (unlike the default string case,
 where it is treated as an object), so copy wil be created
 using `new Map(obj)`, not using `{...obj}`.
 
-### `deepCopyOnMap(key, ...)(fn)`
-### `decomap(key, ...)(fn)`
+### `atPath(key, ...)(key, ...)... .map(fn)`
 
-Creates a modifier function allowing
+A modifier function allowing
 to "map" value at specified key
 in an immutable fashion, eg. creating a modified copy
 when actual modification happens.
@@ -147,28 +142,21 @@ are not present, they are _not_ created.
 In other words, if the key to modify does not exist,
 no change happens.
 
-Specify keys by passing a list of keys to `deepCopyOnMap`.
-Key can be either:
- - number
- - `keyInMap(obj)`
- - array of Keys
- - anything else, which is `toString()`ed and dot-split.
-
 In case no change actually happens (same value is set which is
 already present), returns the original object.
 
 ```js
-const mapName = decomap("name");
+const mapName = atPath("name").map;
   
 mapName(x => x.toUpperCase())({name: "Tom"});
 // => {name: "TOM"}
   
-const mapCity = decomap("address.city");
-const mapCity2 = decomap("address", "city");
+const mapCity = atPath("address.city").map;
+const mapCity2 = atPath("address", "city").map;
 // and other forms, like:
-// const mapCity3 = decomap(["address", "city"]);
-// const mapCity4 = decomap("address", [[], "city"]);
-// const mapCity5 = decomap([[], "address.city"]);
+// const mapCity3 = atPath(["address", "city"]).map;
+// const mapCity4 = atPath("address")([[], "city"]).map
+// const mapCity5 = atPath()([[], "address.city"])().map
 // etc.
 const object = {address: {city: "New York"}};
   
@@ -204,8 +192,8 @@ an object will be treated as an array (unlike the default string case,
 where it is treated as an object), so copy wil be created
 using `[...obj]`, not using `{...obj}`.
 
-That way you can create eg. `const c = decomap("person", 34, "name")`
-to "set" `obj.person[34].name` with `c(fn)(obj)`.
+That way you can create eg. `const c = atPath("person", 34, "name").map`
+to "map" `obj.person[34].name` with `c(fn)(obj)`.
 
 If you put a `keyInMap(key)` in a list of keys to use,
 an object will be treated as a `Map` (unlike the default string case,
@@ -220,7 +208,7 @@ Creates "`obj` as an index in a map".
 ### `cowWorkshop(keys, fn = x => x)(obj, [options])`
 
 This is multipurpose enumerate-and-act function to manipulate objects
-using `deget` and `decow`. The `options` argument can contain these additional fields:
+using `atPath`. The `options` argument can contain these additional fields:
   - `result` -- where to put elements (`obj` by default),
   - `resultKeys` -- what keys to use to put into `result` (`keys` by default)
   - `diff` -- where to put diffing elements (`undefined` by default)

+ 21 - 35
index.js

@@ -27,21 +27,14 @@ const copyWith = (key, value) =>
         isMapKey(key) ? copyMapWith(key.key, value) :
             copyObjectWith(key, value);
 
-const constructKeys = function (keyDescriptions) {
-    const keys = [];
-
-    function fillKeys (keyDescriptions) {
-        keyDescriptions.forEach(each => {
-            if (typeof each === 'number' || isMapKey(each)) keys.push(each);
-            else if (Array.isArray(each)) fillKeys(each);
-            else keys.push(...each.toString().split('.'));
-        });
-    }
-
-    fillKeys(keyDescriptions);
-
+function parseKeysInto (keyDescriptions, keys) {
+    keyDescriptions.forEach(each => {
+        if (typeof each === 'number' || isMapKey(each)) keys.push(each);
+        else if (Array.isArray(each)) fillKeys(each);
+        else keys.push(...each.toString().split('.'));
+    });
     return keys;
-};
+}
 
 const getKey = (x, key) => x == null ? undefined : isMapKey(key) ? x.get(key.key) : x[key];
 
@@ -50,13 +43,6 @@ const getKeyWithCheck = (x, key) =>
         isMapKey(key) ? x.has(key.key) ? {value: x.get(key.key)} : null :
             x.hasOwnProperty(key) ? {value: x[key]} : null;
 
-export const deepGetOrNil = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return obj => keys.reduce(getKey, obj);
-};
-
-export const deget = deepGetOrNil;
-
 const chain = (list, stop, step) => (function worker (index) {
     return index >= list.length ? stop : step(list[index], worker(index + 1));
 })(0);
@@ -66,25 +52,25 @@ const copyOnModification = (obj, key, value, modifierFn) => {
     return value === modified ? obj : copyWith(key, modified)(obj);
 };
 
-export const deepCopyOnWrite = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return val => chain(keys, () => val, (key, next) => x =>
-        copyOnModification(x, key, getKey(x, key), next)
-    );
-};
+const atPathBeginningWith = prefix => (...keyDescriptions) => {
+    const keys = parseKeysInto(keyDescriptions, [...prefix]);
+
+    return Object.assign(atPathBeginningWith(keys), {
+        get: obj => keys.reduce(getKey, obj),
 
-export const decow = deepCopyOnWrite;
+        put: val => chain(keys, () => val, (key, next) => x =>
+            copyOnModification(x, key, getKey(x, key), next)
+        ),
 
-export const deepCopyOnMap = (...keyDescriptions) => {
-    const keys = constructKeys(keyDescriptions);
-    return fn => chain(keys, fn, (key, next) => x => {
-        const valueWithCheck = getKeyWithCheck(x, key);
-        if (valueWithCheck == null) return x;
-        return copyOnModification(x, key, valueWithCheck.value, next);
+        map: fn => chain(keys, fn, (key, next) => x => {
+            const valueWithCheck = getKeyWithCheck(x, key);
+            if (valueWithCheck == null) return x;
+            return copyOnModification(x, key, valueWithCheck.value, next);
+        })
     });
 };
 
-export const decomap = deepCopyOnMap;
+export const atPath = atPathBeginningWith([]);
 
 export const cowWorkshop = (keys, fn = x => x) => (obj, {result = obj, resultKeys = keys, diff} = {}) => {
     keys.forEach((key, index) => {