Repository: calmm-js/partial.lenses
Pull request #12 (2016-03-17 12:55:42)

Added `L.props` for editing a subset of an obj.


Patch:
diff --git a/README.md b/README.md
index baaae58..2ddc9b8 100644
--- a/README.md
+++ b/README.md
@@ -535,6 +535,15 @@ lens:
 * When setting property to undefined, the property is removed from the result.
   If the result would be an empty object, the whole result will be undefined.
 
+#### [`L.props(key, ...keys)`](#lpropskey-keys "L.props :: (p1 :: a1, ...ps) -> PLens {p1 :: a1, ...ps, ...o} {p1 :: a1, ...ps}")
+
+`L.props(key, ...keys)` focuses on a subset of properties of an object.  The
+view of `L.props` is undefined when none of the properties is defined.
+Otherwise the view is an object containing a subset of the properties.  Setting
+through `L.props` updates the whole subset of properties, which means that any
+undefined properties are removed if they did exists previously.  When set, any
+extra properties are ignored.
+
 #### [`L.replace(inn, out)`](#lreplaceinn-out "L.replace :: Maybe s -> Maybe s -> PLens s s")
 
 `L.replace(inn, out)`, when viewed, replaces the value `inn` with `out` and vice
diff --git a/src/partial.lenses.js b/src/partial.lenses.js
index e0e0763..803139d 100644
--- a/src/partial.lenses.js
+++ b/src/partial.lenses.js
@@ -2,6 +2,8 @@ import R from "ramda"
 
 //
 
+const empty = {}
+
 const deleteKey = (k, o) => {
   if (o === undefined || !(k in o))
     return o
@@ -167,7 +169,7 @@ L.pick = template => R.lens(
     }
     return r
   },
-  (o = {}, cIn) => {
+  (o = empty, cIn) => {
     let c = cIn
     for (const k in template)
       c = L.set(template[k], o[k], c)
@@ -176,4 +178,38 @@ L.pick = template => R.lens(
 
 L.identity = R.lens(R.identity, R.identity)
 
+L.props = (k, ...ks) => {
+  const kks = [k, ...ks]
+  return R.lens(
+    (o = empty) => {
+      let r
+      kks.forEach(k => {
+        if (k in o) {
+          if (!r)
+            r = {}
+          r[k] = o[k]
+        }
+      })
+      return r
+    },
+    toConserve((s = empty, o = empty) => {
+      let r
+      for (const k in o) {
+        if (!R.contains(k, kks)) {
+          if (!r)
+            r = {}
+          r[k] = o[k]
+        }
+      }
+      kks.forEach(k => {
+        if (k in s) {
+          if (!r)
+            r = {}
+          r[k] = s[k]
+        }
+      })
+      return r
+    }))
+}
+
 export default L


Test patch:
diff --git a/test/tests.js b/test/tests.js
index 5b95e19..d4aecfd 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -154,6 +154,16 @@ describe("L.pick", () => {
   testEq('L.deleteAll(L(L.pick({x: "b", y: "a"}), L.firstOf("y", "x")), {a: [2], b: 1})', undefined)
 })
 
+describe("L.props", () => {
+  testEq('L.view(L.props("x", "y"), {x: 1, y: 2, z: 3})', {x: 1, y: 2})
+  testEq('L.view(L.props("x", "y"), {z: 3})', undefined)
+  testEq('L.view(L.props("x", "y"), {x: 2, z: 3})', {x: 2})
+  testEq('L.delete(L.props("x", "y"), {x: 1, y: 2, z: 3})', {z: 3})
+  testEq('L.set(L.props("x", "y"), {}, {x: 1, y: 2, z: 3})', {z: 3})
+  testEq('L.set(L.props("x", "y"), {y: 4}, {x: 1, y: 2, z: 3})', {y: 4, z: 3})
+  testEq('L.delete(L.props("x", "y"), {x: 1, y: 2})', undefined)
+})
+
 const BST = {
   search: key =>
     L(L.normalize(node => {