This CL can be used to compare the style of nixpkgs-fmt against other formatters (nixpkgs, alejandra). Change-Id: I87c6abff6bcb546b02ead15ad0405f81e01b6d9e Reviewed-on: https://cl.tvl.fyi/c/depot/+/4397 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org> Reviewed-by: lukegb <lukegb@tvl.fyi> Reviewed-by: wpcarro <wpcarro@gmail.com> Reviewed-by: Profpatsch <mail@profpatsch.de> Reviewed-by: kanepyork <rikingcoding@gmail.com> Reviewed-by: tazjin <tazjin@tvl.su> Reviewed-by: cynthia <cynthia@tvl.fyi> Reviewed-by: edef <edef@edef.eu> Reviewed-by: eta <tvl@eta.st> Reviewed-by: grfn <grfn@gws.fyi>
		
			
				
	
	
		
			192 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ lib, depot, ... }:
 | 
						|
/*
 | 
						|
  JSON Merge-Patch for nix
 | 
						|
  Spec: https://tools.ietf.org/html/rfc7396
 | 
						|
 | 
						|
  An algorithm for changing and removing fields in nested objects.
 | 
						|
 | 
						|
  For example, given the following original document:
 | 
						|
 | 
						|
  {
 | 
						|
  a = "b";
 | 
						|
  c = {
 | 
						|
      d = "e";
 | 
						|
      f = "g";
 | 
						|
  }
 | 
						|
  }
 | 
						|
 | 
						|
  Changing the value of `a` and removing `f` can be achieved by merging the patch
 | 
						|
 | 
						|
  {
 | 
						|
  a = "z";
 | 
						|
  c.f = null;
 | 
						|
  }
 | 
						|
 | 
						|
  which results in
 | 
						|
 | 
						|
  {
 | 
						|
  a = "z";
 | 
						|
  c = {
 | 
						|
      d = "e";
 | 
						|
  };
 | 
						|
  }
 | 
						|
 | 
						|
  Pseudo-code:
 | 
						|
  define MergePatch(Target, Patch):
 | 
						|
      if Patch is an Object:
 | 
						|
        if Target is not an Object:
 | 
						|
          Target = {} # Ignore the contents and set it to an empty Object
 | 
						|
        for each Name/Value pair in Patch:
 | 
						|
          if Value is null:
 | 
						|
            if Name exists in Target:
 | 
						|
              remove the Name/Value pair from Target
 | 
						|
          else:
 | 
						|
            Target[Name] = MergePatch(Target[Name], Value)
 | 
						|
        return Target
 | 
						|
      else:
 | 
						|
        return Patch
 | 
						|
*/
 | 
						|
 | 
						|
let
 | 
						|
  foldlAttrs = op: init: attrs:
 | 
						|
    lib.foldl' op init
 | 
						|
      (lib.mapAttrsToList lib.nameValuePair attrs);
 | 
						|
 | 
						|
  mergePatch = target: patch:
 | 
						|
    if lib.isAttrs patch
 | 
						|
    then
 | 
						|
      let target' = if lib.isAttrs target then target else { };
 | 
						|
      in foldlAttrs
 | 
						|
        (acc: patchEl:
 | 
						|
          if patchEl.value == null
 | 
						|
          then removeAttrs acc [ patchEl.name ]
 | 
						|
          else acc // {
 | 
						|
            ${patchEl.name} =
 | 
						|
              mergePatch
 | 
						|
                (acc.${patchEl.name} or "unnused")
 | 
						|
                patchEl.value;
 | 
						|
          })
 | 
						|
        target'
 | 
						|
        patch
 | 
						|
    else patch;
 | 
						|
 | 
						|
  inherit (depot.nix.runTestsuite)
 | 
						|
    runTestsuite
 | 
						|
    it
 | 
						|
    assertEq
 | 
						|
    ;
 | 
						|
 | 
						|
  tests =
 | 
						|
    let
 | 
						|
      # example target from the RFC
 | 
						|
      testTarget = {
 | 
						|
        a = "b";
 | 
						|
        c = {
 | 
						|
          d = "e";
 | 
						|
          f = "g";
 | 
						|
        };
 | 
						|
      };
 | 
						|
      # example patch from the RFC
 | 
						|
      testPatch = {
 | 
						|
        a = "z";
 | 
						|
        c.f = null;
 | 
						|
      };
 | 
						|
      emptyPatch = it "the empty patch returns the original target" [
 | 
						|
        (assertEq "id"
 | 
						|
          (mergePatch testTarget { })
 | 
						|
          testTarget)
 | 
						|
      ];
 | 
						|
      nonAttrs = it "one side is a non-attrset value" [
 | 
						|
        (assertEq "target is a value means the value is replaced by the patch"
 | 
						|
          (mergePatch 42 testPatch)
 | 
						|
          (mergePatch { } testPatch))
 | 
						|
        (assertEq "patch is a value means it replaces target alltogether"
 | 
						|
          (mergePatch testTarget 42)
 | 
						|
          42)
 | 
						|
      ];
 | 
						|
      rfcExamples = it "the examples from the RFC" [
 | 
						|
        (assertEq "a subset is deleted and overwritten"
 | 
						|
          (mergePatch testTarget testPatch)
 | 
						|
          {
 | 
						|
            a = "z";
 | 
						|
            c = {
 | 
						|
              d = "e";
 | 
						|
            };
 | 
						|
          })
 | 
						|
        (assertEq "a more complicated example from the example section"
 | 
						|
          (mergePatch
 | 
						|
            {
 | 
						|
              title = "Goodbye!";
 | 
						|
              author = {
 | 
						|
                givenName = "John";
 | 
						|
                familyName = "Doe";
 | 
						|
              };
 | 
						|
              tags = [ "example" "sample" ];
 | 
						|
              content = "This will be unchanged";
 | 
						|
            }
 | 
						|
            {
 | 
						|
              title = "Hello!";
 | 
						|
              phoneNumber = "+01-123-456-7890";
 | 
						|
              author.familyName = null;
 | 
						|
              tags = [ "example" ];
 | 
						|
            })
 | 
						|
          {
 | 
						|
            title = "Hello!";
 | 
						|
            phoneNumber = "+01-123-456-7890";
 | 
						|
            author = {
 | 
						|
              givenName = "John";
 | 
						|
            };
 | 
						|
            tags = [ "example" ];
 | 
						|
            content = "This will be unchanged";
 | 
						|
          })
 | 
						|
      ];
 | 
						|
 | 
						|
      rfcTests =
 | 
						|
        let
 | 
						|
          r = index: target: patch: res:
 | 
						|
            (assertEq "test number ${toString index}"
 | 
						|
              (mergePatch target patch)
 | 
						|
              res);
 | 
						|
        in
 | 
						|
        it "the test suite from the RFC" [
 | 
						|
          (r 1 { "a" = "b"; } { "a" = "c"; } { "a" = "c"; })
 | 
						|
          (r 2 { "a" = "b"; } { "b" = "c"; } { "a" = "b"; "b" = "c"; })
 | 
						|
          (r 3 { "a" = "b"; } { "a" = null; } { })
 | 
						|
          (r 4 { "a" = "b"; "b" = "c"; }
 | 
						|
            { "a" = null; }
 | 
						|
            { "b" = "c"; })
 | 
						|
          (r 5 { "a" = [ "b" ]; } { "a" = "c"; } { "a" = "c"; })
 | 
						|
          (r 6 { "a" = "c"; } { "a" = [ "b" ]; } { "a" = [ "b" ]; })
 | 
						|
          (r 7 { "a" = { "b" = "c"; }; }
 | 
						|
            { "a" = { "b" = "d"; "c" = null; }; }
 | 
						|
            { "a" = { "b" = "d"; }; })
 | 
						|
          (r 8 { "a" = [{ "b" = "c"; }]; }
 | 
						|
            { "a" = [ 1 ]; }
 | 
						|
            { "a" = [ 1 ]; })
 | 
						|
          (r 9 [ "a" "b" ] [ "c" "d" ] [ "c" "d" ])
 | 
						|
          (r 10 { "a" = "b"; } [ "c" ] [ "c" ])
 | 
						|
          (r 11 { "a" = "foo"; } null null)
 | 
						|
          (r 12 { "a" = "foo"; } "bar" "bar")
 | 
						|
          (r 13 { "e" = null; } { "a" = 1; } { "e" = null; "a" = 1; })
 | 
						|
          (r 14 [ 1 2 ]
 | 
						|
            { "a" = "b"; "c" = null; }
 | 
						|
            { "a" = "b"; })
 | 
						|
          (r 15 { }
 | 
						|
            { "a" = { "bb" = { "ccc" = null; }; }; }
 | 
						|
            { "a" = { "bb" = { }; }; })
 | 
						|
        ];
 | 
						|
 | 
						|
    in
 | 
						|
    runTestsuite "mergePatch" [
 | 
						|
      emptyPatch
 | 
						|
      nonAttrs
 | 
						|
      rfcExamples
 | 
						|
      rfcTests
 | 
						|
    ];
 | 
						|
 | 
						|
in
 | 
						|
{
 | 
						|
  __functor = _: mergePatch;
 | 
						|
 | 
						|
  inherit tests;
 | 
						|
}
 |