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>
		
			
				
	
	
		
			122 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			122 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
# Copyright © 2021 sterni
 | 
						|
# SPDX-License-Identifier: MIT
 | 
						|
#
 | 
						|
# This file provides a cursed HTML DSL for nix which works by overloading
 | 
						|
# the NIX_PATH lookup operation via angle bracket operations, e. g. `<nixpkgs>`.
 | 
						|
 | 
						|
{ ... }:
 | 
						|
 | 
						|
let
 | 
						|
  /* Escape everything we have to escape in an HTML document if either
 | 
						|
     in a normal context or an attribute string (`<>&"'`).
 | 
						|
 | 
						|
     A shorthand for this function called `esc` is also provided.
 | 
						|
 | 
						|
     Type: string -> string
 | 
						|
 | 
						|
     Example:
 | 
						|
 | 
						|
     escapeMinimal "<hello>"
 | 
						|
     => "<hello>"
 | 
						|
  */
 | 
						|
  escapeMinimal = builtins.replaceStrings
 | 
						|
    [ "<" ">" "&" "\"" "'" ]
 | 
						|
    [ "<" ">" "&" """ "'" ];
 | 
						|
 | 
						|
  /* Return a string with a correctly rendered tag of the given name,
 | 
						|
     with the given attributes which are automatically escaped.
 | 
						|
 | 
						|
     If the content argument is `null`, the tag will have no children nor a
 | 
						|
     closing element. If the content argument is a string it is used as the
 | 
						|
     content as is (unescaped). If the content argument is a list, its
 | 
						|
     elements are concatenated.
 | 
						|
 | 
						|
     `renderTag` is only an internal function which is reexposed as `__findFile`
 | 
						|
     to allow for much neater syntax than calling `renderTag` everywhere:
 | 
						|
 | 
						|
     ```nix
 | 
						|
     { depot, ... }:
 | 
						|
     let
 | 
						|
       inherit (depot.users.sterni.nix.html) __findFile esc;
 | 
						|
     in
 | 
						|
 | 
						|
     <html> {} [
 | 
						|
       (<head> {} (<title> {} (esc "hello world")))
 | 
						|
       (<body> {} [
 | 
						|
         (<h1> {} (esc "hello world"))
 | 
						|
         (<p> {} (esc "foo bar"))
 | 
						|
       ])
 | 
						|
     ]
 | 
						|
 | 
						|
     ```
 | 
						|
 | 
						|
     As you can see, the need to call a function disappears, instead the
 | 
						|
     `NIX_PATH` lookup operation via `<foo>` is overloaded, so it becomes
 | 
						|
     `renderTag "foo"` automatically.
 | 
						|
 | 
						|
     Since the content argument may contain the result of other `renderTag`
 | 
						|
     calls, we can't escape it automatically. Instead this must be done manually
 | 
						|
     using `esc`.
 | 
						|
 | 
						|
     Type: string -> attrs<string> -> (list<string> | string | null) -> string
 | 
						|
 | 
						|
     Example:
 | 
						|
 | 
						|
     <link> {
 | 
						|
       rel = "stylesheet";
 | 
						|
       href = "/css/main.css";
 | 
						|
       type = "text/css";
 | 
						|
     } null
 | 
						|
 | 
						|
     renderTag "link" {
 | 
						|
       rel = "stylesheet";
 | 
						|
       href = "/css/main.css";
 | 
						|
       type = "text/css";
 | 
						|
     } null
 | 
						|
 | 
						|
     => "<link href=\"/css/main.css\" rel=\"stylesheet\" type=\"text/css\"/>"
 | 
						|
 | 
						|
     <p> {} [
 | 
						|
       "foo "
 | 
						|
       (<strong> {} "bar")
 | 
						|
     ]
 | 
						|
 | 
						|
     renderTag "p" {} "foo <strong>bar</strong>"
 | 
						|
     => "<p>foo <strong>bar</strong></p>"
 | 
						|
  */
 | 
						|
  renderTag = tag: attrs: content:
 | 
						|
    let
 | 
						|
      attrs' = builtins.concatStringsSep "" (
 | 
						|
        builtins.map
 | 
						|
          (n:
 | 
						|
            " ${escapeMinimal n}=\"${escapeMinimal (toString attrs.${n})}\""
 | 
						|
          )
 | 
						|
          (builtins.attrNames attrs)
 | 
						|
      );
 | 
						|
      content' =
 | 
						|
        if builtins.isList content
 | 
						|
        then builtins.concatStringsSep "" content
 | 
						|
        else content;
 | 
						|
    in
 | 
						|
    if content == null
 | 
						|
    then "<${tag}${attrs'}/>"
 | 
						|
    else "<${tag}${attrs'}>${content'}</${tag}>";
 | 
						|
 | 
						|
  /* Prepend "<!DOCTYPE html>" to a string.
 | 
						|
 | 
						|
     Type: string -> string
 | 
						|
 | 
						|
     Example:
 | 
						|
 | 
						|
     withDoctype (<body> {} (esc "hello"))
 | 
						|
     => "<!DOCTYPE html><body>hello</body>"
 | 
						|
  */
 | 
						|
  withDoctype = doc: "<!DOCTYPE html>" + doc;
 | 
						|
 | 
						|
in
 | 
						|
{
 | 
						|
  inherit escapeMinimal renderTag withDoctype;
 | 
						|
 | 
						|
  __findFile = _: renderTag;
 | 
						|
  esc = escapeMinimal;
 | 
						|
}
 |