/hamlet.coffee

https://github.com/strathmeyer/hamlet.js · CoffeeScript · 172 lines · 138 code · 28 blank · 6 comment · 47 complexity · 26c0db7b798a3cc8ee516697dc465794 MD5 · raw file

  1. # * Hamlet Html Templates for javascript. http://www.yesodweb.com/book/templates
  2. # Re-uses some code from HTML Parser By John Resig (ejohn.org)
  3. # * LICENSE: Mozilla Public License
  4. # this one javascript function is _.template from underscore.js, MIT license
  5. # remove escape and evaluate, just use interpolate
  6. Hamlet = `function(str, data){
  7. var c = Hamlet.templateSettings;
  8. str = Hamlet.toHtml(str);
  9. var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
  10. 'with(obj||{}){__p.push(\'' +
  11. str.replace(/\\/g, '\\\\')
  12. .replace(/'/g, "\\'")
  13. .replace(c.interpolate, function(match, code) {
  14. return "'," + code.replace(/\\'/g, "'") + ",'";
  15. })
  16. .replace(/\r/g, '\\r')
  17. .replace(/\n/g, '\\n')
  18. .replace(/\t/g, '\\t')
  19. + "');}return __p.join('');";
  20. var func = new Function('obj', tmpl);
  21. return data ? func(data) : func;
  22. };
  23. `
  24. Hamlet.templateSettings = {
  25. interpolate : /\{\{([\s\S]+?)\}\}/g,
  26. }
  27. Hamlet.toHtml = (html) ->
  28. content = []
  29. tag_stack = []
  30. last_tag_indent = 0
  31. needs_space = false
  32. push_innerHTML = (str) ->
  33. if i = indexOf(str, '#')
  34. str = str.substring(0, i)
  35. needs_space = true
  36. content.push(str)
  37. for line in html.split(/\n\r*/)
  38. pos = 0
  39. pos += 1 while line[pos] == ' '
  40. unindented = line.substring(pos)
  41. if unindented.length == 0
  42. content.push(' ')
  43. else if unindented[0] == '#'
  44. else
  45. if pos <= last_tag_indent
  46. if tag_stack.length > 0 and pos == last_tag_indent
  47. [oldp, oldt] = tag_stack.pop()
  48. last_tag_indent = tag_stack[tag_stack.length - 1]?[0] || 0
  49. content.push("</#{oldt}>")
  50. while tag_stack.length > 0 and pos < last_tag_indent
  51. needs_space = false
  52. [oldp, oldt] = tag_stack.pop()
  53. last_tag_indent = tag_stack[tag_stack.length - 1]?[0] || 0
  54. content.push("</#{oldt}>")
  55. if unindented[0] == '>'
  56. unindented = unindented.substring(1)
  57. needs_space = false
  58. content.push(" ") if needs_space
  59. needs_space = false
  60. if unindented[0] != '<'
  61. push_innerHTML(unindented)
  62. else
  63. last_tag_indent = pos
  64. innerHTML = ""
  65. tag_portion = unindented.substring(1)
  66. if ti = indexOf(unindented, '>')
  67. tag_portion = unindented.substring(1, ti)
  68. if tag_portion[tag_portion.length] == "/"
  69. tag_portion = tag_portion.substring(innerHTML.length - 1)
  70. innerHTML = unindented.substring(ti + 1)
  71. tag_attrs = ""
  72. tag_name = tag_portion
  73. if si = indexOf(tag_portion, ' ')
  74. tag_name = tag_portion.substring(0, si)
  75. tag_attrs = tag_portion.substring(si)
  76. if tag_name[0] == '#'
  77. tag_attrs = "id=" + tag_name.substring(1) + tag_attrs
  78. tag_name = "div"
  79. if tag_name[0] == '.'
  80. tag_attrs = "class=" + tag_name.substring(1) + tag_attrs
  81. tag_name = "div"
  82. if emptyTags[tag_name]
  83. content.push("<#{tag_name}/>")
  84. else
  85. tag_stack.push([last_tag_indent, tag_name])
  86. if tag_attrs.length == 0
  87. content.push( "<#{tag_name}>")
  88. else
  89. content.push( "<#{tag_name}" +
  90. join_attrs(parse_attrs(tag_attrs)) + ">"
  91. )
  92. unless innerHTML.length == 0
  93. push_innerHTML(innerHTML)
  94. while tag_stack.length > 0
  95. [oldp, oldt] = tag_stack.pop()
  96. content.push("</#{oldt}>")
  97. content.join("")
  98. indexOf = (str, substr) ->
  99. i = str.indexOf(substr)
  100. if i == -1 then null else i
  101. makeMap = (str) ->
  102. obj = {}
  103. items = str.split(",")
  104. for i in items
  105. obj[ items[i] ] = true
  106. return obj
  107. attrMatch = /(?:\.|#)?([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g
  108. fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected")
  109. emptyTags = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed")
  110. parse_attrs = (html) ->
  111. attrs = []
  112. classes = []
  113. # TODO: more efficient function then replace? we don't need to replace
  114. html.replace attrMatch, (match, name) ->
  115. if match[0] == "."
  116. classes.push( name )
  117. else
  118. value = if match[0] == "#"
  119. val = name
  120. name = "id"
  121. val
  122. else
  123. arguments[2] || arguments[3] || arguments[4] ||
  124. if fillAttrs[name]
  125. name
  126. else
  127. ""
  128. if name == "class"
  129. classes.push( value )
  130. else
  131. attrs.push([name,
  132. value.replace(/(^|[^\\])"/g, '$1\\\"')
  133. ])
  134. return
  135. if classes.length > 0
  136. attrs.push(["class", classes.join(" ")])
  137. attrs
  138. join_attrs = (attrs) ->
  139. for attr in attrs
  140. " " + attr[0] + '="' + attr[1] + '"'