Merge pull request #4420 from isaacl/stringOpts

Optimize regexs throughout lila
pull/4423/head
Isaac Levy 2018-06-25 23:11:27 -04:00 committed by GitHub
commit 8dcddaa104
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 133 additions and 103 deletions

View File

@ -26,7 +26,7 @@ private[app] final class Renderer extends Actor {
case streams: lila.streamer.LiveStreams.WithTitles => sender ! V.streamer.liveStreams(streams)
}
private val spaceRegex = """\s{2,}""".r
private val spaceRegex = """\s{2,}+""".r
private def spaceless(html: Html) = Html {
spaceRegex.replaceAllIn(html.body.replace("\\n", " "), " ")
}

View File

@ -29,10 +29,12 @@ object Auth extends LilaController {
}
}
private val refRegex = """[\w@-/]++""".r
private def goodReferrer(referrer: String): Boolean = {
referrer.nonEmpty &&
referrer.stripPrefix("/") != "mobile" && {
"""(?:[\w@-]|(:?\/[\w@-]))*\/?""".r.matches(referrer) ||
(!referrer.contains("//") && refRegex.matches(referrer)) ||
referrer.startsWith(Env.oAuth.baseUrl)
}
}

View File

@ -25,7 +25,7 @@ object Editor extends LilaController {
def load(urlFen: String) = Open { implicit ctx =>
val fenStr = lila.common.String.decodeUriPath(urlFen)
.map(_.replace("_", " ").trim).filter(_.nonEmpty)
.map(_.replace('_', ' ').trim).filter(_.nonEmpty)
.orElse(get("fen"))
fuccess {
val situation = readFen(fenStr)

View File

@ -32,7 +32,7 @@ object UserAnalysis extends LilaController with TheftPrevention {
def load(urlFen: String, variant: Variant) = Open { implicit ctx =>
val decodedFen: Option[FEN] = lila.common.String.decodeUriPath(urlFen)
.map(_.replace("_", " ").trim).filter(_.nonEmpty)
.map(_.replace('_', ' ').trim).filter(_.nonEmpty)
.orElse(get("fen")) map FEN.apply
val pov = makePov(decodedFen, variant)
val orientation = get("color").flatMap(chess.Color.apply) | pov.color

View File

@ -31,15 +31,15 @@ trait StringHelper { self: NumberHelper =>
def when(cond: Boolean, str: String) = cond ?? str
private val NumberFirstRegex = """^(\d+)\s(.+)$""".r
private val NumberLastRegex = """^(.+)\s(\d+)$""".r
private val NumberFirstRegex = """(\d++)\s(.+)""".r
private val NumberLastRegex = """\s(\d++)$""".r.unanchored
def splitNumber(s: String)(implicit ctx: UserContext): Html = Html {
s match {
case NumberFirstRegex(number, text) =>
s"<strong>${(~parseIntOption(number)).localize}</strong><br />$text"
case NumberLastRegex(text, number) =>
s"$text<br /><strong>${(~parseIntOption(number)).localize}</strong>"
case h => h.replace("\n", "<br />")
case NumberLastRegex(n) if s.length > n.length + 1 =>
s"${s.dropRight(n.length + 1)}<br /><strong>${(~parseIntOption(n)).localize}</strong>"
case h => h.replaceIf('\n', "<br />")
}
}
def splitNumber(s: Html)(implicit ctx: UserContext): Html = splitNumber(s.body)

View File

@ -22,7 +22,7 @@ object ActivityHtml extends lila.Lilaisms {
if (p == 0) ""
else s"""<$tag>${wrapNumber(name.pluralSameTxt(p))}</$tag>"""
private val wrapNumberRegex = """(\d+)""".r
private val wrapNumberRegex = """(\d++)""".r
private def wrapNumber(str: String) =
wrapNumberRegex.replaceAllIn(str, "<strong>$1</strong>")
}

View File

@ -2,7 +2,7 @@
@url = {
@variant match {
case chess.variant.Standard => {https://en.wikipedia.org/wiki/Chess}
case chess.variant.FromPosition => {@routes.Editor.index?fen=@initialFen.map(_.value.replace(" ", "_"))}
case chess.variant.FromPosition => {@routes.Editor.index?fen=@initialFen.map(_.value.replace(' ', '_'))}
case v => {@routes.Page.variant(v.key)}
}
}

View File

@ -46,8 +46,8 @@ object Mobile {
)
)
private val PathPattern = """^.+/socket/v(\d+)$""".r
private val HeaderPattern = """^application/vnd\.lichess\.v(\d+)\+json$""".r
private val PathPattern = """/socket/v(\d++)$""".r.unanchored
private val HeaderPattern = """application/vnd\.lichess\.v(\d++)\+json""".r
def requestVersion(req: RequestHeader): Option[ApiVersion] = {
(req.headers.get(HeaderNames.ACCEPT), req.path) match {

View File

@ -50,7 +50,7 @@ final class PgnDump(
// merge analysis & eval comments
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }
s"$pgn\n\n\n".replace("] } { [", "] [")
s"$pgn\n\n\n".replaceIf("] } { [", "] [")
}
// def exportGamesFromIds(ids: List[String]): Enumerator[String] =

View File

@ -2,9 +2,9 @@ package lila.blog
object ProtocolFix {
private val RemoveRegex = """http://(\w{2}\.)?lichess\.org""".r
private val RemoveRegex = """http://(\w{2}\.)?+lichess\.org""".r
def remove(html: String) = RemoveRegex.replaceAllIn(html, _ => "//lichess.org")
private val AddRegex = """(https?:)?(//)?(\w{2}\.)?lichess\.org""".r
private val AddRegex = """(https?+:)?+(//)?+(\w{2}\.)?+lichess\.org""".r
def add(html: String) = AddRegex.replaceAllIn(html, _ => "https://lichess.org")
}

View File

@ -2,10 +2,10 @@ package lila.blog
object Youtube {
private val EmbedRegex = """youtube\.com/watch\?v=[\w-]+\#t=([^"]+).+\?feature=oembed""".r
private val HourMinSecRegex = """(\d+)h(\d+)m(\d+)s""".r
private val MinSecRegex = """(\d+)m(\d+)s""".r
private val SecRegex = """(\d+)s""".r
private val EmbedRegex = """youtube\.com/watch\?v=[\w-]++\#t=([^"]+).+\?feature=oembed""".r
private val HourMinSecRegex = """(\d++)h(\d++)m(\d++)s""".r
private val MinSecRegex = """(\d++)m(\d++)s""".r
private val SecRegex = """(\d++)s""".r
/*
* <div data-oembed="https://www.youtube.com/watch?v=uz-dZ2W4Bf0#t=4m14s" data-oembed-type="video" data-oembed-provider="youtube"><iframe width="480" height="270" src="https://www.youtube.com/embed/uz-dZ2W4Bf0?feature=oembed" frameborder="0" allowfullscreen></iframe></div>

View File

@ -209,28 +209,29 @@ final class ChatApi(
private object Writer {
import java.util.regex.Matcher.quoteReplacement
import java.util.regex.{ Pattern, Matcher }
def preprocessUserInput(in: String) = multiline(Spam.replace(noShouting(noPrivateUrl(in))))
def cut(text: String) = Some(text.trim take Line.textMaxSize) filter (_.nonEmpty)
private val domainRegex = netDomain.replace(".", """\.""")
private val gameUrlRegex = (domainRegex + """\b/([\w]{8})[\w]{4}\b""").r
private def noPrivateUrl(str: String): String =
gameUrlRegex.replaceAllIn(str, m => quoteReplacement(netDomain + "/" + (m group 1)))
private val multilineRegex = """\n{3,}""".r
private val gameUrlRegex = (Pattern.quote(netDomain) + """\b/(\w{8})\w{4}\b""").r
private val gameUrlReplace = Matcher.quoteReplacement(netDomain) + "/$1";
private def noPrivateUrl(str: String): String = gameUrlRegex.replaceAllIn(str, gameUrlReplace)
private def noShouting(str: String): String = if (isShouting(str)) str.toLowerCase else str
private val multilineRegex = """\n{3,}+""".r
private def multiline(str: String) = multilineRegex.replaceAllIn(str, """\n\n""")
}
private object noShouting {
import java.lang.Character.isUpperCase
private val onlyLettersRegex = """[^\w]""".r
def apply(text: String) = if (text.size < 5) text else {
val onlyLetters = onlyLettersRegex.replaceAllIn(text take 80, "")
if (onlyLetters.count(isUpperCase) > onlyLetters.size / 2)
text.toLowerCase
else text
}
private def isShouting(text: String) = text.length >= 5 && {
import java.lang.Character._
// true if >1/2 of the latin letters are uppercase
(text take 80).foldLeft(0) { (i, c) =>
getType(c) match {
case UPPERCASE_LETTER => i + 1
case LOWERCASE_LETTER => i - 1
case _ => i
}
} > 0
}
}

View File

@ -54,7 +54,7 @@ object Line {
def write(x: Line) = BSONString(lineToStr(x))
}
private val UserLineRegex = """^(?s)([\w-]{2,})(.)(.+)$""".r
private val UserLineRegex = """(?s)([\w-]{2,}+)([ !?])(.++)""".r
def strToUserLine(str: String): Option[UserLine] = str match {
case UserLineRegex(username, " ", text) => UserLine(username, text, troll = false, deleted = false).some
case UserLineRegex(username, "!", text) => UserLine(username, text, troll = true, deleted = false).some

View File

@ -11,7 +11,7 @@ object UrlList {
def apply(text: String): List[Url] =
text.lines.toList.map(_.trim).filter(_.nonEmpty) flatMap toUrl take max
private val UrlRegex = """.*(?:youtube\.com|youtu\.be)/(?:watch)?(?:\?v=)?([^"&?\/ ]{11}).*""".r
private val UrlRegex = """(?:youtube\.com|youtu\.be)/(?:watch)?(?:\?v=)?([^"&?/ ]{11})""".r.unanchored
/*
* https://www.youtube.com/watch?v=wEwoyYp_iw8
@ -29,7 +29,7 @@ object UrlList {
case class StudyId(value: String) extends AnyVal
private val UrlRegex = """.*(?:lichess\.org)/study/(\w+{8}).*$""".r
private val UrlRegex = """(?:lichess\.org)/study/(\w{8})""".r.unanchored
def apply(text: String): List[StudyId] =
text.lines.toList.map(_.trim).filter(_.nonEmpty) flatMap toId take max

View File

@ -44,11 +44,10 @@ object HTTPRequest {
"""coccoc|integromedb|contentcrawlerspider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler\.com|siteexplorer\.info|elisabot|proximic|changedetection|blexbot|arabot|wesee:search|niki-bot|crystalsemanticsbot|rogerbot|360spider|psbot|interfaxscanbot|lipperheyseoservice|ccmetadatascaper|g00g1e\.net|grapeshotcrawler|urlappendbot|brainobot|fr-crawler|binlar|simplecrawler|simplecrawler|livelapbot|twitterbot|cxensebot|smtbot|facebookexternalhit|daumoa|sputnikimagebot|visionutils|yisouspider|parsijoobot|mediatoolkit\.com|semrushbot""")
}
case class UaMatcher(regex: String) {
val pattern = regex.r.pattern
case class UaMatcher(rStr: String) {
private val regex = rStr.r
def apply(req: RequestHeader): Boolean =
userAgent(req) ?? { ua => pattern.matcher(ua).find }
def apply(req: RequestHeader): Boolean = userAgent(req) ?? { regex.find(_) }
}
def isFishnet(req: RequestHeader) = req.path startsWith "/fishnet/"
@ -59,10 +58,9 @@ object HTTPRequest {
ua.contains("facebookexternalhit/") || ua.contains("twitterbot/")
}
private val fileExtensionPattern = """.+\.[a-z0-9]{2,4}$""".r.pattern
private[this] val fileExtensionRegex = """\.(?<!^\.)[a-z0-9]{2,4}$""".r
def hasFileExtension(req: RequestHeader) =
fileExtensionPattern.matcher(req.path).matches
def hasFileExtension(req: RequestHeader) = fileExtensionRegex.find(req.path)
def weirdUA(req: RequestHeader) = userAgent(req).fold(true)(_.size < 30)

View File

@ -5,16 +5,16 @@ object LameName {
def username(name: String) =
anyName(name) || lameTitlePrefix.matcher(name).lookingAt
def anyName(name: String) = lameWords.matcher(name).find
def anyName(name: String) = lameWords.find(name)
def anyNameButLichessIsOk(name: String) = lameWords.matcher {
def anyNameButLichessIsOk(name: String) = lameWords find {
lichessRegex.replaceAllIn(name, "")
}.find
}
private val lichessRegex = "(?i)lichess".r
private val lameTitlePrefix =
"[Ww]?[NCFIGl1L]M|(?i:w?[ncfigl1])m[-_A-Z0-9]".r.pattern
"[Ww]?+[NCFIGl1L]M|(?i:w?+[ncfigl1])m[-_A-Z0-9]".r.pattern
private val lameWords = {
val extras = Map(
@ -73,6 +73,6 @@ object LameName {
"cuck"
).map {
_.map(subs).map(_ + "+").mkString
}.mkString("|").r.pattern
}.mkString("|").r
}
}

View File

@ -1,16 +1,14 @@
package lila.common
import java.util.regex.Matcher.quoteReplacement
import ornicar.scalalib.Random
import play.api.mvc.{ Cookie, DiscardingCookie, Session, RequestHeader }
object LilaCookie {
private val domainRegex = """^.+(\.[^\.]+\.[^\.]+)$""".r
private val domainRegex = """\.[^.]++\.[^.]++$""".r
private def domain(req: RequestHeader): String =
domainRegex.replaceAllIn(req.domain, m => quoteReplacement(m group 1))
domainRegex.findFirstIn(req.domain).getOrElse(req.domain)
val sessionId = "sid"

View File

@ -55,6 +55,7 @@ trait Lilaisms
@inline implicit def toPimpedTryList[A](l: List[Try[A]]) = new PimpedTryList(l)
@inline implicit def toPimpedList[A](l: List[A]) = new PimpedList(l)
@inline implicit def toPimpedSeq[A](l: Seq[A]) = new PimpedSeq(l)
@inline implicit def toPimpedChars(i: Iterable[CharSequence]) = new PimpedChars(i)
@inline implicit def toPimpedByteArray(ba: Array[Byte]) = new PimpedByteArray(ba)
@inline implicit def toPimpedOption[A](a: Option[A]) = new PimpedOption(a)

View File

@ -1,6 +1,7 @@
package lila.base
import java.util.Base64
import java.lang.{ StringBuilder => jStringBuilder }
import scala.util.Try
final class PimpedTryList[A](private val list: List[Try[A]]) extends AnyVal {
@ -13,6 +14,23 @@ final class PimpedList[A](private val list: List[A]) extends AnyVal {
}
}
final class PimpedChars(private val iter: Iterable[CharSequence]) extends AnyVal {
def concat: String = {
val it = iter.iterator
if (it.hasNext) {
val first = it.next
if (it.hasNext) {
val sb = new jStringBuilder(first)
do {
sb.append(it.next)
} while (it.hasNext)
sb
} else first
}.toString
else ""
}
}
final class PimpedSeq[A](private val seq: Seq[A]) extends AnyVal {
def has(a: A) = seq contains a
}

View File

@ -43,6 +43,15 @@ final class PimpedString(private val s: String) extends AnyVal {
def boot[A](v: => A): A = lila.common.Chronometer.syncEffect(v) { lap =>
lila.log.boot.info(s"${lap.millis}ms $s")
}
def replaceIf(t: Char, r: Char): String =
if (s.indexOf(t) >= 0) s.replace(t, r) else s
def replaceIf(t: Char, r: CharSequence): String =
if (s.indexOf(t) >= 0) s.replace(String.valueOf(t), r) else s
def replaceIf(t: CharSequence, r: CharSequence): String =
if (s.contains(t)) s.replace(t, r) else s
}
final class PimpedConfig(private val config: Config) extends AnyVal {

View File

@ -18,7 +18,7 @@ object IpAddress {
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
private val ipv4Regex = """^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
// ipv6 address in standard form (no compression, no leading zeros)
private val ipv6Regex = """^((0|[1-9a-f][0-9a-f]{0,3}):){7}(0|[1-9a-f][0-9a-f]{0,3})""".r
private val ipv6Regex = """^((0|[1-9a-f][0-9a-f]{0,3}+):){7}(0|[1-9a-f][0-9a-f]{0,3})""".r
def isv4(a: IpAddress) = ipv4Regex matches a.value
def isv6(a: IpAddress) = ipv6Regex matches a.value
@ -29,16 +29,16 @@ object IpAddress {
}
case class EmailAddress(value: String) extends AnyVal with StringValue {
def isHotmail = EmailAddress.hotmailPattern.matcher(value).find
def isHotmail = EmailAddress.hotmailRegex.find(value)
}
object EmailAddress {
private val pattern =
"""^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r.pattern
private val regex =
"""^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]++@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r
def from(str: String): Option[EmailAddress] =
pattern.matcher(str).find option EmailAddress(str)
regex.find(str) option EmailAddress(str)
private val hotmailPattern = """(.*)@(live|hotmail|outlook)\.(.*)""".r.pattern
private val hotmailRegex = """@(live|hotmail|outlook)\.""".r
}

View File

@ -689,7 +689,7 @@ object mon {
private val stripVersionRegex = """[^\w\.\-]""".r
private def stripVersion(v: String) = stripVersionRegex.replaceAllIn(v, "")
private def nodots(s: String) = s.replace(".", "_")
private def nodots(s: String) = s.replace('.', '_')
private val makeVersion = nodots _ compose stripVersion _
private val logger = lila.log("monitor")

View File

@ -36,7 +36,7 @@ final class Photographer(coll: Coll, prefix: String) {
private def sanitizeName(name: String) = {
// the char `^` breaks play, even URL encoded
java.net.URLEncoder.encode(name, "UTF-8").replace("%", "")
java.net.URLEncoder.encode(name, "UTF-8").replaceIf('%', "")
}
}

View File

@ -42,18 +42,23 @@ object DataForm {
) {
def looksLikeVenting = List(name, post.text) exists { txt =>
mostlyUpperCase(txt) || ventingPattern.matcher(txt).find
mostlyUpperCase(txt) || ventingRegex.find(txt)
}
}
private def mostlyUpperCase(txt: String) = {
val extract = txt.take(300)
(extract.contains(' ') || extract.size > 5) && {
extract.count(_.isUpper) > extract.count(_.isLower) * 2
}
private def mostlyUpperCase(text: String) = text.length > 5 && {
import java.lang.Character._
// true if >2/3 of the latin letters are upper
(text take 300).foldLeft(0) { (i, c) =>
getType(c) match {
case UPPERCASE_LETTER => i + 1
case LOWERCASE_LETTER => i - 2
case _ => i
}
} > 0
}
private val ventingPattern = """cheat|engine|rating|loser|banned|abort""".r.pattern
private val ventingRegex = """cheat|engine|rating|loser|banned|abort""".r
case class PostEdit(changes: String)
}

View File

@ -5,7 +5,7 @@ import lila.user.{ User, UserContext }
trait Granter {
private val TeamSlugPattern = """^team-([\w-]+)$""".r
private val TeamSlugPattern = """team-([\w-]++)""".r
protected def userBelongsToTeam(teamId: String, userId: String): Fu[Boolean]
protected def userOwnsTeam(teamId: String, userId: String): Fu[Boolean]

View File

@ -90,7 +90,7 @@ case class Player(
object Player {
private val nameSplitRegex = """^([^\(]+)\((.+)\)$""".r
private val nameSplitRegex = """([^(]++)\((\d++)\)""".r
def make(
color: Color,

View File

@ -52,7 +52,7 @@ private[gameSearch] final class DataForm {
private[gameSearch] object DataForm {
val DateDelta = """^(\d+)(\w)$""".r
val DateDelta = """\d++[a-z]""".r
private val dateConstraint = Constraints.pattern(
regex = DateDelta,
error = "Invalid date."

View File

@ -41,7 +41,7 @@ private[i18n] final class JsDump(path: String) {
finally { out.close }
}
private def escape(text: String) = text.replace(""""""", """\"""")
private def escape(text: String) = text.replaceIf('"', "\\\"")
}
object JsDump {

View File

@ -103,7 +103,7 @@ private final class PoolActor(
}
val monitor = lila.mon.lobby.pool
val monId = config.id.value.replace("+", "_")
val monId = config.id.value.replace('+', '_')
}
private object PoolActor {

View File

@ -11,14 +11,14 @@ object PracticeGoal {
case class EvalIn(cp: Int, nbMoves: Int) extends PracticeGoal
case class Promotion(cp: Int) extends PracticeGoal
private val MateR = """(?i)^(?:check)?mate$""".r
private val MateInR = """(?i)^(?:check)?mate in (\d+)$""".r
private val DrawInR = """(?i)^draw in (\d+)$""".r
private val EqualInR = """(?i)^equal(?:ize)? in (\d+)$""".r
private val EvalInR = """(?i)^((?:\+|-|)\d+)cp in (\d+)$""".r
private val PromotionR = """(?i)^promotion with ((?:\+|-|)\d+)cp$""".r
private val MateR = """(?i)(?:check)?+mate""".r
private val MateInR = """(?i)(?:check)?+mate in (\d++)""".r
private val DrawInR = """(?i)draw in (\d++)""".r
private val EqualInR = """(?i)equal(?:ize)?+ in (\d++)""".r
private val EvalInR = """(?i)((?:\+|-|)\d++)cp in (\d++)""".r
private val PromotionR = """(?i)promotion with ((?:\+|-|)\d++)cp""".r
private val MultiSpaceR = """\s{2,}""".r
private val MultiSpaceR = """\s{2,}+""".r
def apply(chapter: lila.study.Chapter): PracticeGoal =
chapter.tags(_.Termination).map(v => MultiSpaceR.replaceAllIn(v.trim, " ")).flatMap {

View File

@ -157,6 +157,6 @@ object Report {
)
}
private val farmWithRegex = s""".+ points from @(${User.historicalUsernameRegex.pattern}) .*""".r
private val sandbagWithRegex = s""".+ winning player @(${User.historicalUsernameRegex.pattern}) .*""".r
private val farmWithRegex = s""". points from @(${User.historicalUsernameRegex.pattern}) """.r.unanchored
private val sandbagWithRegex = s""". winning player @(${User.historicalUsernameRegex.pattern}) """.r.unanchored
}

View File

@ -36,11 +36,11 @@ private final class ReportScore(
def flagScore(user: User) =
(user.lameOrTroll) ?? -30d
private val gamePattern = """lichess.org/(\w{8,12})""".r.pattern
private val gameRegex = """lichess.org/\w{8,12}""".r
def textScore(reason: Reason, text: String) = {
(reason == Reason.Cheat || reason == Reason.Boost) &&
gamePattern.matcher(text).find
gameRegex.find(text)
} ?? 20
// https://github.com/ornicar/lila/issues/4093

View File

@ -78,8 +78,8 @@ object Streamer {
def minUrl = s"twitch.tv/$userId"
}
object Twitch {
private val UserIdRegex = """^([\w-]{2,24})$""".r
private val UrlRegex = """.*twitch\.tv/([\w-]{2,24}).*""".r
private val UserIdRegex = """([\w-]{2,24}+)""".r
private val UrlRegex = """twitch\.tv/([\w-]{2,24}+)""".r.unanchored
// https://www.twitch.tv/chessnetwork
def parseUserId(str: String): Option[String] = str match {
case UserIdRegex(u) => u.some
@ -94,7 +94,7 @@ object Streamer {
}
object YouTube {
private val ChannelIdRegex = """^([\w-]{24})$""".r
private val UrlRegex = """.*youtube\.com/channel/([\w-]{24}).*""".r
private val UrlRegex = """youtube\.com/channel/([\w-]{24})""".r.unanchored
def parseChannelId(str: String): Option[String] = str match {
case ChannelIdRegex(c) => c.some
case UrlRegex(c) => c.some

View File

@ -152,8 +152,8 @@ object Chapter {
def defaultName(order: Int) = Name(s"Chapter $order")
private val defaultNamePattern = """^Chapter \d+$""".r.pattern
def isDefaultName(n: Name) = n.value.isEmpty || defaultNamePattern.matcher(n.value).matches
private val defaultNameRegex = """Chapter \d+""".r
def isDefaultName(n: Name) = n.value.isEmpty || defaultNameRegex.matches(n.value)
def fixName(n: Name) = Name(n.value.trim take 80)

View File

@ -146,8 +146,8 @@ private final class ChapterMaker(
private val UrlRegex = {
val escapedDomain = domain.replace(".", "\\.")
s""".*$escapedDomain/(\\w{8,12}).*"""
}.r
s"""$escapedDomain/(\\w{8,12})"""
}.r.unanchored
private def parsePov(str: String): Fu[Option[Pov]] = str match {
case s if s.size == Game.gameIdSize => GameRepo.pov(s, chess.White)

View File

@ -6,13 +6,13 @@ import lila.tree.Node.{ Shape, Shapes }
private[study] object CommentParser {
private val circlesRegex = """(?s).*\[\%csl[\s\r\n]+((?:\w{3}[,\s]*)+)\].*""".r
private val circlesRegex = """(?s)\[\%csl[\s\r\n]+((?:\w{3}[,\s]*)+)\]""".r.unanchored
private val circlesRemoveRegex = """\[\%csl[\s\r\n]+((?:\w{3}[,\s]*)+)\]""".r
private val arrowsRegex = """(?s).*\[\%cal[\s\r\n]+((?:\w{5}[,\s]*)+)\].*""".r
private val arrowsRegex = """(?s)\[\%cal[\s\r\n]+((?:\w{5}[,\s]*)+)\]""".r.unanchored
private val arrowsRemoveRegex = """\[\%cal[\s\r\n]+((?:\w{5}[,\s]*)+)\]""".r
private val clockRegex = """(?s).*\[\%clk[\s\r\n]+([\d:\.]+)\].*""".r
private val clockRegex = """(?s)\[\%clk[\s\r\n]+([\d:\.]+)\]""".r.unanchored
private val clockRemoveRegex = """\[\%clk[\s\r\n]+[\d:\.]+\]""".r
private val tcecClockRegex = """(?s).*tl=([\d:\.]+).*""".r
private val tcecClockRegex = """(?s)tl=([\d:\.]+)""".r.unanchored
private val tcecClockRemoveRegex = """tl=[\d:\.]+""".r
case class ParsedComment(
@ -36,8 +36,8 @@ private[study] object CommentParser {
s <- parseIntOption(seconds)
} yield Centis(h * 360000 + m * 6000 + s * 100)
private val clockHourMinuteRegex = """^(\d+):(\d+)$""".r
private val clockHourMinuteSecondRegex = """^(\d+):(\d+)[:\.](\d+)$""".r
private val clockHourMinuteRegex = """^(\d++):(\d+)$""".r
private val clockHourMinuteSecondRegex = """^(\d++):(\d++)[:\.](\d+)$""".r
def readCentis(str: String): Option[Centis] = str match {
case clockHourMinuteRegex(hours, minutes) => readCentis(hours, minutes, "0")

View File

@ -11,7 +11,7 @@ private final class PgnFetch {
// http://www.chessgames.com/perl/chessgame?gid=1427487
// http://www.chessgames.com/perl/nph-chesspgn?text=1&gid=1427487
// http://www.chessgames.com/pgn/boleslavsky_ufimtsev_1944.pgn?gid=1427487
private val ChessbaseRegex = """.*chessgames\.com/.*[\?&]gid=(\d+).*""".r
private val ChessbaseRegex = """chessgames\.com/.*[\?&]gid=(\d+)""".r.unanchored
def fromUrl(url: String): Fu[Option[Pgn]] = url match {
case ChessbaseRegex(id) => parseIntOption(id) ?? downloadChessbase

View File

@ -4,7 +4,7 @@ object Links {
def make(text: String): List[Link] = text.lines.toList.map(_.trim) flatMap toLink
private val UrlRegex = """^(?:http[s]?:\/\/)?([^/]+)/?.*$""".r
private val UrlRegex = """^(?:https?://)?+([^/]+)""".r.unanchored
private def toLink(line: String): Option[Link] = line match {
case UrlRegex(domain) => Link(

View File

@ -151,8 +151,6 @@ object User {
val anonymous = "Anonymous"
val lichessId = "lichess"
val idPattern = """^[\w-]{3,20}$""".r.pattern
case class GDPRErase(user: User) extends AnyVal
case class Erased(value: Boolean) extends AnyVal

View File

@ -29,7 +29,7 @@ case class UserControl(
)
def queryString = List(
filter.tags.nonEmpty option s"tags=${filter.tags.sorted mkString "/"}".replace(" ", "+"),
filter.tags.nonEmpty option s"tags=${filter.tags.sorted mkString "/"}".replace(' ', '+'),
query.map { q => s"q=$q" }
).flatten mkString "&"