From 2fe77d98c764a05da1a534b4b45128785070f7ee Mon Sep 17 00:00:00 2001 From: phaer Date: Thu, 9 Nov 2023 22:36:28 +0000 Subject: [PATCH] feat: support descriptions from comments Check if the first node of a given nix file is a comment and if so, insert them after the files heading while correcting whitespace. --- src/main.rs | 54 ++- .../nixdoc__description_of_lib_debug.snap | 326 ++++++++++++++++++ test/lib-debug.nix | 246 +++++++++++++ 3 files changed, 620 insertions(+), 6 deletions(-) create mode 100644 src/snapshots/nixdoc__description_of_lib_debug.snap create mode 100644 test/lib-debug.nix diff --git a/src/main.rs b/src/main.rs index e2b8244..c29334e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -394,6 +394,28 @@ fn collect_entries(root: rnix::Root, category: &str) -> Vec { vec![] } +fn retrieve_description( + nix: &rnix::Root, + description: &str, + category: &str, +) -> String { + format!( + "# {} {{#sec-functions-library-{}}}\n{}", + description, + category, + dedent( + &nix.syntax() + .first_child() + .and_then(|node| + retrieve_doc_comment(&node, true) + // we add two spaces to the beginning after comment + // markers are stripped to keep dedent correct. + .map(|mut s| {s.insert_str(0, " "); s}) + ) .unwrap_or_default() + ) + ) +} + fn main() { let mut output = io::stdout(); let opts = Options::parse(); @@ -406,14 +428,14 @@ fn main() { .expect("could not read location information"), }; let nix = rnix::Root::parse(&src).ok().expect("failed to parse input"); + let description = retrieve_description( + &nix, + &opts.description, + &opts.category, + ); // TODO: move this to commonmark.rs - writeln!( - output, - "# {} {{#sec-functions-library-{}}}\n", - &opts.description, opts.category - ) - .expect("Failed to write header"); + writeln!(output, "{}", description).expect("Failed to write header"); for entry in collect_entries(nix, &opts.category) { entry @@ -450,6 +472,26 @@ fn test_main() { insta::assert_snapshot!(output); } +#[test] +fn test_description_of_lib_debug() { + let mut output = Vec::new(); + let src = fs::read_to_string("test/lib-debug.nix").unwrap(); + let nix = rnix::Root::parse(&src).ok().expect("failed to parse input"); + let category = "debug"; + let desc = retrieve_description(&nix, &"Debug", category); + writeln!(output, "{}", desc).expect("Failed to write header"); + + for entry in collect_entries(nix, category) { + entry + .write_section(&Default::default(), &mut output) + .expect("Failed to write section") + } + + let output = String::from_utf8(output).expect("not utf8"); + + insta::assert_snapshot!(output); +} + #[test] fn test_arg_formatting() { let mut output = Vec::new(); diff --git a/src/snapshots/nixdoc__description_of_lib_debug.snap b/src/snapshots/nixdoc__description_of_lib_debug.snap new file mode 100644 index 0000000..5997f87 --- /dev/null +++ b/src/snapshots/nixdoc__description_of_lib_debug.snap @@ -0,0 +1,326 @@ +--- +source: src/main.rs +expression: output +--- +# Debug {#sec-functions-library-debug} +Collection of functions useful for debugging +broken nix expressions. + +* `trace`-like functions take two values, print + the first to stderr and return the second. +* `traceVal`-like functions take one argument + which both printed and returned. +* `traceSeq`-like functions fully evaluate their + traced value before printing (not just to “weak + head normal form” like trace does by default). +* Functions that end in `-Fn` take an additional + function as their first argument, which is applied + to the traced value before it is printed. + +## `lib.debug.traceIf` {#function-library-lib.debug.traceIf} + +**Type**: `traceIf :: bool -> string -> a -> a` + +Conditionally trace the supplied message, based on a predicate. + +`pred` + +: Predicate to check + + +`msg` + +: Message that should be traced + + +`x` + +: Value to return + + +::: {.example #function-library-example-lib.debug.traceIf} +# `lib.debug.traceIf` usage example + +```nix +traceIf true "hello" 3 +trace: hello +=> 3 +``` +::: + +## `lib.debug.traceValFn` {#function-library-lib.debug.traceValFn} + +**Type**: `traceValFn :: (a -> b) -> a -> a` + +Trace the supplied value after applying a function to it, and +return the original value. + +`f` + +: Function to apply + + +`x` + +: Value to trace and return + + +::: {.example #function-library-example-lib.debug.traceValFn} +# `lib.debug.traceValFn` usage example + +```nix +traceValFn (v: "mystring ${v}") "foo" +trace: mystring foo +=> "foo" +``` +::: + +## `lib.debug.traceVal` {#function-library-lib.debug.traceVal} + +**Type**: `traceVal :: a -> a` + +Trace the supplied value and return it. + +::: {.example #function-library-example-lib.debug.traceVal} +# `lib.debug.traceVal` usage example + +```nix +traceVal 42 +# trace: 42 +=> 42 +``` +::: + +## `lib.debug.traceSeq` {#function-library-lib.debug.traceSeq} + +**Type**: `traceSeq :: a -> b -> b` + +`builtins.trace`, but the value is `builtins.deepSeq`ed first. + +`x` + +: The value to trace + + +`y` + +: The value to return + + +::: {.example #function-library-example-lib.debug.traceSeq} +# `lib.debug.traceSeq` usage example + +```nix +trace { a.b.c = 3; } null +trace: { a = ; } +=> null +traceSeq { a.b.c = 3; } null +trace: { a = { b = { c = 3; }; }; } +=> null +``` +::: + +## `lib.debug.traceSeqN` {#function-library-lib.debug.traceSeqN} + +**Type**: `traceSeqN :: Int -> a -> b -> b` + +Like `traceSeq`, but only evaluate down to depth n. +This is very useful because lots of `traceSeq` usages +lead to an infinite recursion. + +`depth` + +: Function argument + + +`x` + +: Function argument + + +`y` + +: Function argument + + +::: {.example #function-library-example-lib.debug.traceSeqN} +# `lib.debug.traceSeqN` usage example + +```nix +traceSeqN 2 { a.b.c = 3; } null +trace: { a = { b = {…}; }; } +=> null +``` +::: + +## `lib.debug.traceValSeqFn` {#function-library-lib.debug.traceValSeqFn} + +A combination of `traceVal` and `traceSeq` that applies a +provided function to the value to be traced after `deepSeq`ing +it. + +`f` + +: Function to apply + + +`v` + +: Value to trace + + +## `lib.debug.traceValSeq` {#function-library-lib.debug.traceValSeq} + +A combination of `traceVal` and `traceSeq`. + +## `lib.debug.traceValSeqNFn` {#function-library-lib.debug.traceValSeqNFn} + +A combination of `traceVal` and `traceSeqN` that applies a +provided function to the value to be traced. + +`f` + +: Function to apply + + +`depth` + +: Function argument + + +`v` + +: Value to trace + + +## `lib.debug.traceValSeqN` {#function-library-lib.debug.traceValSeqN} + +A combination of `traceVal` and `traceSeqN`. + +## `lib.debug.traceFnSeqN` {#function-library-lib.debug.traceFnSeqN} + +Trace the input and output of a function `f` named `name`, +both down to `depth`. + +This is useful for adding around a function call, +to see the before/after of values as they are transformed. + +`depth` + +: Function argument + + +`name` + +: Function argument + + +`f` + +: Function argument + + +`v` + +: Function argument + + +::: {.example #function-library-example-lib.debug.traceFnSeqN} +# `lib.debug.traceFnSeqN` usage example + +```nix +traceFnSeqN 2 "id" (x: x) { a.b.c = 3; } +trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; } +=> { a.b.c = 3; } +``` +::: + +## `lib.debug.runTests` {#function-library-lib.debug.runTests} + +**Type**: +``` +runTests :: { + tests = [ String ]; + ${testName} :: { + expr :: a; + expected :: a; + }; +} +-> +[ + { + name :: String; + expected :: a; + result :: a; + } +] +``` + +Evaluates a set of tests. + +A test is an attribute set `{expr, expected}`, +denoting an expression and its expected result. + +The result is a `list` of __failed tests__, each represented as +`{name, expected, result}`, + +- expected + - What was passed as `expected` +- result + - The actual `result` of the test + +Used for regression testing of the functions in lib; see +tests.nix for more examples. + +Important: Only attributes that start with `test` are executed. + +- If you want to run only a subset of the tests add the attribute `tests = ["testName"];` + +`tests` + +: Tests to run + + +::: {.example #function-library-example-lib.debug.runTests} +# `lib.debug.runTests` usage example + +```nix +runTests { + testAndOk = { + expr = lib.and true false; + expected = false; + }; + testAndFail = { + expr = lib.and true false; + expected = true; + }; +} +-> +[ + { + name = "testAndFail"; + expected = true; + result = false; + } +] +``` +::: + +## `lib.debug.testAllTrue` {#function-library-lib.debug.testAllTrue} + +Create a test assuming that list elements are `true`. + +`expr` + +: Function argument + + +::: {.example #function-library-example-lib.debug.testAllTrue} +# `lib.debug.testAllTrue` usage example + +```nix +{ testX = allTrue [ true ]; } +``` +::: + + diff --git a/test/lib-debug.nix b/test/lib-debug.nix new file mode 100644 index 0000000..97e87ac --- /dev/null +++ b/test/lib-debug.nix @@ -0,0 +1,246 @@ +/* Collection of functions useful for debugging + broken nix expressions. + + * `trace`-like functions take two values, print + the first to stderr and return the second. + * `traceVal`-like functions take one argument + which both printed and returned. + * `traceSeq`-like functions fully evaluate their + traced value before printing (not just to “weak + head normal form” like trace does by default). + * Functions that end in `-Fn` take an additional + function as their first argument, which is applied + to the traced value before it is printed. +*/ +{ lib }: +let + inherit (lib) + isList + isAttrs + substring + attrValues + concatLists + const + elem + generators + id + mapAttrs + trace; +in + +rec { + + # -- TRACING -- + + /* Conditionally trace the supplied message, based on a predicate. + + Type: traceIf :: bool -> string -> a -> a + + Example: + traceIf true "hello" 3 + trace: hello + => 3 + */ + traceIf = + # Predicate to check + pred: + # Message that should be traced + msg: + # Value to return + x: if pred then trace msg x else x; + + /* Trace the supplied value after applying a function to it, and + return the original value. + + Type: traceValFn :: (a -> b) -> a -> a + + Example: + traceValFn (v: "mystring ${v}") "foo" + trace: mystring foo + => "foo" + */ + traceValFn = + # Function to apply + f: + # Value to trace and return + x: trace (f x) x; + + /* Trace the supplied value and return it. + + Type: traceVal :: a -> a + + Example: + traceVal 42 + # trace: 42 + => 42 + */ + traceVal = traceValFn id; + + /* `builtins.trace`, but the value is `builtins.deepSeq`ed first. + + Type: traceSeq :: a -> b -> b + + Example: + trace { a.b.c = 3; } null + trace: { a = ; } + => null + traceSeq { a.b.c = 3; } null + trace: { a = { b = { c = 3; }; }; } + => null + */ + traceSeq = + # The value to trace + x: + # The value to return + y: trace (builtins.deepSeq x x) y; + + /* Like `traceSeq`, but only evaluate down to depth n. + This is very useful because lots of `traceSeq` usages + lead to an infinite recursion. + + Example: + traceSeqN 2 { a.b.c = 3; } null + trace: { a = { b = {…}; }; } + => null + + Type: traceSeqN :: Int -> a -> b -> b + */ + traceSeqN = depth: x: y: + let snip = v: if isList v then noQuotes "[…]" v + else if isAttrs v then noQuotes "{…}" v + else v; + noQuotes = str: v: { __pretty = const str; val = v; }; + modify = n: fn: v: if (n == 0) then fn v + else if isList v then map (modify (n - 1) fn) v + else if isAttrs v then mapAttrs + (const (modify (n - 1) fn)) v + else v; + in trace (generators.toPretty { allowPrettyValues = true; } + (modify depth snip x)) y; + + /* A combination of `traceVal` and `traceSeq` that applies a + provided function to the value to be traced after `deepSeq`ing + it. + */ + traceValSeqFn = + # Function to apply + f: + # Value to trace + v: traceValFn f (builtins.deepSeq v v); + + /* A combination of `traceVal` and `traceSeq`. */ + traceValSeq = traceValSeqFn id; + + /* A combination of `traceVal` and `traceSeqN` that applies a + provided function to the value to be traced. */ + traceValSeqNFn = + # Function to apply + f: + depth: + # Value to trace + v: traceSeqN depth (f v) v; + + /* A combination of `traceVal` and `traceSeqN`. */ + traceValSeqN = traceValSeqNFn id; + + /* Trace the input and output of a function `f` named `name`, + both down to `depth`. + + This is useful for adding around a function call, + to see the before/after of values as they are transformed. + + Example: + traceFnSeqN 2 "id" (x: x) { a.b.c = 3; } + trace: { fn = "id"; from = { a.b = {…}; }; to = { a.b = {…}; }; } + => { a.b.c = 3; } + */ + traceFnSeqN = depth: name: f: v: + let res = f v; + in lib.traceSeqN + (depth + 1) + { + fn = name; + from = v; + to = res; + } + res; + + + # -- TESTING -- + + /* Evaluates a set of tests. + + A test is an attribute set `{expr, expected}`, + denoting an expression and its expected result. + + The result is a `list` of __failed tests__, each represented as + `{name, expected, result}`, + + - expected + - What was passed as `expected` + - result + - The actual `result` of the test + + Used for regression testing of the functions in lib; see + tests.nix for more examples. + + Important: Only attributes that start with `test` are executed. + + - If you want to run only a subset of the tests add the attribute `tests = ["testName"];` + + Example: + + runTests { + testAndOk = { + expr = lib.and true false; + expected = false; + }; + testAndFail = { + expr = lib.and true false; + expected = true; + }; + } + -> + [ + { + name = "testAndFail"; + expected = true; + result = false; + } + ] + + Type: + runTests :: { + tests = [ String ]; + ${testName} :: { + expr :: a; + expected :: a; + }; + } + -> + [ + { + name :: String; + expected :: a; + result :: a; + } + ] + */ + runTests = + # Tests to run + tests: concatLists (attrValues (mapAttrs (name: test: + let testsToRun = if tests ? tests then tests.tests else []; + in if (substring 0 4 name == "test" || elem name testsToRun) + && ((testsToRun == []) || elem name tests.tests) + && (test.expr != test.expected) + + then [ { inherit name; expected = test.expected; result = test.expr; } ] + else [] ) tests)); + + /* Create a test assuming that list elements are `true`. + + Example: + { testX = allTrue [ true ]; } + */ + testAllTrue = expr: { inherit expr; expected = map (x: true) expr; }; +}