dnjs
dnjs copied to clipboard
DOM Notation JS
dnjs
| Install | For Configuration | For HTML templating | Instead of jq |
|---|
╔══════════════════════════════╗
║ ╔═══════════════╗ ║
║ ║ ╔══════╗ ║ ║
║ ║ ║ JSON ║ dnjs ║ JavaScript ║
║ ║ ╚══════╝ ║ ║
║ ╚═══════════════╝ ║
╚══════════════════════════════╝
dnjs is a pure subset of JavaScript that wants to replace (across many host languages - currently go and Python):
- Overly limiting/baroque configuration languages, eg:
yaml - Mucky string based
HTML/XMLtemplating, eg:handlebars- see blog post - Unfamiliar
JSONprocessing languages, eg:jq
Extensions to JSON:
| Feature | Syntax |
|---|---|
| Comments | // |
Unquoted Object keys |
{a: 42} |
| Trailing commas | {a: 42, } |
| Imports (Non-local imports are simply ignored) | import { c } from "./b.dn.js" |
| ... | import b from "./b.dn.js" |
| Exports | export default a |
| ... | export const b = c |
| Rest syntax | {...a}, [...a] |
| Arrow Functions | const f = (a, b) => c |
| Ternary expressions | a === b ? c : d |
| Map | a.map((v, i) => b) |
| Filter | a.filter((v, i) => b) |
| Reduce | a.reduce((x, y) => [...x, ...y], []) |
| Entries | Object.entries(a).map(([k, v], i) => b) |
| From entries | Object.fromEntries(a) |
| Hyperscript, somewhat compatible with mithril | m("sometag#some-id.some-class.other-class", {"href": "foo.js", "class": ["another-class"]}, children) |
| Evaluates to | {"tag": "sometag", "attrs": {"id": "some-id", className: "some-class other-class another-class", "href": "foo.js", "children": children} |
| For trusted html | m.trust(a) |
| Templates | `foo ${a}` |
| Dedent | dedent(`foo ${a}`) |
| List functions | .length, .includes(a) |
It is powerful yet familiar, and the reduced syntax makes it easy to implement. Currently the state is very alpha - see the TODO at the end.
Installing the standalone binary
Downloads
Installing the Python interpreter/API
pip install dnjs
dnjs --help
Examples
Some of these examples reference other files in the examples folder.
For configuration:
import { environments } from "./global.dn.js"
// names of the services to deploy
const serviceNames = ["signup", "account"]
const makeService = (environment, serviceName) => ({
name: serviceName,
ip: environment === environments.PROD ? "189.34.0.4" : "127.0.0.1"
})
export default (environment) => serviceNames.map(
(v, i) => makeService(environment, v)
)
Running:
dnjs --pretty examples/configuration.dn.js examples/environment.json
Gives us:
[
{
"name": "signup",
"ip": "127.0.0.1"
},
{
"name": "account",
"ip": "127.0.0.1"
}
]
For HTML templating
dnjs prescribes functions for making HTML, that handily are a subset of mithril (this makes it possible to write powerful, reusable cross-language HTML components).
Given the file commentsPage.dn.js:
import m from "mithril"
import { page } from "./basePage.dn.js"
const commentList = (comments) => m("ul",
comments.map((comment, i) => m("li", `Comment ${i} says: ${comment.text}`))
)
export default (comments) => page(commentList(comments))
Then in a python webserver we can render the file as HTML:
from dnjs import render
@app.route("/some-route"):
def some_route():
...
return render("commentsPage.dn.js", comments)
And the endpoint will return:
<html>
<head>
<script src="someScript.js">
</script>
</head>
<body>
<ul>
<li>
Comment 0 says: hiya!
</li>
<li>
Comment 1 says: oioi
</li>
</ul>
</body>
</html>
Or we can use the same components on the frontend with mithril:
import page from "../commentsPage.dn.js"
...
m.mount(document.body, page)
Or we can render the HTML on the command line similar to before:
dnjs --html examples/commentsPage.dn.js examples/comments.json
Note, that without the --html flag, we still make the following JSON, the conversion to HTML is a post-processing stage:
{
"tag": "html",
"attrs": {
"className": ""
},
"children": [
{
"tag": "head",
"attrs": {
...
For css templating
Using --css will post-process eg:
export default {
".bold": {"font-weight": "bold"},
".red": {"color": "red"},
}
to:
.bold {
font-weight: bold;
}
.red {
color: red;
}
As a jq replacement
JSON='[{foo: 1, bar: "one"}, {foo: 2, bar: "two"}]'
echo $JSON | dnjs -p 'a=>a.map(b=>[b.bar, b.foo])' -
[["one", 1], ["two", 2]]
csv
echo $JSON | dnjs -p 'a=>a.map(b=>[b.bar, b.foo])' --csv -
"one",1
"two",2
csv, raw
echo $JSON | dnjs -p 'a=>a.map(b=>[b.bar, b.foo])' --csv --raw -
one,1
two,2
jsonl
JSON='{foo: 1, bar: "one"}\n{foo: 2, bar: "two"}'
echo $JSON | while read l; do echo $l | dnjs -p 'a=>a.bar' --raw -; done
one
two
Flattening
Remember, you can flatten arrays with:
.reduce((a, b)=>[...a, ...b], [])
Name
Originally the name stood for DOM Notation JavaScript.
Python
API
These functions return JSON-able data:
from dnjs import get_default_export, get_named_export
get_default_export(path)
get_named_export(path, name)
This function returns HTML as a str:
from dnjs import render
render(path, *values)
The types used throughout dnjs are fairly simple dataclasss , there's not much funny stuff going on in the code - check it out!
Development
Install dev requirements with:
pip install -r requirements-dev.txt
Run tests with:
pytest
Pin requirements with:
pip-compile -q; cat requirements.in requirements-dev.in | pip-compile -q --output-file=requirements-dev.txt -
Rebuild and publish (after upversioning) with:
# up version setup.py
rm dist/*; python setup.py sdist bdist_wheel; twine upload dist/*
JS
Javascript validation library to follow - see TODO section below.
Run tests with:
npm install
npm test
TODO
- Use on something real to iron out bugs.
- Spec out weird behaviour + make the same as js:
- numbers
===
- Nicer docs:
- Write up why we don't need filters like | to_human.
- Consider
onclick,onkeydown,on...functions... and how we want to handle them / attach them on reaching the browser in a isomophic setup. - Decide what else should be added:
- Common string functions like upper case, replace etc?
parseIntetc..
- Write JS library that simply wraps mithril render and has a
dnjs.isValid(path)function that uses the grammar (doing this may involve removing somelark-specific bits in the grammar. - Typescript support?
- Consider what prevents
dnjsfrom becoming a data interchange format - eg. infinite recursion.--safemode? Specify PATHs that it's permitted to import from. - Allow importing JSON using Experimental JSON modules](https://nodejs.org/api/esm.html#esm_experimental_json_modules).
- Remove accidental non-js compatability - eg. template grammar is a bit wacky.