Skip to content

Instantly share code, notes, and snippets.

@godDLL
Last active June 21, 2016 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save godDLL/f31224df756fff2623290e331b40b1fe to your computer and use it in GitHub Desktop.
Save godDLL/f31224df756fff2623290e331b40b1fe to your computer and use it in GitHub Desktop.
Tiny static site generator, with <?js "templates" ?>

HTMX - static document generator, with Javascript preprocessing

Use Javascript where you would ordinarily use PHP or some templating language.

STATUS: v0.0.2 so alpha it hurts

This has just been hacked together, so expect a bumpy ride.

Overview

$ echo "1::b::3" | htmx --context "{b:'2'}"
→ 123

Default script delimiters are the same for left and right side. Double colon :: was chosen for it's scarse use in websites source-code.

You can set it up with ['<\\?', '\\?>'] delimiters for PHP-like short tags.

If your Javascript returns an object structure instead of a text response, (say some vDOM) you can use the preprocess() function to layout the final output.

Download

npm install -g htmx

Node.js Quickstart

var
fs= require('fs'),
htmx= require('htmx')()

fs.writeFileSync(
  'test.html',                                // → "123"
  htmx(
    fs.readFileSync('test.htmx').toString(),  // → "1::b::3"
    fs.readFileSync('test.js').toString()     // → "{b:2}"
))

Shell Quickstart

$ cat index.html
→ "1::b::3"

$ cat index.js
→ {b:'2'}

$ htmx --context index.js  --template index.html
→ 123
// -c can be a JSON string
// if -t is missing, STDIN is used instead

$ htmx --root .  --build ../build  // see TODO.md
// builds current dir, using index.js for context, if exists

F. A. Q. (advanced usage)

All shell options can be shortened, as long as they are distinguishable.
So the --root option can become -r and the --context option can become -c

Use the --delimiter option like so: htmx -d \\\{\\\{ \\\}\\\}. Yes, I know.
RegExp escape, shell escape, no quotes, weird space in the middle. PRs welcome.

The preprocess() function lives in the preprocess.js module, which you will have to hack on. PRs welcome.

The --template option can not accept a template string. Use STDIN instead.

Rationale

PHP is way too clunky still. Things like Jinja's filter pipes in Javascript naturally become chains, the script return value naturally becomes the response, I mean, I didn't do much to make all this work, not at all.

Javascript is a fine templating language, when used like this.

var
fs= require('fs'),
readFile= fs.readFileSync,
writeFile= fs.writeFileSync,
statPath= fs.statSync,
JSON= /^\s*\{[^]*\}\s*$/gi
// relaxed Javascript Object, for use with eval([js]).pop()
module.exports= function (HTMX){
function unpickle (__json){ return eval ('[' + __json + ']')[0] }
var
test= process.argv.slice(2).join(' '),
getParam= function (name){
return new RegExp(name + '\\s+([^]*?)\\s*(-|$)', 'gi') },
dir= getParam('[-]?-b\\w*').exec(test),
ctx= getParam('[-]?-c\\w*').exec(test),
tpl= getParam('[-]?-t\\w*').exec(test),
dat= getParam('[-]?-r\\w*').exec(test)
// TODO: yuck, replace this shit
var
delim= /[-]?-d\w*\s+([^]*?)\s([^]*?)\s*(-|$)/gi.exec(test)
if (delim) delim= [delim[1], delim[2]]
var
render= HTMX(delim)
if (dir) {
dir= dir[1]
var isdir
try {
isdir= statPath(dir).isDirectory
} catch (_) { }
if (!isdir)
throw new Error("--build isn't a directory, can't read")
}
if (dat){
dat= dat[1]
if (!dir)
throw new Error("--build directory required")
// TODO: for each file in `dat` recursively, `render()` that fucker, with it's `ctx`
process.stderr.write( 'TODO: not implemented, PRs welcome')
} else {
tpl && (tpl= tpl[1])
if (ctx){
ctx= ctx[1],
process.stderr.write('Running ' + (tpl || 'STDIN') + ' with ' + ctx + "\n")
if (ctx && ctx.match( JSON)){
ctx= unpickle( ctx)
} else {
try {
ctx= unpickle( readFile( ctx).toString())
} catch (_){
throw new Error("--context not a Javascript object, can't read as filename")
}
}
}
if (tpl){
try {
var out= render( readFile( tpl).toString(), ctx)
} catch (TemplateException){
if ('ENOENT' == TemplateException.code)
throw new Error("--template expected filename, can not read")
else {
process.stderr.write( "Can't render template")
throw TemplateException
}
}
if (!dir)
process.stdout.write( out)
else
writeFile( dir + '/' + tpl.split('/').pop(), out)
} else {
tpl= []
process.stdin.on('readable', function (){
tpl.push( process.stdin.read())
})
process.stdin.on('end', function (){
if (tpl)
process.stdout.write( render( tpl.join(''), ctx))
})
}
}
}
#!/usr/bin/env node
// build a --template, or a whole project --root directory
// default context filename is `index.js` for dirs, unless --context
// specify a --build directory to output there, instead of STDOUT
var
preprocess= require('./preprocess'),
// here you can pre-process the response before final output
DELIM= ["::", "::"], // or use ["<\\?js", "\\?>"] to get `1<?js2?>3` → `123`
// describe your document context in JSON, then use <?js ?> for the template
// i.e. root/index.json {a:1, b:"hi"} , index.html <!doctype html> ::b+a:: → "hi1"
JSON= /^\s*\{[^]*\}\s*$/gi
// relaxed Javascript Object, for use with eval([js]).pop()
function HTMX (delim){
function unpickle (__ctx, __js){ with (__ctx){ return eval (__js) }}
var
EMPTY_STR= '',
NOT_DELIM= ANY= "([^]*?)"
return (function (delim, pre){
return function renderTemplateWithContext (t, c){
var
r= t.split( new RegExp(delim.join( NOT_DELIM), 'ig')),
n= 0, s,
c= c || {}
do {
s= 3 *n + 1
if (r[s] && r[s].match( JSON)){
r[s]= unpickle( c, '[' + r[s] + ']')[0]
r[s]= pre.call( c, r[s])
r[s]= r[s].return
} else {
r[s]= unpickle( c, r[s])
}
n += 1
} while (void 0 !== r[3 *n])
return r.join(EMPTY_STR)
}
})(delim || DELIM, preprocess)
}
if (require.main === module){
if (2 >= process.argv.length)
throw new Error("use --root with --build, or --template with --context")
require('./cli')(HTMX)
} else
module.exports= HTMX
{
"name": "htmx",
"version": "0.0.2",
"description": "Use Javascript in place of PHP",
"homepage": "https://gist.github.com/godDLL/f31224df756fff2623290e331b40b1fe#comments",
"tonicExample": "require('htmx')( ['<%', '%>'])( '1<%r %>3', {r:2})",
"repository": {
"type": "git",
"url": "https://gist.github.com/godDLL/f31224df756fff2623290e331b40b1fe.git"
},
"author": "Yuli Che. <god.DLL@iCloud.com>",
"license": "MIT",
"keywords": [
"php",
"web"
],
"main": "htmx.js",
"bin": "htmx.js",
"files": [
"TODO.md",
"htmx.js",
"cli.js",
"preprocess.js",
"watch.js"
]
}
// TODO: maybe set delim and preproc on the HTMX() object, or pass it in an options obj
function preprocess (res){
return res
}
module.exports= preprocess

TODO

v0.0.3

  • focus the project more clearly
  • deal with x

v0.0.4

  • deal with the extensibility of preprocess(), DELIM
  • tests/examples

v0.1.0

  • require command-line-args
  • cli --help
  • cli build

v0.1.1

  • cli watch

v0.1.2

  • JSON to HTML preprocess() option
  • JSON to CSS preprocess() option

v0.2.0 beta

Maybe this can grow to be a PHP-like execution environment, so that live HTTP responses can be generated and served.


DONE

v0.0.2 alpha

  • fix npm install -g htmx

v0.0.1 alpha

  • npm package
  • preprocess, for doing whatever you want before scriptlet's final output
  • renderTemplateWithContext, module's main functionality
  • DELIM, for custom scriptlet delimiters, i. e. var htmx= require('htmx')( ['<%', '%>'] )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment