diff --git a/flake.nix b/flake.nix index f94c214..faf8ced 100644 --- a/flake.nix +++ b/flake.nix @@ -32,14 +32,14 @@ default = nixite.serve self.packages.${system}.raw; - test = let test = import ./testing/import.nix; - in pkgs.writeShellScriptBin "test" '' - ${test ./testing/md.test.nix} - ${test ./testing/html.test.nix} - ${test ./testing/elems.test.nix} - ${test ./testing/site.test.nix} - ${test ./testing/style.test.nix} - ''; + test = let run = import ./testing/run.nix pkgs; + in run [ + #./testing/md.test.nix + ./testing/html.test.nix + #./testing/elems.test.nix + #./testing/site.test.nix + ./testing/style.test.nix + ]; }; }; } diff --git a/nixite/html.nix b/nixite/html.nix index eceb752..6945a7a 100644 --- a/nixite/html.nix +++ b/nixite/html.nix @@ -1,7 +1,9 @@ let keyvalue = key: value: assert builtins.isString key; - if value == "" || value == [ ] || value == { } then + if builtins.isAttrs value then + builtins.trace "Skipping ${key} as it is a set" "" else + if value == "" || value == [ ] then "" else ''${key}="${toString value}"''; @@ -26,11 +28,27 @@ in rec { else keyvalue key value) attrs))); - tag = tag: attrs: child: { - inherit tag attrs child; - __toString = toHTML; + tag = tag: { + inherit tag; + __functor = self: attrs: + if !(builtins.isAttrs attrs) then + throw "HTML tag requires attribute set" + else { + inherit attrs; + __functor = self: child: + if !(isTag child) then + throw "tag child must be tag, list, or string" + else { + inherit tag attrs child; + __toString = toHTML; + }; + }; }; + isTag = tag: + (builtins.isString tag || builtins.isList tag + || (tag ? toString && builtins.isFunction tag.toString)); + addToHead = page: heads: page // { child = map diff --git a/nixite/style.nix b/nixite/style.nix index 8c9acb5..7efab3b 100644 --- a/nixite/style.nix +++ b/nixite/style.nix @@ -2,25 +2,40 @@ let html = import ./html.nix; join = { - __functor = self: new: self // new // { style = self.style + new.style; }; + __functor = self: new: self // new // { style = self.style // new.style; }; }; - mkStyle = identifier: styles: - if styles == { } then + mkStyle = identifier: styles: { + ${identifier} = styles; + __toString = self: + toString (builtins.attrValues (builtins.mapAttrs styleToString self)); + }; + + styleToString = identifier: styles: + if !builtins.isAttrs styles then "" - else '' - ${identifier} { - ${ - toString (builtins.attrValues - (builtins.mapAttrs (key: value: "${key}: ${value};") styles)) + else if styles == { } then + "" + else + assert builtins.isString identifier; '' + ${identifier} { + ${ + toString (builtins.attrValues + (builtins.mapAttrs (key: value: "${key}: ${value};") styles)) + } } - } - ''; + ''; mkIdentifier = name: tag: - { class ? [ name ], id ? "", ... }: - "${tag}" + builtins.concatStringsSep "" (map (c: "." + c) class) + assert builtins.isString name; + let + elem = if builtins.isString tag then tag else tag.tag or ""; + inheritClass = + if builtins.isString tag then [ ] else tag.attrs.class or [ ]; + in { class ? [ name ] ++ inheritClass, id ? "", ... }: + "${elem}" + builtins.concatStringsSep "" (map (c: "." + c) class) + (if id != "" then "#" else "") + id; + in { styled = name: tag: cprops: assert builtins.isString name; @@ -36,9 +51,9 @@ in { if cprops.class == [ ] then [ ] else [ name ] ++ cprops.class else [ name ]; - }) { style = ""; }; + }); - self = if builtins.isFunction tag then + self = if tag ? __functor && builtins.isFunction tag.__functor then props: tag (joinProps props) else if builtins.isString tag then props: html.tag tag (joinProps props) @@ -54,8 +69,14 @@ in { (self { } (__child props)) else throw "Call element with attributes and child.")) self; + in { - ${name} = __self; + ${name} = { + tag = (if builtins.isString tag then tag else tag.tag); + attrs = + joinProps (if builtins.isString tag then { } else tag.attrs or { }); + __functor = self: __self; + }; style = mkStyle (mkIdentifier name tag cprops) (cprops.style or { }); } // join; diff --git a/testing/html.test.nix b/testing/html.test.nix index 9dcbdef..009055b 100644 --- a/testing/html.test.nix +++ b/testing/html.test.nix @@ -2,6 +2,19 @@ let html = import ../nixite/html.nix; it = import ./it.nix; in with html; [ + + (it "keeps info in the tag" (let p = tag "p"; + in { + actual = p.tag; + expected = "p"; + })) + + (it "keeps attr info in the tag" (let p = tag "p" { class = ""; }; + in { + actual = p.attrs; + expected = { class = ""; }; + })) + (it "makes a p tag" { actual = tag "p" { } "Hello"; expected = { diff --git a/testing/run.nix b/testing/run.nix new file mode 100644 index 0000000..0e54bb7 --- /dev/null +++ b/testing/run.nix @@ -0,0 +1,5 @@ +pkgs: +let test = import ./import.nix; +in files: +(pkgs.writeShellScriptBin "test" + (builtins.concatStringsSep "\n" (map test files))) diff --git a/testing/style.test.nix b/testing/style.test.nix index 0e9f31b..950e42f 100644 --- a/testing/style.test.nix +++ b/testing/style.test.nix @@ -3,55 +3,61 @@ let html = import ../nixite/html.nix; it = import ./it.nix; - p = style.styled "generic" "p" { - foo = "bar"; - forgetme = "nothing"; - }; + my = (style.styled "div" "div" { + class = [ "something" ]; + style = { this = "that"; }; + }) (style.styled "s" "div" { + id = "s"; + class = [ "something" ]; + style = { s = "yes"; }; + }) (style.styled "foobar" "div" { + class = [ "foo" "bar" ]; + style = { something = "something"; }; + }) (style.style "body" { foo = "bar"; }) (style.styled "list" "ul" { + __child = child: + assert builtins.isList child; + (map (html.tag "li" { }) child); + }); +in [ - my = (style.styled "p" "p" { style = { some-style = "some value"; }; }) - (style.styled "classless" "div" { class = [ ]; }) - (style.styled "quote" p.generic { + (it "makes a p component" (let + my = (style.styled "para" "p" { style = { some-style = "some value"; }; }); + in { + expected = html.tag "p" { class = [ "para" ]; } "yes"; + actual = my.para { } "yes"; + asString = true; + })) + + (it "extends existing components" (let + my = (style.styled "generic" "p" { + foo = "bar"; + forgetme = "nothing"; + }); + this = (style.styled "quote" my.generic { baz = "baz"; forgetme = "forgotten"; - }) (style.styled "div" "div" { - class = [ "something" ]; - style = { this = "that"; }; - }) (style.styled "s" "div" { - id = "s"; - class = [ "something" ]; - style = { s = "yes"; }; - }) (style.styled "foobar" "div" { - class = [ "foo" "bar" ]; - style = { something = "something"; }; - }) (style.style "body" { foo = "bar"; }) (style.styled "list" "ul" { - __child = child: - assert builtins.isList child; - (map (html.tag "li" { }) child); }); -in [ - (it "extends existing components" { + in { expected = html.tag "p" { forgetme = "forgotten"; baz = "baz"; foo = "bar"; class = [ "generic" "quote" ]; } "yes"; - actual = my.quote { } "yes"; + actual = this.quote { } "yes"; asString = true; - }) - (it "makes a p component" { - expected = html.tag "p" { class = [ "p" ]; } "yes"; - actual = my.p { } "yes"; - asString = true; - }) - (it "makes a component with no class" { - expected = html.tag "div" { class = [ ]; } "yes"; - actual = my.classless { } "yes"; - asString = true; - }) + })) + + (it "makes a component with no class" + (let my = (style.styled "classless" "div" { class = [ ]; }); + in { + expected = html.tag "div" { class = [ ]; } "yes"; + actual = my.classless { } "yes"; + asString = true; + })) (it "does not error without attrs" { - expected = html.tag "p" { class = [ "p" ]; } "yes"; - actual = my.p "yes"; + expected = html.tag "div" { class = [ "div" "something" ]; } "yes"; + actual = my.div "yes"; asString = true; }) (it "makes a component" { @@ -88,24 +94,46 @@ in [ actual = my.div { id = "foo"; } "foobar"; asString = true; }) - (it "makes a style" { - expected = '' - p.p { - some-style: some value; - } - div.something { - this: that; - } - div.something#s { - s: yes; - } - div.foo.bar { - something: something; - } - body { - foo: bar; - } - ''; - actual = my.style; - }) + (it "makes a style" + (let my = (style.styled "para" "p" { style = { foo = "bar"; }; }); + in { + expected = { "p.para" = { foo = "bar"; }; }; + actual = removeAttrs my.style [ "__toString" ]; + })) + + (it "retains tag" + (let p = (style.styled "para" "p" { style = { foo = "bar"; }; }); + in { + expected = "p"; + actual = p.para.tag; + })) + + (it "retains attrs" + (let p = (style.styled "para" "p" { style = { foo = "bar"; }; }); + in { + expected = { foo = "bar"; }; + actual = p.para.attrs.style; + })) + + (it "retains class" (let p = (style.styled "para" "p" { }); + in { + expected = [ "para" ]; + actual = p.para.attrs.class; + })) + + (it "merges styles" (let + p = (style.styled "para" "p" { style = { foo = "bar"; }; }); + d = (style.styled "para2" p.para { style = { baz = "bar"; }; }); + my = p d; + in { + expected = { + "p.para" = { foo = "bar"; }; + "p.para2.para" = { + foo = "bar"; + baz = "bar"; + }; + }; + actual = removeAttrs my.style [ "__toString" ]; + })) + ]