├── .gitignore
├── src
├── main.roc
├── SafeStr.roc
├── Attribute.roc
└── Html.roc
├── examples
├── .gitignore
└── example.roc
├── .github
├── dependabot.yaml
├── release-drafter.yaml
└── workflows
│ ├── test.yaml
│ ├── bundle.yaml
│ ├── update-draft-release.yaml
│ ├── check-pr-labels.yaml
│ ├── generate-docs.yaml
│ └── run-pre-commit.yaml
├── Justfile
├── README.md
├── flake.nix
├── LICENCE
├── .pre-commit-config.yaml
└── flake.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | generated-docs/
2 |
--------------------------------------------------------------------------------
/src/main.roc:
--------------------------------------------------------------------------------
1 | package [Html, Attribute] {}
2 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 | !*.roc
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | updates:
4 | - package-ecosystem: github-actions
5 | # Look for workflow files in .github/workflows/
6 | directory: /
7 | # Check for updates once a month
8 | schedule:
9 | interval: monthly
10 |
--------------------------------------------------------------------------------
/Justfile:
--------------------------------------------------------------------------------
1 | default: format check test examples docs
2 |
3 | format:
4 | roc format src/
5 | roc format examples/
6 |
7 | check:
8 | roc check src/main.roc
9 | fd --extension roc . examples/ --exec roc check
10 |
11 | test:
12 | roc test src/main.roc
13 |
14 | examples:
15 | fd --extension roc . examples/ --exec roc run
16 |
17 | docs:
18 | roc docs src/main.roc
19 |
20 | ratchet:
21 | ratchet upgrade .github/workflows/*.yaml
22 |
--------------------------------------------------------------------------------
/.github/release-drafter.yaml:
--------------------------------------------------------------------------------
1 | # The name of the release
2 | name-template: v$RESOLVED_VERSION
3 | # The tag of the release
4 | tag-template: v$RESOLVED_VERSION
5 | # Template for category headings
6 | category-template: "### $TITLE"
7 | # Template for each individual change
8 | change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
9 | change-title-escapes: \<*_&
10 | categories:
11 | - title: Features
12 | label: feature
13 | - title: Bug fixes
14 | labels: fix
15 | - title: Maintenance
16 | label: chore
17 | version-resolver:
18 | major:
19 | labels:
20 | - major
21 | minor:
22 | labels:
23 | - minor
24 | patch:
25 | labels:
26 | - patch
27 | default: patch # Default increment if no PRs are found
28 | template: |
29 | ## Changelog
30 |
31 | $CHANGES
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | # Run on all PRs
5 | pull_request:
6 | paths-ignore:
7 | - "**.md"
8 | # Run when a PR is merged into main
9 | push:
10 | branches:
11 | - main
12 | paths-ignore:
13 | - "**.md"
14 |
15 | jobs:
16 | test:
17 | name: Test
18 | runs-on: ubuntu-latest
19 | permissions:
20 | contents: write
21 | steps:
22 | - name: Check out the repository
23 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # ratchet:actions/checkout@v5
24 | - name: Install Roc
25 | uses: hasnep/setup-roc@9866c6fdc971ee9f4b3eeba03d825dc32a5efa7f # ratchet:hasnep/setup-roc@v0.5.0
26 | with:
27 | roc-version: 0.0.0-alpha2-rolling
28 | - name: Test the library
29 | run: roc test src/main.roc
30 |
--------------------------------------------------------------------------------
/.github/workflows/bundle.yaml:
--------------------------------------------------------------------------------
1 | name: Bundle
2 |
3 | on:
4 | # Run when a release is published
5 | release:
6 | types:
7 | - published
8 |
9 | jobs:
10 | bundle:
11 | name: Bundle
12 | runs-on: ubuntu-latest
13 | permissions:
14 | contents: write
15 | steps:
16 | - name: Check out the repository
17 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # ratchet:actions/checkout@v5
18 | - name: Install Roc
19 | uses: hasnep/setup-roc@9866c6fdc971ee9f4b3eeba03d825dc32a5efa7f # ratchet:hasnep/setup-roc@v0.5.0
20 | with:
21 | roc-version: 0.0.0-alpha2-rolling
22 | - name: Bundle and release the library
23 | uses: hasnep/bundle-roc-library@4364d15b4ae83c99e0bc0caab8a254a5d0a9369f # ratchet:hasnep/bundle-roc-library@v0.1.0
24 | with:
25 | library: src/main.roc
26 | token: ${{ github.token }}
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Roc HTML
2 |
3 | A library to create HTML in Roc.
4 |
5 | ## Example
6 |
7 | This Roc code:
8 |
9 | ```roc
10 | Html.html [] [
11 | Html.body [] [
12 | Html.h1 [] [Html.text "Roc"],
13 | Html.p [] [
14 | Html.text "You should really check out ",
15 | Html.a [Attribute.href "https://roc-lang.org/"] [Html.text "Roc"],
16 | Html.text "!",
17 | ]
18 | ]
19 | ] |> Html.render
20 | ```
21 |
22 | Returns this HTML (give or take a little formatting):
23 |
24 | ```html
25 |
26 |
27 |
28 | Roc
29 | You should really check out Roc!
30 |
31 |
32 | ```
33 |
34 | ## Licence
35 |
36 | This repository is released under the [UPL licence](./LICENCE) and was mostly written by [Brian Carroll](https://github.com/brian-carroll/). Thanks, Brian!
37 |
--------------------------------------------------------------------------------
/.github/workflows/update-draft-release.yaml:
--------------------------------------------------------------------------------
1 | name: Update draft release
2 |
3 | on:
4 | # Run when a PR is merged into main
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | update-draft-release:
11 | name: Update draft release
12 | permissions:
13 | # Permissions required for creating a GitHub release
14 | contents: write
15 | pull-requests: read
16 | runs-on: ubuntu-latest
17 | outputs:
18 | tag_name: ${{ steps.update-draft-release.outputs.tag_name }}
19 | steps:
20 | - name: Update the draft release
21 | id: update-draft-release
22 | uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # ratchet:release-drafter/release-drafter@v6
23 | with:
24 | config-name: release-drafter.yaml
25 | disable-autolabeler: true
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/examples/example.roc:
--------------------------------------------------------------------------------
1 | app [main!] {
2 | cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.19.0/Hj-J_zxz7V9YurCSTFcFdu6cQJie4guzsPMUi5kBYUk.tar.br",
3 | html: "../src/main.roc",
4 | }
5 |
6 | import cli.Stdout
7 | import html.Html
8 | import html.Attribute
9 |
10 | main! = |_args|
11 | page = Html.html(
12 | [],
13 | [
14 | Html.body(
15 | [],
16 | [
17 | Html.h1([], [Html.text("Roc")]),
18 | Html.p(
19 | [],
20 | [
21 | Html.text("My favourite language is "),
22 | Html.a([Attribute.href("https://roc-lang.org/")], [Html.text("Roc")]),
23 | Html.text("!"),
24 | ],
25 | ),
26 | ],
27 | ),
28 | ],
29 | )
30 | rendered_html = Html.render(page)
31 | Stdout.line!(rendered_html)
32 |
--------------------------------------------------------------------------------
/.github/workflows/check-pr-labels.yaml:
--------------------------------------------------------------------------------
1 | name: Check PR labels
2 |
3 | on:
4 | # Run on all PRs
5 | pull_request:
6 | types:
7 | - labeled
8 | - opened
9 | - reopened
10 | - synchronize
11 |
12 | jobs:
13 | check-pr-labels:
14 | name: Check PR labels
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Check for PR category labels
18 | uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # ratchet:yogevbd/enforce-label-action@2.2.2
19 | with:
20 | REQUIRED_LABELS_ANY: feature,fix,chore
21 | REQUIRED_LABELS_ANY_DESCRIPTION: Please tag your PR with one of `feature`, `fix`, or `chore`.
22 | - name: Check for PR version labels
23 | uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # ratchet:yogevbd/enforce-label-action@2.2.2
24 | with:
25 | REQUIRED_LABELS_ANY: major,minor,patch
26 | REQUIRED_LABELS_ANY_DESCRIPTION: Please tag your PR with one of `major`, `minor`, `patch`.
27 |
--------------------------------------------------------------------------------
/.github/workflows/generate-docs.yaml:
--------------------------------------------------------------------------------
1 | name: Generate docs
2 |
3 | on:
4 | # Run when a release is published
5 | release:
6 | types:
7 | - published
8 |
9 | jobs:
10 | generate-docs:
11 | name: Generate docs
12 | runs-on: ubuntu-latest
13 | permissions:
14 | pages: write
15 | id-token: write
16 | steps:
17 | - name: Check out the repository
18 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # ratchet:actions/checkout@v5
19 | - name: Install Roc
20 | uses: hasnep/setup-roc@9866c6fdc971ee9f4b3eeba03d825dc32a5efa7f # ratchet:hasnep/setup-roc@v0.5.0
21 | with:
22 | roc-version: 0.0.0-alpha2-rolling
23 | - name: Generate docs
24 | run: roc docs src/main.roc
25 | - name: Fix absolute paths
26 | run: |
27 | find generated-docs/ -type f -name '*.html' -exec sed -i "s/\(href\|src\)=\"\//\1=\"\/${{ github.event.repository.name }}\//g" {} +
28 | - name: Upload docs artifact
29 | uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # ratchet:actions/upload-pages-artifact@v4
30 | with:
31 | path: generated-docs
32 | - name: Deploy docs
33 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # ratchet:actions/deploy-pages@v4
34 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
4 | flake-parts.url = "github:hercules-ci/flake-parts";
5 | roc.url = "github:roc-lang/roc/alpha4-rolling";
6 | };
7 |
8 | nixConfig = {
9 | extra-trusted-public-keys = "roc-lang.cachix.org-1:6lZeqLP9SadjmUbskJAvcdGR2T5ViR57pDVkxJQb8R4=";
10 | extra-trusted-substituters = "https://roc-lang.cachix.org";
11 | };
12 |
13 | outputs =
14 | inputs@{ flake-parts, ... }:
15 | flake-parts.lib.mkFlake { inherit inputs; } {
16 | systems = [
17 | "aarch64-darwin"
18 | "aarch64-linux"
19 | "x86_64-darwin"
20 | "x86_64-linux"
21 | ];
22 | perSystem =
23 | { inputs', pkgs, ... }:
24 | {
25 | devShells.default = pkgs.mkShell {
26 | name = "roc-html";
27 | packages = [
28 | inputs'.roc.packages.cli
29 | ]
30 | # Pre-commit
31 | ++ [
32 | pkgs.actionlint
33 | pkgs.check-jsonschema
34 | pkgs.fd
35 | pkgs.just
36 | pkgs.nixfmt
37 | pkgs.pre-commit
38 | pkgs.prettier
39 | pkgs.python3Packages.pre-commit-hooks
40 | pkgs.ratchet
41 | ];
42 | shellHook = "pre-commit install --overwrite";
43 | };
44 | formatter = pkgs.nixfmt-tree;
45 | };
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/run-pre-commit.yaml:
--------------------------------------------------------------------------------
1 | name: Run Pre-commit
2 |
3 | on:
4 | # Run on all PRs
5 | pull_request:
6 | # Run when a PR is merged into main
7 | push:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | run-pre-commit:
13 | name: Run pre-commit
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Check out the repository
17 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # ratchet:actions/checkout@v5
18 | - name: Install Nix
19 | uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # ratchet:nixbuild/nix-quick-install-action@v34
20 | - name: Cache Nix environment
21 | uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # ratchet:nix-community/cache-nix-action@v6
22 | with:
23 | primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
24 | restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
25 | - name: Use Nix dev shell for subsequent steps
26 | uses: rrbutani/use-nix-shell-action@59a52b2b9bbfe3cc0e7deb8f9059abe37a439edf # ratchet:rrbutani/use-nix-shell-action@v1
27 | with:
28 | extraNixOptions: --accept-flake-config
29 | - name: Run Pre-commit
30 | run: pre-commit run --all-files
31 | env:
32 | # Prevent this action failing when running on the main branch
33 | SKIP: no-commit-to-branch
34 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023 Brian Carroll, Luke Boswell, Hannes Smit and Roc contributors
2 |
3 | The Universal Permissive License (UPL), Version 1.0
4 |
5 | Subject to the condition set forth below, permission is hereby granted to any
6 | person obtaining a copy of this software, associated documentation and/or data
7 | (collectively the "Software"), free of charge and under any and all copyright
8 | rights in the Software, and any and all patent rights owned or freely
9 | licensable by each licensor hereunder covering either (i) the unmodified
10 | Software as contributed to or provided by such licensor, or (ii) the Larger
11 | Works (as defined below), to deal in both
12 |
13 | (a) the Software, and
14 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15 | one is included with the Software (each a “Larger Work” to which the Software
16 | is contributed by such licensors),
17 |
18 | without restriction, including without limitation the rights to copy, create
19 | derivative works of, display, perform, and distribute the Software and make,
20 | use, sell, offer for sale, import, export, have made, and have sold the
21 | Software and the Larger Work(s), and to sublicense the foregoing rights on
22 | either these or other terms.
23 |
24 | This license is subject to the following condition:
25 | The above copyright notice and either this complete permission notice or at
26 | a minimum a reference to the UPL must be included in all copies or
27 | substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35 | SOFTWARE.
36 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | # General checks
3 | - repo: local
4 | hooks:
5 | - name: Prevent committing to main
6 | id: no-commit-to-branch
7 | language: system
8 | entry: no-commit-to-branch
9 | args: [--branch, main]
10 | pass_filenames: false
11 | - name: Make sure files end with a newline character
12 | id: end-of-file-fixer
13 | language: system
14 | entry: end-of-file-fixer
15 | types: [text]
16 | - name: Remove trailing whitespace
17 | id: trailing-whitespace-fixer
18 | language: system
19 | entry: trailing-whitespace-fixer
20 | types: [text]
21 | - name: Check for files that would conflict on case-insensitive filesystem
22 | id: check-case-conflict
23 | language: system
24 | entry: check-case-conflict
25 | - name: Check for merge conflicts
26 | id: check-merge-conflict
27 | language: system
28 | entry: check-merge-conflict
29 | - name: Check executable files have a shebang
30 | id: check-executables-have-shebangs
31 | language: system
32 | entry: check-executables-have-shebangs
33 | types: [executable]
34 | - name: Check scripts with a shebang are executable
35 | id: check-shebang-scripts-are-executable
36 | language: system
37 | entry: check-shebang-scripts-are-executable
38 | - name: Don't allow adding large files
39 | id: check-added-large-files
40 | language: system
41 | entry: check-added-large-files
42 |
43 | # Roc
44 | - repo: https://github.com/hasnep/pre-commit-roc
45 | rev: v0.1.0
46 | hooks:
47 | - name: Lint Roc files
48 | id: check
49 | args: [src/main.roc]
50 | - name: Format Roc files
51 | id: format
52 |
53 | # YAML
54 | - repo: local
55 | hooks:
56 | - name: Format YAML files
57 | id: yaml-format
58 | language: system
59 | entry: prettier --write
60 | types: [yaml]
61 |
62 | # Markdown
63 | - repo: local
64 | hooks:
65 | - name: Format markdown files
66 | id: markdown-format
67 | language: system
68 | entry: prettier --write
69 | types: [markdown]
70 |
71 | # GitHub Actions
72 | - repo: local
73 | hooks:
74 | - name: Validate GitHub Actions workflow files
75 | id: github-workflows-check
76 | language: system
77 | entry: actionlint
78 | types: [yaml]
79 | files: \.github/workflows/.*\.ya?ml$
80 | - name: Check GitHub Actions are pinned
81 | id: github-workflows-check-pinned
82 | language: system
83 | entry: ratchet check
84 | types: [yaml]
85 | files: \.github/workflows/.*\.ya?ml$
86 |
87 | # Nix
88 | - repo: local
89 | hooks:
90 | - name: Format Nix files
91 | id: nix-format
92 | language: system
93 | entry: nixfmt
94 | types: [nix]
95 |
--------------------------------------------------------------------------------
/src/SafeStr.roc:
--------------------------------------------------------------------------------
1 | module [
2 | SafeStr,
3 | to_str,
4 | escape,
5 | dangerously_mark_safe,
6 | with_capacity,
7 | concat,
8 | reserve,
9 | ]
10 |
11 | ## An opaque type used to keep track of the fact that we know a string is HTML-safe.
12 | SafeStr := Str
13 |
14 | ## Escape a string so that it can safely be used in HTML text nodes or attributes.
15 | ## This is the function that should usually be used for converting a Str to a SafeStr.
16 | escape : Str -> SafeStr
17 | escape = |str|
18 | (encoded_bytes, is_original_string_fine) =
19 | # We allocate more bytes than the original string had because we'll need extra bytes
20 | # if there are any characters we need to escape. My choice of the proportion 3/2
21 | # was arbitrary.
22 | capacity = Str.count_utf8_bytes(str) |> Num.div_ceil(2) |> Num.mul(3)
23 | bytes = List.with_capacity(capacity)
24 | # Look at each byte in the string. It's important to look at each codepoint, not the
25 | # whole graheme. (See the test below.) Because we're only looking for `"`, `&`, `'`,
26 | # `<`, and `>`, each of which are one byte, it's fine to just iterate over the bytes.
27 | # In UTF-8, the only bytes that ever start with a 0 are the single-byte codepoints, so
28 | # these bytes will never appear in the middle of a codepoint.
29 | Str.walk_utf8(
30 | str,
31 | (bytes, Bool.true),
32 | |(bytes_so_far, is_original_string_fine_so_far), byte|
33 | when byte is
34 | 34 -> (List.concat(bytes_so_far, Str.to_utf8(""")), Bool.false) # " must be escaped
35 | 38 -> (List.concat(bytes_so_far, Str.to_utf8("&")), Bool.false) # & must be escaped
36 | 39 -> (List.concat(bytes_so_far, Str.to_utf8("'")), Bool.false) # ' must be escaped
37 | 60 -> (List.concat(bytes_so_far, Str.to_utf8("<")), Bool.false) # < must be escaped
38 | 62 -> (List.concat(bytes_so_far, Str.to_utf8(">")), Bool.false) # > must be escaped
39 | _ -> (List.append(bytes_so_far, byte), is_original_string_fine_so_far),
40 | ) # All other bytes are fine!
41 | if is_original_string_fine then
42 | # If we didn't do any replacements, we might as well use the original string
43 | # so that Roc can free encodedBytes and avoid re-validating the UTF-8.
44 | @SafeStr(str)
45 | else
46 | when Str.from_utf8(encoded_bytes) is
47 | Ok(s) -> @SafeStr(s)
48 | Err(BadUtf8(_)) ->
49 | crash("SafeStr.escape: bad utf8. This should not be possible; please report this bug to roc-html.")
50 |
51 | ## Convert a SafeStr to a regular Str.
52 | to_str : SafeStr -> Str
53 | to_str = |@SafeStr(str)| str
54 |
55 | expect to_str(escape("abc
")) == "<h1>abc</h1>"
56 | expect to_str(escape("abc")) == "abc"
57 | expect to_str(escape("折り紙🕊")) == "折り紙🕊"
58 | expect to_str(escape("é é")) == "é é"
59 | expect to_str(escape("﷽ᄀᄀᄀ각ᆨᆨ🇺🇸각नीநி")) == "﷽ᄀᄀᄀ각ᆨᆨ🇺🇸각नीநி"
60 | expect to_str(escape("﷽&ᄀᄀᄀ각ᆨᆨ🇺🇸각नीநி")) == "﷽&ᄀᄀᄀ각ᆨᆨ🇺🇸각नीநி"
61 | expect to_str(escape("'&\"<>")) == "'&"<>"
62 | # Note: This sometimes displays incorrectly in VSCode. It's a family emoji.
63 | expect to_str(escape("👩👩👦👦")) == "👩👩👦👦"
64 | expect to_str(escape("&👩👩👦👦&")) == "&👩👩👦👦&"
65 | expect to_str(escape("`~!@#$%^&*()-=_+[]\\{}|;':\",./<>?")) == "`~!@#$%^&*()-=_+[]\\{}|;':",./<>?"
66 | # Even though this string doesn't contain the *grapheme* "<" or the grapheme ">",
67 | # it does contain the *codepoints* "<" and ">". The browser still interprets them
68 | # as HTML tags, so it's important to escape them.
69 | expect to_str(escape("something͏bad
͏something")) == "something<h1>͏bad</h1>͏something"
70 |
71 | ## Mark a string as safe for HTML without actually escaping it.
72 | ## DO NOT use this function unless you know the input string is safe.
73 | ## NEVER use this function on user input.
74 | dangerously_mark_safe : Str -> SafeStr
75 | dangerously_mark_safe = |str| @SafeStr(str)
76 |
77 | expect
78 | bad_str = ""
79 | to_str(dangerously_mark_safe(bad_str)) == bad_str
80 |
81 | ## The SafeStr equivalent of Str.withCapacity
82 | with_capacity : U64 -> SafeStr
83 | with_capacity = |capacity|
84 | @SafeStr(Str.with_capacity(capacity))
85 |
86 | expect to_str(with_capacity(10)) == ""
87 |
88 | ## The SafeStr equivalent of Str.reserve
89 | reserve : SafeStr, U64 -> SafeStr
90 | reserve = |@SafeStr(str), additional_capacity|
91 | @SafeStr(Str.reserve(str, additional_capacity))
92 |
93 | expect "abc>" |> escape |> reserve(50) |> to_str == "abc>"
94 |
95 | ## The SafeStr equivalent of Str.concat
96 | concat : SafeStr, SafeStr -> SafeStr
97 | concat = |@SafeStr(str1), @SafeStr(str2)|
98 | # If two strings are HTML-safe, their concatenation must be too.
99 | @SafeStr(Str.concat(str1, str2))
100 |
101 | expect escape("3>2") |> concat(escape("2<3")) |> to_str == "3>22<3"
102 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1733328505,
7 | "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "flake-parts": {
20 | "inputs": {
21 | "nixpkgs-lib": "nixpkgs-lib"
22 | },
23 | "locked": {
24 | "lastModified": 1760948891,
25 | "narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
26 | "owner": "hercules-ci",
27 | "repo": "flake-parts",
28 | "rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
29 | "type": "github"
30 | },
31 | "original": {
32 | "owner": "hercules-ci",
33 | "repo": "flake-parts",
34 | "type": "github"
35 | }
36 | },
37 | "flake-utils": {
38 | "inputs": {
39 | "systems": "systems"
40 | },
41 | "locked": {
42 | "lastModified": 1731533236,
43 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
44 | "owner": "numtide",
45 | "repo": "flake-utils",
46 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
47 | "type": "github"
48 | },
49 | "original": {
50 | "owner": "numtide",
51 | "repo": "flake-utils",
52 | "type": "github"
53 | }
54 | },
55 | "nixpkgs": {
56 | "locked": {
57 | "lastModified": 1761373498,
58 | "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=",
59 | "owner": "nixos",
60 | "repo": "nixpkgs",
61 | "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce",
62 | "type": "github"
63 | },
64 | "original": {
65 | "owner": "nixos",
66 | "ref": "nixos-unstable",
67 | "repo": "nixpkgs",
68 | "type": "github"
69 | }
70 | },
71 | "nixpkgs-lib": {
72 | "locked": {
73 | "lastModified": 1754788789,
74 | "narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
75 | "owner": "nix-community",
76 | "repo": "nixpkgs.lib",
77 | "rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
78 | "type": "github"
79 | },
80 | "original": {
81 | "owner": "nix-community",
82 | "repo": "nixpkgs.lib",
83 | "type": "github"
84 | }
85 | },
86 | "nixpkgs_2": {
87 | "locked": {
88 | "lastModified": 1722403750,
89 | "narHash": "sha256-tRmn6UiFAPX0m9G1AVcEPjWEOc9BtGsxGcs7Bz3MpsM=",
90 | "owner": "nixos",
91 | "repo": "nixpkgs",
92 | "rev": "184957277e885c06a505db112b35dfbec7c60494",
93 | "type": "github"
94 | },
95 | "original": {
96 | "owner": "nixos",
97 | "repo": "nixpkgs",
98 | "rev": "184957277e885c06a505db112b35dfbec7c60494",
99 | "type": "github"
100 | }
101 | },
102 | "roc": {
103 | "inputs": {
104 | "flake-compat": "flake-compat",
105 | "flake-utils": "flake-utils",
106 | "nixpkgs": "nixpkgs_2",
107 | "rust-overlay": "rust-overlay"
108 | },
109 | "locked": {
110 | "lastModified": 1757329258,
111 | "narHash": "sha256-pPnOM4hpbAkGCV47aw5eHbpOujjFtJa3v/3/D8gybO8=",
112 | "owner": "roc-lang",
113 | "repo": "roc",
114 | "rev": "d73ea109cc21442da01387c1e5e911607c74692d",
115 | "type": "github"
116 | },
117 | "original": {
118 | "owner": "roc-lang",
119 | "ref": "alpha4-rolling",
120 | "repo": "roc",
121 | "type": "github"
122 | }
123 | },
124 | "root": {
125 | "inputs": {
126 | "flake-parts": "flake-parts",
127 | "nixpkgs": "nixpkgs",
128 | "roc": "roc"
129 | }
130 | },
131 | "rust-overlay": {
132 | "inputs": {
133 | "nixpkgs": [
134 | "roc",
135 | "nixpkgs"
136 | ]
137 | },
138 | "locked": {
139 | "lastModified": 1736303309,
140 | "narHash": "sha256-IKrk7RL+Q/2NC6+Ql6dwwCNZI6T6JH2grTdJaVWHF0A=",
141 | "owner": "oxalica",
142 | "repo": "rust-overlay",
143 | "rev": "a0b81d4fa349d9af1765b0f0b4a899c13776f706",
144 | "type": "github"
145 | },
146 | "original": {
147 | "owner": "oxalica",
148 | "repo": "rust-overlay",
149 | "type": "github"
150 | }
151 | },
152 | "systems": {
153 | "locked": {
154 | "lastModified": 1681028828,
155 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
156 | "owner": "nix-systems",
157 | "repo": "default",
158 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
159 | "type": "github"
160 | },
161 | "original": {
162 | "owner": "nix-systems",
163 | "repo": "default",
164 | "type": "github"
165 | }
166 | }
167 | },
168 | "root": "root",
169 | "version": 7
170 | }
171 |
--------------------------------------------------------------------------------
/src/Attribute.roc:
--------------------------------------------------------------------------------
1 | module [
2 | Attribute,
3 | attribute,
4 | accept,
5 | accept_charset,
6 | accesskey,
7 | action,
8 | align,
9 | allow,
10 | alt,
11 | async,
12 | autocapitalize,
13 | autocomplete,
14 | autofocus,
15 | autoplay,
16 | background,
17 | bgcolor,
18 | border,
19 | buffered,
20 | capture,
21 | challenge,
22 | charset,
23 | checked,
24 | cite,
25 | class,
26 | classes,
27 | code,
28 | codebase,
29 | color,
30 | cols,
31 | colspan,
32 | content,
33 | contenteditable,
34 | contextmenu,
35 | controls,
36 | coords,
37 | crossorigin,
38 | csp,
39 | data,
40 | datetime,
41 | decoding,
42 | default,
43 | defer,
44 | dir,
45 | dirname,
46 | disabled,
47 | download,
48 | draggable,
49 | enctype,
50 | enterkeyhint,
51 | for,
52 | form,
53 | formaction,
54 | formenctype,
55 | formmethod,
56 | formnovalidate,
57 | formtarget,
58 | headers,
59 | height,
60 | hidden,
61 | high,
62 | href,
63 | hreflang,
64 | http_equiv,
65 | icon,
66 | id,
67 | importance,
68 | inputmode,
69 | integrity,
70 | intrinsicsize,
71 | ismap,
72 | itemprop,
73 | keytype,
74 | kind,
75 | label,
76 | lang,
77 | language,
78 | list,
79 | loading,
80 | loop,
81 | low,
82 | manifest,
83 | max,
84 | maxlength,
85 | media,
86 | method,
87 | min,
88 | minlength,
89 | multiple,
90 | muted,
91 | name,
92 | novalidate,
93 | open,
94 | optimum,
95 | pattern,
96 | ping,
97 | placeholder,
98 | poster,
99 | preload,
100 | radiogroup,
101 | readonly,
102 | referrerpolicy,
103 | rel,
104 | required,
105 | reversed,
106 | role,
107 | rows,
108 | rowspan,
109 | sandbox,
110 | scope,
111 | scoped,
112 | selected,
113 | shape,
114 | size,
115 | sizes,
116 | slot,
117 | span,
118 | spellcheck,
119 | src,
120 | srcdoc,
121 | srclang,
122 | srcset,
123 | start,
124 | step,
125 | style,
126 | summary,
127 | tabindex,
128 | target,
129 | title,
130 | translate,
131 | type,
132 | usemap,
133 | value,
134 | width,
135 | wrap,
136 | ]
137 |
138 | Attribute : [Attribute Str Str]
139 |
140 | ## Define a non-standard attribute.
141 | ## You can use this to add attributes that are not already supported.
142 | attribute : Str -> (Str -> Attribute)
143 | attribute = |attr_name|
144 | |attr_value| Attribute(attr_name, attr_value)
145 |
146 | ## Construct a `accept` attribute.
147 | accept : Str -> Attribute
148 | accept = attribute("accept")
149 |
150 | ## Construct a `accept-charset` attribute.
151 | accept_charset : Str -> Attribute
152 | accept_charset = attribute("accept-charset")
153 |
154 | ## Construct a `accesskey` attribute.
155 | accesskey : Str -> Attribute
156 | accesskey = attribute("accesskey")
157 |
158 | ## Construct a `action` attribute.
159 | action : Str -> Attribute
160 | action = attribute("action")
161 |
162 | ## Construct a `align` attribute.
163 | align : Str -> Attribute
164 | align = attribute("align")
165 |
166 | ## Construct a `allow` attribute.
167 | allow : Str -> Attribute
168 | allow = attribute("allow")
169 |
170 | ## Construct a `alt` attribute.
171 | alt : Str -> Attribute
172 | alt = attribute("alt")
173 |
174 | ## Construct a `async` attribute.
175 | async : Str -> Attribute
176 | async = attribute("async")
177 |
178 | ## Construct a `autocapitalize` attribute.
179 | autocapitalize : Str -> Attribute
180 | autocapitalize = attribute("autocapitalize")
181 |
182 | ## Construct a `autocomplete` attribute.
183 | autocomplete : Str -> Attribute
184 | autocomplete = attribute("autocomplete")
185 |
186 | ## Construct a `autofocus` attribute.
187 | autofocus : Str -> Attribute
188 | autofocus = attribute("autofocus")
189 |
190 | ## Construct a `autoplay` attribute.
191 | autoplay : Str -> Attribute
192 | autoplay = attribute("autoplay")
193 |
194 | ## Construct a `background` attribute.
195 | background : Str -> Attribute
196 | background = attribute("background")
197 |
198 | ## Construct a `bgcolor` attribute.
199 | bgcolor : Str -> Attribute
200 | bgcolor = attribute("bgcolor")
201 |
202 | ## Construct a `border` attribute.
203 | border : Str -> Attribute
204 | border = attribute("border")
205 |
206 | ## Construct a `buffered` attribute.
207 | buffered : Str -> Attribute
208 | buffered = attribute("buffered")
209 |
210 | ## Construct a `capture` attribute.
211 | capture : Str -> Attribute
212 | capture = attribute("capture")
213 |
214 | ## Construct a `challenge` attribute.
215 | challenge : Str -> Attribute
216 | challenge = attribute("challenge")
217 |
218 | ## Construct a `charset` attribute.
219 | charset : Str -> Attribute
220 | charset = attribute("charset")
221 |
222 | ## Construct a `checked` attribute.
223 | checked : Str -> Attribute
224 | checked = attribute("checked")
225 |
226 | ## Construct a `cite` attribute.
227 | cite : Str -> Attribute
228 | cite = attribute("cite")
229 |
230 | ## Construct a `class` attribute.
231 | ##
232 | ## See the [classes] function to apply multiple classes simultaneously.
233 | class : Str -> Attribute
234 | class = attribute("class")
235 |
236 | ## Construct a `class` attribute from a list of classes.
237 | ##
238 | ## See the [class] function to apply a single class.
239 | ## For example:
240 | ## ```
241 | ## classes(["a", "b", "c", "a"]) == class("a b c")
242 | ## ```
243 | classes : List Str -> Attribute
244 | classes = |list_of_classes|
245 | unique_classes = Set.to_list(Set.from_list(list_of_classes))
246 | class(Str.join_with(unique_classes, " "))
247 |
248 | expect
249 | out = classes(["a", "b", "c", "a"])
250 | out == class("a b c")
251 |
252 | ## Construct a `code` attribute.
253 | code : Str -> Attribute
254 | code = attribute("code")
255 |
256 | ## Construct a `codebase` attribute.
257 | codebase : Str -> Attribute
258 | codebase = attribute("codebase")
259 |
260 | ## Construct a `color` attribute.
261 | color : Str -> Attribute
262 | color = attribute("color")
263 |
264 | ## Construct a `cols` attribute.
265 | cols : Str -> Attribute
266 | cols = attribute("cols")
267 |
268 | ## Construct a `colspan` attribute.
269 | colspan : Str -> Attribute
270 | colspan = attribute("colspan")
271 |
272 | ## Construct a `content` attribute.
273 | content : Str -> Attribute
274 | content = attribute("content")
275 |
276 | ## Construct a `contenteditable` attribute.
277 | contenteditable : Str -> Attribute
278 | contenteditable = attribute("contenteditable")
279 |
280 | ## Construct a `contextmenu` attribute.
281 | contextmenu : Str -> Attribute
282 | contextmenu = attribute("contextmenu")
283 |
284 | ## Construct a `controls` attribute.
285 | controls : Str -> Attribute
286 | controls = attribute("controls")
287 |
288 | ## Construct a `coords` attribute.
289 | coords : Str -> Attribute
290 | coords = attribute("coords")
291 |
292 | ## Construct a `crossorigin` attribute.
293 | crossorigin : Str -> Attribute
294 | crossorigin = attribute("crossorigin")
295 |
296 | ## Construct a `csp` attribute.
297 | csp : Str -> Attribute
298 | csp = attribute("csp")
299 |
300 | ## Construct a `data` attribute.
301 | data : Str -> Attribute
302 | data = attribute("data")
303 |
304 | ## Construct a `datetime` attribute.
305 | datetime : Str -> Attribute
306 | datetime = attribute("datetime")
307 |
308 | ## Construct a `decoding` attribute.
309 | decoding : Str -> Attribute
310 | decoding = attribute("decoding")
311 |
312 | ## Construct a `default` attribute.
313 | default : Str -> Attribute
314 | default = attribute("default")
315 |
316 | ## Construct a `defer` attribute.
317 | defer : Str -> Attribute
318 | defer = attribute("defer")
319 |
320 | ## Construct a `dir` attribute.
321 | dir : Str -> Attribute
322 | dir = attribute("dir")
323 |
324 | ## Construct a `dirname` attribute.
325 | dirname : Str -> Attribute
326 | dirname = attribute("dirname")
327 |
328 | ## Construct a `disabled` attribute.
329 | disabled : Str -> Attribute
330 | disabled = attribute("disabled")
331 |
332 | ## Construct a `download` attribute.
333 | download : Str -> Attribute
334 | download = attribute("download")
335 |
336 | ## Construct a `draggable` attribute.
337 | draggable : Str -> Attribute
338 | draggable = attribute("draggable")
339 |
340 | ## Construct a `enctype` attribute.
341 | enctype : Str -> Attribute
342 | enctype = attribute("enctype")
343 |
344 | ## Construct a `enterkeyhint` attribute.
345 | enterkeyhint : Str -> Attribute
346 | enterkeyhint = attribute("enterkeyhint")
347 |
348 | ## Construct a `for` attribute.
349 | for : Str -> Attribute
350 | for = attribute("for")
351 |
352 | ## Construct a `form` attribute.
353 | form : Str -> Attribute
354 | form = attribute("form")
355 |
356 | ## Construct a `formaction` attribute.
357 | formaction : Str -> Attribute
358 | formaction = attribute("formaction")
359 |
360 | ## Construct a `formenctype` attribute.
361 | formenctype : Str -> Attribute
362 | formenctype = attribute("formenctype")
363 |
364 | ## Construct a `formmethod` attribute.
365 | formmethod : Str -> Attribute
366 | formmethod = attribute("formmethod")
367 |
368 | ## Construct a `formnovalidate` attribute.
369 | formnovalidate : Str -> Attribute
370 | formnovalidate = attribute("formnovalidate")
371 |
372 | ## Construct a `formtarget` attribute.
373 | formtarget : Str -> Attribute
374 | formtarget = attribute("formtarget")
375 |
376 | ## Construct a `headers` attribute.
377 | headers : Str -> Attribute
378 | headers = attribute("headers")
379 |
380 | ## Construct a `height` attribute.
381 | height : Str -> Attribute
382 | height = attribute("height")
383 |
384 | ## Construct a `hidden` attribute.
385 | hidden : Str -> Attribute
386 | hidden = attribute("hidden")
387 |
388 | ## Construct a `high` attribute.
389 | high : Str -> Attribute
390 | high = attribute("high")
391 |
392 | ## Construct a `href` attribute.
393 | href : Str -> Attribute
394 | href = attribute("href")
395 |
396 | ## Construct a `hreflang` attribute.
397 | hreflang : Str -> Attribute
398 | hreflang = attribute("hreflang")
399 |
400 | ## Construct a `http-equiv` attribute.
401 | http_equiv : Str -> Attribute
402 | http_equiv = attribute("http-equiv")
403 |
404 | ## Construct a `icon` attribute.
405 | icon : Str -> Attribute
406 | icon = attribute("icon")
407 |
408 | ## Construct a `id` attribute.
409 | id : Str -> Attribute
410 | id = attribute("id")
411 |
412 | ## Construct a `importance` attribute.
413 | importance : Str -> Attribute
414 | importance = attribute("importance")
415 |
416 | ## Construct a `inputmode` attribute.
417 | inputmode : Str -> Attribute
418 | inputmode = attribute("inputmode")
419 |
420 | ## Construct a `integrity` attribute.
421 | integrity : Str -> Attribute
422 | integrity = attribute("integrity")
423 |
424 | ## Construct a `intrinsicsize` attribute.
425 | intrinsicsize : Str -> Attribute
426 | intrinsicsize = attribute("intrinsicsize")
427 |
428 | ## Construct a `ismap` attribute.
429 | ismap : Str -> Attribute
430 | ismap = attribute("ismap")
431 |
432 | ## Construct a `itemprop` attribute.
433 | itemprop : Str -> Attribute
434 | itemprop = attribute("itemprop")
435 |
436 | ## Construct a `keytype` attribute.
437 | keytype : Str -> Attribute
438 | keytype = attribute("keytype")
439 |
440 | ## Construct a `kind` attribute.
441 | kind : Str -> Attribute
442 | kind = attribute("kind")
443 |
444 | ## Construct a `label` attribute.
445 | label : Str -> Attribute
446 | label = attribute("label")
447 |
448 | ## Construct a `lang` attribute.
449 | lang : Str -> Attribute
450 | lang = attribute("lang")
451 |
452 | ## Construct a `language` attribute.
453 | language : Str -> Attribute
454 | language = attribute("language")
455 |
456 | ## Construct a `list` attribute.
457 | list : Str -> Attribute
458 | list = attribute("list")
459 |
460 | ## Construct a `loading` attribute.
461 | loading : Str -> Attribute
462 | loading = attribute("loading")
463 |
464 | ## Construct a `loop` attribute.
465 | loop : Str -> Attribute
466 | loop = attribute("loop")
467 |
468 | ## Construct a `low` attribute.
469 | low : Str -> Attribute
470 | low = attribute("low")
471 |
472 | ## Construct a `manifest` attribute.
473 | manifest : Str -> Attribute
474 | manifest = attribute("manifest")
475 |
476 | ## Construct a `max` attribute.
477 | max : Str -> Attribute
478 | max = attribute("max")
479 |
480 | ## Construct a `maxlength` attribute.
481 | maxlength : Str -> Attribute
482 | maxlength = attribute("maxlength")
483 |
484 | ## Construct a `media` attribute.
485 | media : Str -> Attribute
486 | media = attribute("media")
487 |
488 | ## Construct a `method` attribute.
489 | method : Str -> Attribute
490 | method = attribute("method")
491 |
492 | ## Construct a `min` attribute.
493 | min : Str -> Attribute
494 | min = attribute("min")
495 |
496 | ## Construct a `minlength` attribute.
497 | minlength : Str -> Attribute
498 | minlength = attribute("minlength")
499 |
500 | ## Construct a `multiple` attribute.
501 | multiple : Str -> Attribute
502 | multiple = attribute("multiple")
503 |
504 | ## Construct a `muted` attribute.
505 | muted : Str -> Attribute
506 | muted = attribute("muted")
507 |
508 | ## Construct a `name` attribute.
509 | name : Str -> Attribute
510 | name = attribute("name")
511 |
512 | ## Construct a `novalidate` attribute.
513 | novalidate : Str -> Attribute
514 | novalidate = attribute("novalidate")
515 |
516 | ## Construct a `open` attribute.
517 | open : Str -> Attribute
518 | open = attribute("open")
519 |
520 | ## Construct a `optimum` attribute.
521 | optimum : Str -> Attribute
522 | optimum = attribute("optimum")
523 |
524 | ## Construct a `pattern` attribute.
525 | pattern : Str -> Attribute
526 | pattern = attribute("pattern")
527 |
528 | ## Construct a `ping` attribute.
529 | ping : Str -> Attribute
530 | ping = attribute("ping")
531 |
532 | ## Construct a `placeholder` attribute.
533 | placeholder : Str -> Attribute
534 | placeholder = attribute("placeholder")
535 |
536 | ## Construct a `poster` attribute.
537 | poster : Str -> Attribute
538 | poster = attribute("poster")
539 |
540 | ## Construct a `preload` attribute.
541 | preload : Str -> Attribute
542 | preload = attribute("preload")
543 |
544 | ## Construct a `radiogroup` attribute.
545 | radiogroup : Str -> Attribute
546 | radiogroup = attribute("radiogroup")
547 |
548 | ## Construct a `readonly` attribute.
549 | readonly : Str -> Attribute
550 | readonly = attribute("readonly")
551 |
552 | ## Construct a `referrerpolicy` attribute.
553 | referrerpolicy : Str -> Attribute
554 | referrerpolicy = attribute("referrerpolicy")
555 |
556 | ## Construct a `rel` attribute.
557 | rel : Str -> Attribute
558 | rel = attribute("rel")
559 |
560 | ## Construct a `required` attribute.
561 | required : Str -> Attribute
562 | required = attribute("required")
563 |
564 | ## Construct a `reversed` attribute.
565 | reversed : Str -> Attribute
566 | reversed = attribute("reversed")
567 |
568 | ## Construct a `role` attribute.
569 | role : Str -> Attribute
570 | role = attribute("role")
571 |
572 | ## Construct a `rows` attribute.
573 | rows : Str -> Attribute
574 | rows = attribute("rows")
575 |
576 | ## Construct a `rowspan` attribute.
577 | rowspan : Str -> Attribute
578 | rowspan = attribute("rowspan")
579 |
580 | ## Construct a `sandbox` attribute.
581 | sandbox : Str -> Attribute
582 | sandbox = attribute("sandbox")
583 |
584 | ## Construct a `scope` attribute.
585 | scope : Str -> Attribute
586 | scope = attribute("scope")
587 |
588 | ## Construct a `scoped` attribute.
589 | scoped : Str -> Attribute
590 | scoped = attribute("scoped")
591 |
592 | ## Construct a `selected` attribute.
593 | selected : Str -> Attribute
594 | selected = attribute("selected")
595 |
596 | ## Construct a `shape` attribute.
597 | shape : Str -> Attribute
598 | shape = attribute("shape")
599 |
600 | ## Construct a `size` attribute.
601 | size : Str -> Attribute
602 | size = attribute("size")
603 |
604 | ## Construct a `sizes` attribute.
605 | sizes : Str -> Attribute
606 | sizes = attribute("sizes")
607 |
608 | ## Construct a `slot` attribute.
609 | slot : Str -> Attribute
610 | slot = attribute("slot")
611 |
612 | ## Construct a `span` attribute.
613 | span : Str -> Attribute
614 | span = attribute("span")
615 |
616 | ## Construct a `spellcheck` attribute.
617 | spellcheck : Str -> Attribute
618 | spellcheck = attribute("spellcheck")
619 |
620 | ## Construct a `src` attribute.
621 | src : Str -> Attribute
622 | src = attribute("src")
623 |
624 | ## Construct a `srcdoc` attribute.
625 | srcdoc : Str -> Attribute
626 | srcdoc = attribute("srcdoc")
627 |
628 | ## Construct a `srclang` attribute.
629 | srclang : Str -> Attribute
630 | srclang = attribute("srclang")
631 |
632 | ## Construct a `srcset` attribute.
633 | srcset : Str -> Attribute
634 | srcset = attribute("srcset")
635 |
636 | ## Construct a `start` attribute.
637 | start : Str -> Attribute
638 | start = attribute("start")
639 |
640 | ## Construct a `step` attribute.
641 | step : Str -> Attribute
642 | step = attribute("step")
643 |
644 | ## Construct a `style` attribute.
645 | style : Str -> Attribute
646 | style = attribute("style")
647 |
648 | ## Construct a `summary` attribute.
649 | summary : Str -> Attribute
650 | summary = attribute("summary")
651 |
652 | ## Construct a `tabindex` attribute.
653 | tabindex : Str -> Attribute
654 | tabindex = attribute("tabindex")
655 |
656 | ## Construct a `target` attribute.
657 | target : Str -> Attribute
658 | target = attribute("target")
659 |
660 | ## Construct a `title` attribute.
661 | title : Str -> Attribute
662 | title = attribute("title")
663 |
664 | ## Construct a `translate` attribute.
665 | translate : Str -> Attribute
666 | translate = attribute("translate")
667 |
668 | ## Construct a `type` attribute.
669 | type : Str -> Attribute
670 | type = attribute("type")
671 |
672 | ## Construct a `usemap` attribute.
673 | usemap : Str -> Attribute
674 | usemap = attribute("usemap")
675 |
676 | ## Construct a `value` attribute.
677 | value : Str -> Attribute
678 | value = attribute("value")
679 |
680 | ## Construct a `width` attribute.
681 | width : Str -> Attribute
682 | width = attribute("width")
683 |
684 | ## Construct a `wrap` attribute.
685 | wrap : Str -> Attribute
686 | wrap = attribute("wrap")
687 |
--------------------------------------------------------------------------------
/src/Html.roc:
--------------------------------------------------------------------------------
1 | module [
2 | Node,
3 | text,
4 | element,
5 | void_element,
6 | render,
7 | render_without_doc_type,
8 | dangerously_include_unescaped_html,
9 | # Content sectioning
10 | address,
11 | article,
12 | aside,
13 | footer,
14 | h1,
15 | h2,
16 | h3,
17 | h4,
18 | h5,
19 | h6,
20 | header,
21 | main,
22 | nav,
23 | section,
24 | # Demarcating edits
25 | del,
26 | ins,
27 | # Document metadata
28 | base,
29 | head,
30 | link,
31 | meta,
32 | style,
33 | title,
34 | # Embedded content
35 | embed,
36 | iframe,
37 | object,
38 | picture,
39 | portal,
40 | source,
41 | # Forms
42 | button,
43 | datalist,
44 | fieldset,
45 | form,
46 | input,
47 | label,
48 | legend,
49 | meter,
50 | optgroup,
51 | option,
52 | output,
53 | progress,
54 | select,
55 | textarea,
56 | # Image and multimedia
57 | area,
58 | audio,
59 | img,
60 | map,
61 | track,
62 | video,
63 | # Inline text semantics
64 | a,
65 | abbr,
66 | b,
67 | bdi,
68 | bdo,
69 | br,
70 | cite,
71 | code,
72 | data,
73 | dfn,
74 | em,
75 | i,
76 | kbd,
77 | mark,
78 | q,
79 | rp,
80 | rt,
81 | ruby,
82 | s,
83 | samp,
84 | small,
85 | span,
86 | strong,
87 | sub,
88 | sup,
89 | time,
90 | u,
91 | var,
92 | wbr,
93 | # Interactive elements
94 | details,
95 | dialog,
96 | summary,
97 | # Main root
98 | html,
99 | # SVG and MathML
100 | math,
101 | svg,
102 | # Scripting
103 | canvas,
104 | noscript,
105 | script,
106 | # Sectioning root
107 | body,
108 | # Table content
109 | caption,
110 | col,
111 | colgroup,
112 | table,
113 | tbody,
114 | td,
115 | tfoot,
116 | th,
117 | thead,
118 | tr,
119 | # Text content
120 | blockquote,
121 | dd,
122 | div,
123 | dl,
124 | dt,
125 | figcaption,
126 | figure,
127 | hr,
128 | li,
129 | menu,
130 | ol,
131 | p,
132 | pre,
133 | ul,
134 | # Web components
135 | slot,
136 | template,
137 | ]
138 |
139 | import Attribute exposing [Attribute, attribute]
140 | import SafeStr exposing [SafeStr, escape, dangerously_mark_safe]
141 |
142 | ## An HTML node, either an HTML element or some text inside an HTML element.
143 | Node : [Element Str U64 (List Attribute) (List Node), Text Str, UnescapedHtml Str]
144 |
145 | ## Create a `Text` node containing a string.
146 | ##
147 | ## The string will be escaped so that it's safely rendered as a text node even if the string contains HTML tags.
148 | ##
149 | ## ```
150 | ## expect
151 | ## textNode = Html.text("")
152 | ## Html.render_without_doc_type(textNode) == "<script>alert('hi')</script>"
153 | ## ```
154 | text : Str -> Node
155 | text = Text
156 |
157 | expect
158 | text_node = text("")
159 | render_without_doc_type(text_node) == "<script>alert('hi')</script>"
160 |
161 | ## Mark a string as safe for HTML without actually escaping it.
162 | ##
163 | ## DO NOT use this function unless you're sure the input string is safe.
164 | ##
165 | ## NEVER use this function on user input; use the `text` function instead.
166 | ##
167 | ## ```
168 | ## expect
169 | ## htmlNode = Html.dangerously_include_unescaped_html("")
170 | ## Html.render_without_doc_type(htmlNode) == ""
171 | ## ```
172 | dangerously_include_unescaped_html : Str -> Node
173 | dangerously_include_unescaped_html = UnescapedHtml
174 |
175 | expect
176 | html_node = dangerously_include_unescaped_html("")
177 | render_without_doc_type(html_node) == ""
178 |
179 | ## Define a non-standard HTML element.
180 | ## You can use this to add elements that are not already supported.
181 | ##
182 | ## For example, you could bring back the obsolete