Skip to content

Commit

Permalink
docs(README): document the new template engine
Browse files Browse the repository at this point in the history
  • Loading branch information
favonia committed Oct 23, 2022
1 parent d594d3d commit 175f358
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 23 deletions.
37 changes: 21 additions & 16 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ A small and fast DDNS updater for Cloudflare.

### ⚡ Efficiency

* 🤏 The Docker images are small (less than 4 MB after compression).
* 🤏 The Docker images are small (less than 3 MB after compression).
* 🔁 The Go runtime will re-use existing HTTP connections.
* 🗃️ Cloudflare API responses are cached to reduce the API usage.

Expand All @@ -34,7 +34,7 @@ Simply list all the domain names and you are done!
* 🌍 Internationalized domain names (_e.g._, `🐱.example.org`) are fully supported. _(The updater smooths out [some rough edges of the Cloudflare API](https://github.com/cloudflare/cloudflare-go/pull/690#issuecomment-911884832).)_
* 🃏 Wildcard domain names (_e.g._, `*.example.org`) are also supported.
* 🔍 This updater automatically finds the DNS zones for you, and it can handle multiple DNS zones.
* 🕹️ You can toggle IPv4 (`A` records), IPv6 (`AAAA` records) and Cloudflare proxying and change TTL on a per-domain basis.
* 🕹️ You can toggle IPv4 (`A` records), IPv6 (`AAAA` records) and Cloudflare proxying on a per-domain basis.

### 🕵️ Privacy

Expand Down Expand Up @@ -344,27 +344,32 @@ In most cases, `CF_ACCOUNT_ID` is not needed.

| Name | Valid Values | Meaning | Required? | Default Value |
| ---- | ------------ | ------- | --------- | ------------- |
| `PROXIED` | Boolean values, such as `true`, `false`, `0` and `1`. See [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool) | Whether new DNS records should be proxied by Cloudflare | No | `false`
| `PROXIED` | Boolean values, such as `true`, `false`, `0` and `1`. See [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool). See below for experimental support of per-domain proxy settings. | Whether new DNS records should be proxied by Cloudflare | No | `false`
| `TTL` | Time-to-live (TTL) values in seconds | The TTL values used to create new DNS records | No | `1` (This means “automatic” to Cloudflare)

> <details>
> <summary>🧪 Experimental support of templates (subject to changes):</summary>
> <summary>🧪 Experimental per-domain proxy settings (subject to changes):</summary>
>
> Both `PROXIED` and `TTL` can be [Jet Templates](https://github.com/CloudyKit/jet/blob/master/docs/syntax.md) for per-domain settings. For example, `PROXIED={{!hasSuffix("example.org")}}` means all domains should be proxied except domains like `www.example.org` and `example.org`. The Go templates are executed with the following two custom functions:
> - `inDomains(patterns ...string) bool`
> The `PROXIED` can be a boolean expression. Here are some examples:
> - `PROXIED=is(example.org)`: enable proxy only for the domain `example.org`
> - `PROXIED=is(example1.org) || sub(example2.org)`: enable proxy only for the domain `example1.org` and the subdomains of `example2.org`
> - `PROXIED=!is(example.org)`: enable proxy _except for_ the domain `example.org`
> - `PROXIED=is(example1.org) || is(example2.org) || is(example3.org)`: enable proxy only for the domains `example1.org`, `example2.org`, and `example3.org`
>
> Returns `true` if and only if the target domain matches one of `patterns`. All domains are normalized before comparison. For example, internationalized domain names are converted to Punycode before comparing them.
> - `hasSuffix(patterns ...string) bool`
> More formally, a boolean expression has one of the following forms:
> - A boolean value accepted by [strconv.ParseBool](https://pkg.go.dev/strconv#ParseBool), such as `t` as `true`.
> - `is(d)` which matches the domain `d`. Note that `is(*.a)` only matches the wildcard domain `*.a`; use `sub(a)` to all subdomains of `a` (including `*.a`).
> - `sub(d)` which matches subdomains of `d` (not including `d` itself).
> - `! e` where `e` is a boolean expression, representing logical negation.
> - `e1 || e2` where `e1` and `e2` are boolean expressions, representing logical or.
> - `e1 && e2` where `e1` and `e2` are boolean expressions, representing logical and.
>
> Returns `true` if and only if the target domain has one of `patterns` as itself or its parent (or ancestor). Note that labels in domains must fully match; for example, the suffix `b.org` will not match `www.bb.org` because `bb.org` and `b.org` are incomparable, while the suffix `bb.org` will match `www.bb.org`.
> One can use parentheses to group expressions, such as `!(is(a) && (is(b) || is(c)))`.
> For convenience, the engine also accepts these short forms:
> - `is(d1, d2, ..., dn) = is(d1) || is(d2) || ... || is(dn)`
> - `sub(d1, d2, ..., dn) = sub(d1) || sub(d2) || ... || sub(dn)`
>
> Some examples:
> - `TTL={{if hasSuffix("b.c")}} 60 {{else if inDomains("d.e.f","a.bb.c")}} 90 {{else}} 120 {{end}}`
>
> For the domain `b.c` and its descendants, the TTL is 60, and for the domains `d.e.f` and `a.bb.c`, the TTL is 90, and then for all other domains, the TTL is 120.
> - `PROXIED={{hasSuffix("b.c") && ! inDomains("a.b.c"))}}`
>
> Proxy the domain `b.c` and its descendants except for the domain `a.b.c`.
> Using these short forms, `is(example1.org) || is(example2.org) || is(example3.org)` can be abbreviated as `is(example1.org,example2.org,example3.org)`.
> </details>

</details>
Expand Down
6 changes: 3 additions & 3 deletions internal/domainexp/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func scanMustConstant(ppfmt pp.PP, input string, tokens []string, wanted string)

type predicate = func(domain.Domain) bool

func hasSuffix(s, suffix string) bool {
return len(suffix) == 0 || (strings.HasSuffix(s, suffix) && (len(s) == len(suffix) || s[len(s)-len(suffix)-1] == '.'))
func hasStrictSuffix(s, suffix string) bool {
return strings.HasSuffix(s, suffix) && (len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.')
}

// scanAtomic mimics ParseBool, call scanFunction, and then check parenthesized expressions.
Expand Down Expand Up @@ -134,7 +134,7 @@ func scanFactor(ppfmt pp.PP, input string, tokens []string) (predicate, []string
"sub": func(d domain.Domain) bool {
asciiD := d.DNSNameASCII()
for _, pat := range ASCIIDomains {
if hasSuffix(asciiD, pat) {
if hasStrictSuffix(asciiD, pat) {
return true
}
}
Expand Down
8 changes: 4 additions & 4 deletions internal/domainexp/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ func TestParseExpression(t *testing.T) {
m.EXPECT().Errorf(pp.EmojiUserError, `Failed to parse %q: unexpected token %q`, "is(&&", "&&")
},
},
"sub/1": {"sub(example.com)", true, f("example.com"), true, nil},
"sub/1": {"sub(example.com)", true, f("example.com"), false, nil},
"sub/2": {"sub(example.com)", true, w("example.com"), true, nil},
"sub/3": {"sub(example.com)", true, f("sub.example.com"), true, nil},
"sub/4": {"sub(example.com)", true, f("subexample.com"), false, nil},
"sub/idn/1": {"sub(☕.de)", true, f("xn--53h.de"), true, nil},
"sub/idn/2": {"sub(Xn--53H.de)", true, f("xn--53h.de"), true, nil},
"sub/idn/3": {"sub(*.Xn--53H.de)", true, w("xn--53h.de"), true, nil},
"sub/idn/1": {"sub(☕.de)", true, f("www.xn--53h.de"), true, nil},
"sub/idn/2": {"sub(Xn--53H.de)", true, f("www.xn--53h.de"), true, nil},
"sub/idn/3": {"sub(Xn--53H.de)", true, w("xn--53h.de"), true, nil},
"not/1": {"!0", true, nil, true, nil},
"not/2": {"!!!!!!!!!!!0", true, nil, true, nil},
"not/3": {
Expand Down

0 comments on commit 175f358

Please sign in to comment.