intertext
Version:
Services for Recurrent Text-related Tasks
351 lines (314 loc) • 18.7 kB
text/coffeescript
'use strict'
############################################################################################################
CND = require 'cnd'
rpr = CND.rpr
badge = 'INTERTEXT/CUPOFHTML'
log = CND.get_logger 'plain', badge
info = CND.get_logger 'info', badge
whisper = CND.get_logger 'whisper', badge
alert = CND.get_logger 'alert', badge
debug = CND.get_logger 'debug', badge
warn = CND.get_logger 'warn', badge
help = CND.get_logger 'help', badge
urge = CND.get_logger 'urge', badge
echo = CND.echo.bind CND
#...........................................................................................................
MAIN = @
DATOM = require 'datom'
Multimix = require 'multimix'
types = require './types'
{ isa
validate
cast
type_of } = types
#...........................................................................................................
_defaults = { flatten: true, DATOM: null, newlines: true, }
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
class @_Targeted_collection extends Multimix
constructor: ( target ) ->
super()
@_ = target
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
class @Tags extends @_Targeted_collection
address: ( P... ) => @_.tag 'address', { $blk: true, }, P...
article: ( P... ) => @_.tag 'article', { $blk: true, }, P...
aside: ( P... ) => @_.tag 'aside', { $blk: true, }, P...
blockquote: ( P... ) => @_.tag 'blockquote', { $blk: true, }, P...
dd: ( P... ) => @_.tag 'dd', { $blk: true, }, P...
details: ( P... ) => @_.tag 'details', { $blk: true, }, P...
dialog: ( P... ) => @_.tag 'dialog', { $blk: true, }, P...
div: ( P... ) => @_.tag 'div', { $blk: true, }, P...
dl: ( P... ) => @_.tag 'dl', { $blk: true, }, P...
dt: ( P... ) => @_.tag 'dt', { $blk: true, }, P...
fieldset: ( P... ) => @_.tag 'fieldset', { $blk: true, }, P...
figcaption: ( P... ) => @_.tag 'figcaption', { $blk: true, }, P...
figure: ( P... ) => @_.tag 'figure', { $blk: true, }, P...
footer: ( P... ) => @_.tag 'footer', { $blk: true, }, P...
form: ( P... ) => @_.tag 'form', { $blk: true, }, P...
h1: ( P... ) => @_.tag 'h1', { $blk: true, }, P...
h2: ( P... ) => @_.tag 'h2', { $blk: true, }, P...
h3: ( P... ) => @_.tag 'h3', { $blk: true, }, P...
h4: ( P... ) => @_.tag 'h4', { $blk: true, }, P...
h5: ( P... ) => @_.tag 'h5', { $blk: true, }, P...
h6: ( P... ) => @_.tag 'h6', { $blk: true, }, P...
header: ( P... ) => @_.tag 'header', { $blk: true, }, P...
hgroup: ( P... ) => @_.tag 'hgroup', { $blk: true, }, P...
hr: ( P... ) => @_.tag 'hr', { $blk: true, }, P...
li: ( P... ) => @_.tag 'li', { $blk: true, }, P...
main: ( P... ) => @_.tag 'main', { $blk: true, }, P...
nav: ( P... ) => @_.tag 'nav', { $blk: true, }, P...
ol: ( P... ) => @_.tag 'ol', { $blk: true, }, P...
p: ( P... ) => @_.tag 'p', { $blk: true, }, P...
pre: ( P... ) => @_.tag 'pre', { $blk: true, }, P...
section: ( P... ) => @_.tag 'section', { $blk: true, }, P...
table: ( P... ) => @_.tag 'table', { $blk: true, }, P...
ul: ( P... ) => @_.tag 'ul', { $blk: true, }, P...
#.........................................................................................................
a: ( P... ) => @_.tag 'a', P...
abbr: ( P... ) => @_.tag 'abbr', P...
acronym: ( P... ) => @_.tag 'acronym', P...
applet: ( P... ) => @_.tag 'applet', P...
area: ( P... ) => @_.tag 'area', P...
audio: ( P... ) => @_.tag 'audio', P...
b: ( P... ) => @_.tag 'b', P...
base: ( P... ) => @_.tag 'base', P...
basefont: ( P... ) => @_.tag 'basefont', P...
bdi: ( P... ) => @_.tag 'bdi', P...
bdo: ( P... ) => @_.tag 'bdo', P...
big: ( P... ) => @_.tag 'big', P...
body: ( P... ) => @_.tag 'body', P...
br: ( P... ) => @_.tag 'br', P...
button: ( P... ) => @_.tag 'button', P...
canvas: ( P... ) => @_.tag 'canvas', P...
caption: ( P... ) => @_.tag 'caption', P...
center: ( P... ) => @_.tag 'center', P...
cite: ( P... ) => @_.tag 'cite', P...
code: ( P... ) => @_.tag 'code', P...
col: ( P... ) => @_.tag 'col', P...
colgroup: ( P... ) => @_.tag 'colgroup', P...
data: ( P... ) => @_.tag 'data', P...
datalist: ( P... ) => @_.tag 'datalist', P...
del: ( P... ) => @_.tag 'del', P...
dfn: ( P... ) => @_.tag 'dfn', P...
em: ( P... ) => @_.tag 'em', P...
embed: ( P... ) => @_.tag 'embed', P...
font: ( P... ) => @_.tag 'font', P...
frame: ( P... ) => @_.tag 'frame', P...
frameset: ( P... ) => @_.tag 'frameset', P...
head: ( P... ) => @_.tag 'head', P...
html: ( P... ) => @_.tag 'html', P...
i: ( P... ) => @_.tag 'i', P...
iframe: ( P... ) => @_.tag 'iframe', P...
img: ( P... ) => @_.tag 'img', P...
input: ( P... ) => @_.tag 'input', P...
ins: ( P... ) => @_.tag 'ins', P...
kbd: ( P... ) => @_.tag 'kbd', P...
keygen: ( P... ) => @_.tag 'keygen', P...
label: ( P... ) => @_.tag 'label', P...
legend: ( P... ) => @_.tag 'legend', P...
link: ( P... ) => @_.tag 'link', P...
map: ( P... ) => @_.tag 'map', P...
mark: ( P... ) => @_.tag 'mark', P...
menu: ( P... ) => @_.tag 'menu', P...
menuitem: ( P... ) => @_.tag 'menuitem', P...
meta: ( P... ) => @_.tag 'meta', P...
meter: ( P... ) => @_.tag 'meter', P...
noscript: ( P... ) => @_.tag 'noscript', P...
object: ( P... ) => @_.tag 'object', P...
optgroup: ( P... ) => @_.tag 'optgroup', P...
option: ( P... ) => @_.tag 'option', P...
output: ( P... ) => @_.tag 'output', P...
param: ( P... ) => @_.tag 'param', P...
progress: ( P... ) => @_.tag 'progress', P...
q: ( P... ) => @_.tag 'q', P...
rb: ( P... ) => @_.tag 'rb', P...
rp: ( P... ) => @_.tag 'rp', P...
rt: ( P... ) => @_.tag 'rt', P...
rtc: ( P... ) => @_.tag 'rtc', P...
ruby: ( P... ) => @_.tag 'ruby', P...
s: ( P... ) => @_.tag 's', P...
samp: ( P... ) => @_.tag 'samp', P...
script: ( P... ) => @_.tag 'script', P...
select: ( P... ) => @_.tag 'select', P...
small: ( P... ) => @_.tag 'small', P...
source: ( P... ) => @_.tag 'source', P...
span: ( P... ) => @_.tag 'span', P...
strike: ( P... ) => @_.tag 'strike', P...
strong: ( P... ) => @_.tag 'strong', P...
style: ( P... ) => @_.tag 'style', P...
sub: ( P... ) => @_.tag 'sub', P...
summary: ( P... ) => @_.tag 'summary', P...
sup: ( P... ) => @_.tag 'sup', P...
tbody: ( P... ) => @_.tag 'tbody', P...
td: ( P... ) => @_.tag 'td', P...
template: ( P... ) => @_.tag 'template', P...
textarea: ( P... ) => @_.tag 'textarea', P...
tfoot: ( P... ) => @_.tag 'tfoot', P...
th: ( P... ) => @_.tag 'th', P...
thead: ( P... ) => @_.tag 'thead', P...
time: ( P... ) => @_.tag 'time', P...
title: ( P... ) => @_.tag 'title', P...
tr: ( P... ) => @_.tag 'tr', P...
track: ( P... ) => @_.tag 'track', P...
u: ( P... ) => @_.tag 'u', P...
var: ( P... ) => @_.tag 'var', P...
video: ( P... ) => @_.tag 'video', P...
wbr: ( P... ) => @_.tag 'wbr', P...
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
class @Specials extends @_Targeted_collection
doctype: ( type = 'html' ) => @_._cram @_raw 'doctype', type
# img: ( P... ) => XXXX @_.tag '!–', P...
#---------------------------------------------------------------------------------------------------------
raw: ( P... ) => validate.list_of 'text', P; @_raw 'raw', P...
text: ( P... ) => validate.list_of 'text', P; @_raw 'text', P...
comment: ( P... ) => validate.list_of 'text', P; @_raw 'raw', "<!-- #{P.join()} -->"
newline: ( P... ) => validate.list_of 'text', P; @_raw 'raw', "\n"
#---------------------------------------------------------------------------------------------------------
_raw: ( name, P... ) => @_._cram @_.DATOM.new_datom "^#{name}", { text: ( P.join '' ), $: 'ð1', }
#---------------------------------------------------------------------------------------------------------
link_css: ( href ) ->
### `<link rel=stylesheet href="../reset.css"/>` ###
unless ( arity = arguments.length ) is 1
throw new Error "^intertext/cupofhtml/link_css@2935^ expected 1 argument, got #{arity}"
validate.nonempty_text href
return @_._cram @_.DATOM.new_datom '^link', { rel: 'stylesheet', href, }
#---------------------------------------------------------------------------------------------------------
script: ( x ) ->
unless ( arity = arguments.length ) is 1
throw new Error "^intertext//cupofhtml/link_js@3502^ expected 1 argument, got #{arity}"
return switch type = type_of x
when 'text' then @_script_src x
when 'function' then @_script_literal x
throw new Error "^intertext/cupofhtml/script@4069^ expected a text or a function, got a #{type}"
#---------------------------------------------------------------------------------------------------------
_script_src: ( src ) ->
### `<script type="text/javascript" src="../jquery-3.4.1.js">` ###
validate.nonempty_text src
@_.cram 'script', { src, }
#---------------------------------------------------------------------------------------------------------
_script_literal: ( f ) ->
### `<script type="text/javascript"> var a, b; ...;</script>` ###
@_.cram 'script', => @raw "(#{f.toString()})();"
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
@_escape_text = ( x ) ->
R = x
R = R.replace /&/g, '&'
R = R.replace /</g, '<'
R = R.replace />/g, '>'
return R
#-----------------------------------------------------------------------------------------------------------
@_as_attribute_literal = ( x ) ->
R = if isa.text x then x else JSON.stringify x
must_quote = not isa._intertext_html_naked_attribute_text R
R = @_escape_text R
R = R.replace /'/g, '''
R = R.replace /\n/g, ' '
R = "'" + R + "'" if must_quote
return R
#-----------------------------------------------------------------------------------------------------------
@$html_from_datoms = ->
{ $, } = ( require 'steampipes' ).export()
return $ ( d, send ) =>
ds = if ( isa.list d ) then d else [ d, ]
for d in ds
send @_html_from_datom { newlines: false, }, d
return null
#-----------------------------------------------------------------------------------------------------------
@_html_from_datom = ( settings, d ) ->
### TAINT should not use `$key` for the tag name, rather, use `$key` to distinguish tags, texts ###
### TAINT make compatible with Paragate HTMLish parser ###
unless DATOM.types.isa.datom_datom d
unless isa.text d
throw new Error "^intertext/cupofhtml/_html_from_datom@4786^ unable to convert a #{type_of d} to HTML; got #{rpr d}"
d = { $key: '^text', text: d, }
#.........................................................................................................
atxt = ''
sigil = d.$key[ 0 ]
tagname = d.$key[ 1 .. ]
is_empty_tag = isa._intertext_html_empty_element_tagname tagname
x_key = null
is_block_tag = d.$blk ? false
if settings.newlines
bnl = if is_block_tag then '\n\n' else ''
xnl = '\n'
else
bnl = ''
xnl = ''
#.........................................................................................................
### TAINT simplistic solution; namespace might already be taken? ###
if sigil in '[~]'
switch sigil
when '[' then sigil = '<'
when '~' then sigil = '^'
when ']' then sigil = '>'
[ x_key, tagname, ] = [ tagname, 'x-sys', ]
#.........................................................................................................
return ( @_escape_text d.text ? '' ) if ( sigil is '^' ) and ( tagname is 'text' )
return ( d.text ? '' ) if ( sigil is '^' ) and ( tagname is 'raw' )
return "<!DOCTYPE #{d.$value ? 'html'}>#{xnl}" if ( sigil is '^' ) and ( tagname is 'doctype' )
return "</#{tagname}>#{bnl}" if sigil is '>'
#.........................................................................................................
### NOTE sorting atxt by keys to make result predictable: ###
if isa.object d.$value then src = d.$value
else src = d
atxt += " x-key=#{@_as_attribute_literal x_key}" if x_key?
for key in ( Object.keys src ).sort()
continue if key.startsWith '$'
if ( value = src[ key ] ) is true then atxt += " #{key}"
else atxt += " #{key}=#{@_as_attribute_literal value}"
#.........................................................................................................
### TAINT make self-closing elements configurable, depend on HTML5 type ###
slash = if ( sigil is '<' ) or is_empty_tag then '' else "</#{tagname}>#{bnl}"
x_sys_key = if x_key? then "<x-sys-key>#{x_key}</x-sys-key>" else ''
return "<#{tagname}>#{slash}#{x_sys_key}" if atxt is ''
return "<#{tagname}#{atxt}>#{x_sys_key}#{slash}"
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
############################################################################################################
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
class @Cupofhtml extends DATOM.Cupofdatom
# @include CUPOFHTML, { overwrite: false, }
# @extend MAIN, { overwrite: false, }
_defaults: _defaults
last_expansion: null
H: MAIN.Tags
S: MAIN.Specials
#---------------------------------------------------------------------------------------------------------
constructor: ( settings = null) ->
super { _defaults..., settings..., }
@H = new @H @
@S = new @S @
return @
#---------------------------------------------------------------------------------------------------------
expand: -> return @last_expansion = super()
#---------------------------------------------------------------------------------------------------------
new_tag: ( name, attributes ) ->
if attributes? then f = ( P... ) -> @_.tag name, attributes, P...
else f = ( P... ) -> @_.tag name, P...
@H[ name ] = f.bind @H
return null
#---------------------------------------------------------------------------------------------------------
tag: ( name, content... ) ->
validate.intertext_html_tagname name if name isnt null
@cram name, content...
#---------------------------------------------------------------------------------------------------------
as_html: -> return ( MAIN._html_from_datom @settings, d for d in @expand() ).join ''