Skip to content
Permalink
Browse files

Cleanup and updates to youi-stream

  • Loading branch information
darkfrog26 committed Feb 5, 2020
1 parent 9a8dffb commit dfcda4fb8e93b0cd7c67361d73dec3a687a7ba6f
@@ -7,7 +7,7 @@ import io.youi.http.content.{Content, FileContent, FormDataContent, StringConten
import io.youi.net.{ContentType, URL}
import io.youi.server.Server
import io.youi.server.handler.{CachingManager, HttpHandler, HttpHandlerBuilder, SenderHandler}
import io.youi.stream.{ByTag, Delta, HTMLParser, Selector}
import io.youi.stream.{Delta, HTMLParser, Selector}
import io.youi.{JavaScriptError, JavaScriptLog, Priority, http}
import net.sf.uadetector.UserAgentType
import net.sf.uadetector.service.UADetectorServiceFactory
@@ -198,7 +198,7 @@ trait ServerApplication extends YouIApplication with Server {
""
}
List(
Delta.InsertLastChild(ByTag("body"),
Delta.InsertLastChild(Selector.ByTag("body"),
s"""
|${scriptPaths.map(p => s"""<script src="$p"></script>""").mkString("\n")}
|${responseFields.mkString("\n")}
@@ -2,7 +2,7 @@ package io.youi

import io.youi.http.HttpConnection
import io.youi.server.dsl.{ConnectionFilter, DeltaKey, FilterResponse}
import io.youi.stream.{ByTag, Delta}
import io.youi.stream.{Selector, Delta}

import scala.concurrent.Future

@@ -17,7 +17,7 @@ package object app {
""
}
val applicationDeltas = List(
Delta.InsertLastChild(ByTag("body"),
Delta.InsertLastChild(Selector.ByTag("body"),
s"""
|$jsDeps
|<script src="${server.applicationJSPath}"></script>
@@ -4,7 +4,7 @@ import sbtcrossproject.CrossType

name := "youi"
organization in ThisBuild := "io.youi"
version in ThisBuild := "0.12.13"
version in ThisBuild := "0.12.14-SNAPSHOT"
scalaVersion in ThisBuild := "2.13.1"
crossScalaVersions in ThisBuild := List("2.13.1", "2.12.10")
resolvers in ThisBuild ++= Seq(
@@ -2,7 +2,7 @@ package io.youi

import java.io.File

import io.youi.stream.{ByClass, ById, ByTag, HTMLParser}
import io.youi.stream.{HTMLParser, Selector}
import org.scalajs.dom.Element
import profig.Profig

@@ -44,7 +44,7 @@ object TemplateMacros {
context.abort(context.enclosingPosition, s"Unable to find path for ${file.getAbsolutePath}.")
}
val parser = HTMLParser(file)
val template = parser.stream(Nil, selector = Some(ById(idValue)))
val template = parser.stream(Nil, selector = Some(Selector.ById(idValue)))
if (template.trim.isEmpty) {
context.abort(context.enclosingPosition, s"No content found for #$idValue in ${file.getAbsolutePath}")
}
@@ -85,7 +85,7 @@ object TemplateMacros {
context.abort(context.enclosingPosition, s"Unable to find path for ${file.getAbsolutePath}.")
}
val parser = HTMLParser(file)
val template = parser.stream(Nil, selector = Some(ByClass(classValue)), includeAllMatches = true)
val template = parser.stream(Nil, selector = Some(Selector.ByClass(classValue)), includeAllMatches = true)
if (template.trim.isEmpty) {
context.abort(context.enclosingPosition, s"No content found for .$classValue in ${file.getAbsolutePath}")
}
@@ -126,7 +126,7 @@ object TemplateMacros {
context.abort(context.enclosingPosition, s"Unable to find path for ${file.getAbsolutePath}.")
}
val parser = HTMLParser(file)
val template = parser.stream(Nil, selector = Some(ByTag(tagValue)), includeAllMatches = true)
val template = parser.stream(Nil, selector = Some(Selector.ByTag(tagValue)), includeAllMatches = true)
if (template.trim.isEmpty) {
context.abort(context.enclosingPosition, s"No content found for $tagValue in ${file.getAbsolutePath}")
}
@@ -10,6 +10,8 @@ import io.youi.stream._
import scala.collection.mutable.ListBuffer

object HTMLOptimizer {
import Selector._

private def createTempFile(fileType: String, extension: String): File = {
val temp = File.createTempFile(s"youi-optimizer-$fileType", s".$extension")
temp.deleteOnExit()
@@ -27,7 +29,7 @@ object HTMLOptimizer {
val stream = HTMLParser(input)
var scripts = ListBuffer.empty[ScriptFile]
val result = stream.stream(List(
Delta.Process(ByTag("script"), replace = true, onlyOpenTag = false, processor = (openTag: OpenTag, content: String) => {
Delta.Process(ByTag("script"), replace = true, onlyOpenTag = false, processor = (openTag: Tag.Open, content: String) => {
val script: ScriptFile = openTag.attributes.get("src") match {
case Some(src) => { // External script file
val minified = src.toLowerCase.endsWith(".min.js")
@@ -1,11 +1,13 @@
package io.youi.server.handler

import java.net.{URL, URLEncoder}
import java.nio.file.{Path, Paths}

import io.youi.http.content.Content
import io.youi.http.{HttpConnection, HttpStatus}
import io.youi.net.ContentType
import io.youi.stream.{ByMultiple, ByTag, Delta}
import io.youi.stream.Delta
import io.youi.stream.Selector._
import io.youi.stream._

import scala.concurrent.Future
@@ -0,0 +1,6 @@
package io.youi.stream

trait CacheBuilder {
def isStale: Boolean
def buildCache(): CachedInformation
}
@@ -0,0 +1,5 @@
package io.youi.stream

case class CachedInformation(byId: Map[String, Tag.Open],
byClass: Map[String, Set[Tag.Open]],
byTag: Map[String, Set[Tag.Open]])
@@ -2,18 +2,18 @@ package io.youi.stream

sealed trait Delta {
def selector: Selector
def apply(streamer: HTMLStream, tag: OpenTag): Unit
def apply(streamer: HTMLStream, tag: Tag.Open): Unit
}

class Replace(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.replace(tag.start, tag.close.map(_.end).getOrElse(tag.end), content())
}
}
class ReplaceAttribute(val selector: Selector, attributeName: String, val content: () => String) extends Delta {
private val AttributeRegex = s"""$attributeName="(.*?)"""".r

override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
val attribute = s"""$attributeName="${content()}""""
streamer.process(tag.start, tag.end, (block: String) => {
AttributeRegex.replaceAllIn(block, replacer => {
@@ -33,9 +33,9 @@ class ReplaceAttribute(val selector: Selector, attributeName: String, val conten
class Processor(val selector: Selector,
replace: Boolean,
onlyOpenTag: Boolean,
processor: (OpenTag, String) => String,
closeTagProcessor: Option[(OpenTag, CloseTag, String) => String]) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
processor: (Tag.Open, String) => String,
closeTagProcessor: Option[(Tag.Open, Tag.Close, String) => String]) extends Delta {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
val end = if (onlyOpenTag) {
tag.end
} else {
@@ -50,32 +50,32 @@ class Processor(val selector: Selector,
}
}
class InsertBefore(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.insert(tag.start, content())
}
}
class InsertFirstChild(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.insert(tag.end, content())
}
}
class ReplaceContent(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.replace(tag.end, tag.close.get.start, content())
}
}
class InsertLastChild(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.insert(tag.close.get.start, content())
}
}
class InsertAfter(val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.insert(tag.close.map(_.end).getOrElse(tag.end), content())
}
}
class Repeat[Data](val selector: Selector, data: List[Data], deltas: Data => List[Delta]) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
data.zipWithIndex.foreach {
case (d, index) => {
streamer.grouped(index) {
@@ -93,12 +93,12 @@ class Repeat[Data](val selector: Selector, data: List[Data], deltas: Data => Lis
}
}
class Template(val selector: Selector, deltas: List[Delta]) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
streamer.insert(tag.start, streamer.streamable.stream(deltas, Some(selector)))
}
}
class Grouped(val selector: Selector, deltas: List[Delta]) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
override def apply(streamer: HTMLStream, tag: Tag.Open): Unit = {
deltas.zipWithIndex.foreach {
case (d, index) => {
streamer.grouped(index) {
@@ -114,8 +114,8 @@ object Delta {
def Process(selector: Selector,
replace: Boolean,
onlyOpenTag: Boolean,
processor: (OpenTag, String) => String,
closeTagProcessor: Option[(OpenTag, CloseTag, String) => String] = None): Processor = new Processor(selector, replace, onlyOpenTag, processor, closeTagProcessor)
processor: (Tag.Open, String) => String,
closeTagProcessor: Option[(Tag.Open, Tag.Close, String) => String] = None): Processor = new Processor(selector, replace, onlyOpenTag, processor, closeTagProcessor)
def InsertBefore(selector: Selector, content: => String): InsertBefore = new InsertBefore(selector, () => content)
def InsertFirstChild(selector: Selector, content: => String): InsertFirstChild = new InsertFirstChild(selector, () => content)
def ReplaceContent(selector: Selector, content: => String): ReplaceContent = new ReplaceContent(selector, () => content)
@@ -63,22 +63,22 @@ object HTMLParser {
try {
val parser = new HTMLParser(input)
val tags = parser.parse()
var byId = Map.empty[String, OpenTag]
var byClass = Map.empty[String, Set[OpenTag]]
var byTag = Map.empty[String, Set[OpenTag]]
var byId = Map.empty[String, Tag.Open]
var byClass = Map.empty[String, Set[Tag.Open]]
var byTag = Map.empty[String, Set[Tag.Open]]
tags.foreach { tag =>
if (tag.attributes.contains("id")) {
byId += tag.attributes("id") -> tag
}
tag.attributes.getOrElse("class", "").split(" ").foreach { className =>
val cn = className.trim
if (cn.nonEmpty) {
var classTags = byClass.getOrElse(cn, Set.empty[OpenTag])
var classTags = byClass.getOrElse(cn, Set.empty[Tag.Open])
classTags += tag
byClass += cn -> classTags
}
}
var tagsByName = byTag.getOrElse(tag.tagName, Set.empty[OpenTag])
var tagsByName = byTag.getOrElse(tag.tagName, Set.empty[Tag.Open])
tagsByName += tag
byTag += tag.tagName -> tagsByName
}
@@ -104,11 +104,11 @@ class HTMLParser(input: InputStream) {
private var tagStart = -1
private var tagEnd = -1

private var open = List.empty[OpenTag]
private var tags = Set.empty[OpenTag]
private var open = List.empty[Tag.Open]
private var tags = Set.empty[Tag.Open]

@tailrec
final def parse(): Set[OpenTag] = input.read() match {
final def parse(): Set[Tag.Open] = input.read() match {
case -1 => tags // Finished
case i => {
val c = i.toChar
@@ -144,18 +144,18 @@ class HTMLParser(input: InputStream) {
private def parseTag(): Unit = b.toString() match {
case s if s.startsWith("<!--") || s.endsWith("-->") => // Ignore
case CloseTagRegex(tagName) => {
val closeTag = CloseTag(tagName, tagStart, tagEnd)
val closeTag = Tag.Close(tagName, tagStart, tagEnd)
val openTag = closeUntil(tagName).copy(close = Some(closeTag))
tags += openTag
}
case SelfClosingTagRegex(tagName, attributes) => {
val a = parseAttributes(attributes)
val tag = OpenTag(tagName, a, tagStart, tagEnd, close = None)
val tag = Tag.Open(tagName, a, tagStart, tagEnd, close = None)
tags += tag
}
case OpenTagRegex(tagName, attributes) => {
val a = parseAttributes(attributes)
val tag = OpenTag(tagName, a, tagStart, tagEnd, close = None)
val tag = Tag.Open(tagName, a, tagStart, tagEnd, close = None)
open = tag :: open
}
}
@@ -193,7 +193,7 @@ class HTMLParser(input: InputStream) {
}

@tailrec
private def closeUntil(tagName: String): OpenTag = {
private def closeUntil(tagName: String): Tag.Open = {
if (open.isEmpty) {
throw new RuntimeException(s"Missing close tag for $tagName!")
}
@@ -3,6 +3,8 @@ package io.youi.stream
import java.nio.channels.SeekableByteChannel

class HTMLStream(val streamable: StreamableHTML) {
import StreamAction._

private var actions = Set.empty[StreamAction]
private var group: Option[Group] = None

@@ -1,26 +1,26 @@
package io.youi.stream

sealed trait Selector {
def lookup(streamable: StreamableHTML): Set[OpenTag]
def lookup(streamable: StreamableHTML): Set[Tag.Open]
}

case class ById(id: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[OpenTag] = streamable.byId.get(id).toSet
}
object Selector {
case class ById(id: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[Tag.Open] = streamable.byId.get(id).toSet
}

case class ByClass(className: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[OpenTag] = streamable.byClass.getOrElse(className, Set.empty)
}
case class ByClass(className: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[Tag.Open] = streamable.byClass.getOrElse(className, Set.empty)
}

case class ByTag(tagName: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[OpenTag] = streamable.byTag.getOrElse(tagName, Set.empty)
}
case class ByTag(tagName: String) extends Selector {
override def lookup(streamable: StreamableHTML): Set[Tag.Open] = streamable.byTag.getOrElse(tagName, Set.empty)
}

case class ByMultiple(selectors: Selector*) extends Selector {
override def lookup(streamable: StreamableHTML): Set[OpenTag] = selectors.flatMap(_.lookup(streamable)).toSet
}
case class ByMultiple(selectors: Selector*) extends Selector {
override def lookup(streamable: StreamableHTML): Set[Tag.Open] = selectors.flatMap(_.lookup(streamable)).toSet
}

object Selector {
def parse(value: String): Selector = if (value.startsWith("#")) {
ById(value.substring(1))
} else if (value.startsWith(".")) {
@@ -5,14 +5,16 @@ sealed trait StreamAction {
def priority: Int
}

case class Insert(position: Int, content: String, priority: Int) extends StreamAction
object StreamAction {
case class Insert(position: Int, content: String, priority: Int) extends StreamAction

case class Skip(position: Int, end: Int, priority: Int) extends StreamAction
case class Skip(position: Int, end: Int, priority: Int) extends StreamAction

case class Reposition(position: Int, priority: Int) extends StreamAction
case class Reposition(position: Int, priority: Int) extends StreamAction

case class Group(actions: List[StreamAction], priority: Int) extends StreamAction {
lazy val position = actions.foldLeft(Int.MaxValue)((min, action) => math.min(min, action.position))
}
case class Group(actions: List[StreamAction], priority: Int) extends StreamAction {
lazy val position: Int = actions.foldLeft(Int.MaxValue)((min, action) => math.min(min, action.position))
}

case class Process(position: Int, end: Int, processor: String => String, priority: Int) extends StreamAction
case class Process(position: Int, end: Int, processor: String => String, priority: Int) extends StreamAction
}

0 comments on commit dfcda4f

Please sign in to comment.
You can’t perform that action at this time.