assign-gingerly 0.0.33 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -734,7 +734,7 @@ console.log(obj);
734
734
  // }
735
735
  ```
736
736
 
737
- The `+=` command syntax is `<path> +=` where the path can use the `?.` nested notation. The right-hand side value is added to the existing value using `+=`. If the path doesn't exist, it's created and set directly to the value. If the expression is a string, string concatenation is used. If the expression can't be "added to", it allows JavaScript to throw its natural error.
737
+ The `+=` command syntax is `<path> +=` where the path uses the `?.` nested notation for nested properties, or a plain key for direct properties. The right-hand side value is added to the existing value using `+=`. If the path doesn't exist, it's created and set directly to the value. If the expression is a string, string concatenation is used. If the expression can't be "added to", it allows JavaScript to throw its natural error.
738
738
 
739
739
  ## Example 5 - Toggling boolean values and negating
740
740
 
@@ -764,7 +764,7 @@ console.log(obj);
764
764
  // }
765
765
  ```
766
766
 
767
- The `=!` command syntax is `<path> =!` where the path can use the `?.` nested notation.
767
+ The `=!` command syntax is `<path> =!` where the path uses the `?.` nested notation for nested properties, or a plain key for direct properties.
768
768
 
769
769
  For existing values, the toggle is performed using JavaScript's logical NOT operator (`!value`), regardless of what type it is.
770
770
 
package/assignGingerly.js CHANGED
@@ -201,11 +201,13 @@ function parseDeleteCommand(key) {
201
201
  }
202
202
  /**
203
203
  * Helper function to parse a path string with ?. notation
204
+ * Always splits on '?.' delimiter, preserving dots that are part of values
205
+ * (e.g., CSS class selectors like '.username')
206
+ * Paths must use ?. notation — plain dot notation is not supported.
204
207
  */
205
208
  function parsePath(path) {
206
209
  return path
207
- .split('.')
208
- .map(part => part.replace(/\?/g, ''))
210
+ .split('?.')
209
211
  .filter(part => part.length > 0);
210
212
  }
211
213
  /**
@@ -572,16 +574,25 @@ export function assignGingerly(target, source, options) {
572
574
  if (isIncCommand(key)) {
573
575
  const path = parseIncCommand(key);
574
576
  if (path) {
575
- const pathParts = parsePath(path);
576
- const lastKey = pathParts[pathParts.length - 1];
577
- const parent = ensureNestedPath(target, pathParts);
578
- // If the path doesn't exist, set it directly to the value
579
- if (!(lastKey in parent)) {
580
- parent[lastKey] = value;
577
+ if (isNestedPath(path)) {
578
+ const pathParts = parsePath(path);
579
+ const lastKey = pathParts[pathParts.length - 1];
580
+ const parent = ensureNestedPath(target, pathParts);
581
+ if (!(lastKey in parent)) {
582
+ parent[lastKey] = value;
583
+ }
584
+ else {
585
+ parent[lastKey] += value;
586
+ }
581
587
  }
582
588
  else {
583
- // Path exists, apply increment: oldValue += newValue
584
- parent[lastKey] += value;
589
+ // Plain key - direct operation on target
590
+ if (!(path in target)) {
591
+ target[path] = value;
592
+ }
593
+ else {
594
+ target[path] += value;
595
+ }
585
596
  }
586
597
  }
587
598
  continue;
@@ -591,10 +602,18 @@ export function assignGingerly(target, source, options) {
591
602
  const lhsPath = parseToggleCommand(key);
592
603
  if (lhsPath) {
593
604
  const rhsPath = value;
594
- // Parse LHS path
595
- const lhsPathParts = parsePath(lhsPath);
596
- const lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
597
- const lhsParent = ensureNestedPath(target, lhsPathParts);
605
+ // Resolve LHS
606
+ let lhsParent;
607
+ let lhsLastKey;
608
+ if (isNestedPath(lhsPath)) {
609
+ const lhsPathParts = parsePath(lhsPath);
610
+ lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
611
+ lhsParent = ensureNestedPath(target, lhsPathParts);
612
+ }
613
+ else {
614
+ lhsLastKey = lhsPath;
615
+ lhsParent = target;
616
+ }
598
617
  // Determine what to negate
599
618
  let valueToNegate;
600
619
  if (rhsPath === '.') {
@@ -603,26 +622,30 @@ export function assignGingerly(target, source, options) {
603
622
  valueToNegate = lhsParent[lhsLastKey];
604
623
  }
605
624
  else {
606
- // LHS doesn't exist, treat as undefined -> !undefined = true
607
625
  valueToNegate = undefined;
608
626
  }
609
627
  }
610
628
  else {
611
629
  // RHS path: navigate to get the value (don't create paths)
612
- const rhsPathParts = parsePath(rhsPath);
613
- let current = target;
614
- let exists = true;
615
- for (const part of rhsPathParts) {
616
- if (current && typeof current === 'object' && part in current) {
617
- current = current[part];
618
- }
619
- else {
620
- exists = false;
621
- break;
630
+ if (isNestedPath(rhsPath)) {
631
+ const rhsPathParts = parsePath(rhsPath);
632
+ let current = target;
633
+ let exists = true;
634
+ for (const part of rhsPathParts) {
635
+ if (current && typeof current === 'object' && part in current) {
636
+ current = current[part];
637
+ }
638
+ else {
639
+ exists = false;
640
+ break;
641
+ }
622
642
  }
643
+ valueToNegate = exists ? current : true;
644
+ }
645
+ else {
646
+ // Plain key RHS
647
+ valueToNegate = (rhsPath in target) ? target[rhsPath] : true;
623
648
  }
624
- // If RHS doesn't exist, treat as truthy (will become false)
625
- valueToNegate = exists ? current : true;
626
649
  }
627
650
  // Apply negation to LHS
628
651
  lhsParent[lhsLastKey] = !valueToNegate;
@@ -633,28 +656,37 @@ export function assignGingerly(target, source, options) {
633
656
  if (isDeleteCommand(key)) {
634
657
  const path = parseDeleteCommand(key);
635
658
  if (path !== null) {
636
- const pathParts = parsePath(path);
637
659
  // Determine the parent object
638
660
  let parent = target;
639
661
  let canDelete = true;
640
- // If path is empty or just '?', delete from root
641
- if (pathParts.length === 0) {
642
- parent = target;
643
- }
644
- else {
645
- // Navigate to parent object
646
- for (const part of pathParts) {
647
- if (parent && typeof parent === 'object' && part in parent) {
648
- parent = parent[part];
649
- }
650
- else {
651
- canDelete = false;
652
- break;
662
+ if (isNestedPath(path)) {
663
+ const pathParts = parsePath(path);
664
+ if (pathParts.length === 0) {
665
+ parent = target;
666
+ }
667
+ else {
668
+ for (const part of pathParts) {
669
+ if (parent && typeof parent === 'object' && part in parent) {
670
+ parent = parent[part];
671
+ }
672
+ else {
673
+ canDelete = false;
674
+ break;
675
+ }
653
676
  }
654
677
  }
655
678
  }
679
+ else if (path.length > 0) {
680
+ // Plain key - navigate one level
681
+ if (parent && typeof parent === 'object' && path in parent) {
682
+ parent = parent[path];
683
+ }
684
+ else {
685
+ canDelete = false;
686
+ }
687
+ }
688
+ // else: empty path = delete from root
656
689
  if (canDelete && typeof parent === 'object' && parent !== null) {
657
- // RHS can be a string (single property) or array (multiple properties)
658
690
  const propertiesToDelete = Array.isArray(value) ? value : [value];
659
691
  for (const prop of propertiesToDelete) {
660
692
  if (prop in parent) {
package/assignGingerly.ts CHANGED
@@ -326,11 +326,13 @@ function parseDeleteCommand(key: string): string | null {
326
326
 
327
327
  /**
328
328
  * Helper function to parse a path string with ?. notation
329
+ * Always splits on '?.' delimiter, preserving dots that are part of values
330
+ * (e.g., CSS class selectors like '.username')
331
+ * Paths must use ?. notation — plain dot notation is not supported.
329
332
  */
330
333
  function parsePath(path: string): string[] {
331
334
  return path
332
- .split('.')
333
- .map(part => part.replace(/\?/g, ''))
335
+ .split('?.')
334
336
  .filter(part => part.length > 0);
335
337
  }
336
338
 
@@ -736,16 +738,22 @@ export function assignGingerly(
736
738
  if (isIncCommand(key)) {
737
739
  const path = parseIncCommand(key);
738
740
  if (path) {
739
- const pathParts = parsePath(path);
740
- const lastKey = pathParts[pathParts.length - 1];
741
- const parent = ensureNestedPath(target, pathParts);
742
-
743
- // If the path doesn't exist, set it directly to the value
744
- if (!(lastKey in parent)) {
745
- parent[lastKey] = value;
741
+ if (isNestedPath(path)) {
742
+ const pathParts = parsePath(path);
743
+ const lastKey = pathParts[pathParts.length - 1];
744
+ const parent = ensureNestedPath(target, pathParts);
745
+ if (!(lastKey in parent)) {
746
+ parent[lastKey] = value;
747
+ } else {
748
+ parent[lastKey] += value;
749
+ }
746
750
  } else {
747
- // Path exists, apply increment: oldValue += newValue
748
- parent[lastKey] += value;
751
+ // Plain key - direct operation on target
752
+ if (!(path in target)) {
753
+ target[path] = value;
754
+ } else {
755
+ target[path] += value;
756
+ }
749
757
  }
750
758
  }
751
759
  continue;
@@ -757,10 +765,17 @@ export function assignGingerly(
757
765
  if (lhsPath) {
758
766
  const rhsPath = value;
759
767
 
760
- // Parse LHS path
761
- const lhsPathParts = parsePath(lhsPath);
762
- const lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
763
- const lhsParent = ensureNestedPath(target, lhsPathParts);
768
+ // Resolve LHS
769
+ let lhsParent: any;
770
+ let lhsLastKey: string;
771
+ if (isNestedPath(lhsPath)) {
772
+ const lhsPathParts = parsePath(lhsPath);
773
+ lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
774
+ lhsParent = ensureNestedPath(target, lhsPathParts);
775
+ } else {
776
+ lhsLastKey = lhsPath;
777
+ lhsParent = target;
778
+ }
764
779
 
765
780
  // Determine what to negate
766
781
  let valueToNegate;
@@ -769,26 +784,27 @@ export function assignGingerly(
769
784
  if (lhsLastKey in lhsParent) {
770
785
  valueToNegate = lhsParent[lhsLastKey];
771
786
  } else {
772
- // LHS doesn't exist, treat as undefined -> !undefined = true
773
787
  valueToNegate = undefined;
774
788
  }
775
789
  } else {
776
790
  // RHS path: navigate to get the value (don't create paths)
777
- const rhsPathParts = parsePath(rhsPath);
778
- let current = target;
779
- let exists = true;
780
-
781
- for (const part of rhsPathParts) {
782
- if (current && typeof current === 'object' && part in current) {
783
- current = current[part];
784
- } else {
785
- exists = false;
786
- break;
791
+ if (isNestedPath(rhsPath)) {
792
+ const rhsPathParts = parsePath(rhsPath);
793
+ let current = target;
794
+ let exists = true;
795
+ for (const part of rhsPathParts) {
796
+ if (current && typeof current === 'object' && part in current) {
797
+ current = current[part];
798
+ } else {
799
+ exists = false;
800
+ break;
801
+ }
787
802
  }
803
+ valueToNegate = exists ? current : true;
804
+ } else {
805
+ // Plain key RHS
806
+ valueToNegate = (rhsPath in target) ? target[rhsPath] : true;
788
807
  }
789
-
790
- // If RHS doesn't exist, treat as truthy (will become false)
791
- valueToNegate = exists ? current : true;
792
808
  }
793
809
 
794
810
  // Apply negation to LHS
@@ -801,31 +817,36 @@ export function assignGingerly(
801
817
  if (isDeleteCommand(key)) {
802
818
  const path = parseDeleteCommand(key);
803
819
  if (path !== null) {
804
- const pathParts = parsePath(path);
805
-
806
820
  // Determine the parent object
807
821
  let parent = target;
808
822
  let canDelete = true;
809
823
 
810
- // If path is empty or just '?', delete from root
811
- if (pathParts.length === 0) {
812
- parent = target;
813
- } else {
814
- // Navigate to parent object
815
- for (const part of pathParts) {
816
- if (parent && typeof parent === 'object' && part in parent) {
817
- parent = parent[part];
818
- } else {
819
- canDelete = false;
820
- break;
824
+ if (isNestedPath(path)) {
825
+ const pathParts = parsePath(path);
826
+ if (pathParts.length === 0) {
827
+ parent = target;
828
+ } else {
829
+ for (const part of pathParts) {
830
+ if (parent && typeof parent === 'object' && part in parent) {
831
+ parent = parent[part];
832
+ } else {
833
+ canDelete = false;
834
+ break;
835
+ }
821
836
  }
822
837
  }
838
+ } else if (path.length > 0) {
839
+ // Plain key - navigate one level
840
+ if (parent && typeof parent === 'object' && path in parent) {
841
+ parent = parent[path];
842
+ } else {
843
+ canDelete = false;
844
+ }
823
845
  }
846
+ // else: empty path = delete from root
824
847
 
825
848
  if (canDelete && typeof parent === 'object' && parent !== null) {
826
- // RHS can be a string (single property) or array (multiple properties)
827
849
  const propertiesToDelete = Array.isArray(value) ? value : [value];
828
-
829
850
  for (const prop of propertiesToDelete) {
830
851
  if (prop in parent) {
831
852
  delete parent[prop];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assign-gingerly",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "This package provides a utility function for carefully merging one object into another.",
5
5
  "homepage": "https://github.com/bahrus/assign-gingerly#readme",
6
6
  "bugs": {