All Downloads are FREE. Search and download functionalities are using the official Maven repository.

coursier.ivy.Pattern.scala Maven / Gradle / Ivy

The newest version!
package coursier.ivy

import scala.annotation.tailrec

import scalaz._

import scala.util.matching.Regex
import java.util.regex.Pattern.quote

object Pattern {

  val propertyRegex = (quote("${") + "[^" + quote("{[()]}") + "]*" + quote("}")).r
  val optionalPartRegex = (quote("(") + "[^" + quote("{()}") + "]*" + quote(")")).r
  val variableRegex = (quote("[") + "[^" + quote("{[()]}") + "]*" + quote("]")).r

  sealed abstract class PatternPart(val effectiveStart: Int, val effectiveEnd: Int) extends Product with Serializable {
    require(effectiveStart <= effectiveEnd)
    def start = effectiveStart
    def end = effectiveEnd

    // FIXME Some kind of validation should be used here, to report all the missing variables,
    //       not only the first one missing.
    def apply(content: String): Map[String, String] => String \/ String
  }
  object PatternPart {
    final case class Literal(override val effectiveStart: Int, override val effectiveEnd: Int) extends PatternPart(effectiveStart, effectiveEnd) {
      def apply(content: String): Map[String, String] => String \/ String = {
        assert(content.length == effectiveEnd - effectiveStart)
        val matches = variableRegex.findAllMatchIn(content).toList

        variables =>
          @tailrec
          def helper(idx: Int, matches: List[Regex.Match], b: StringBuilder): String \/ String =
            if (idx >= content.length)
              \/-(b.result())
            else {
              assert(matches.headOption.forall(_.start >= idx))
              matches.headOption.filter(_.start == idx) match {
                case Some(m) =>
                  val variableName = content.substring(m.start + 1, m.end - 1)
                  variables.get(variableName) match {
                    case None => -\/(s"Variable not found: $variableName")
                    case Some(value) =>
                      b ++= value
                      helper(m.end, matches.tail, b)
                  }
                case None =>
                  val nextIdx = matches.headOption.fold(content.length)(_.start)
                  b ++= content.substring(idx, nextIdx)
                  helper(nextIdx, matches, b)
              }
            }

          helper(0, matches, new StringBuilder)
      }
    }
    final case class Optional(start0: Int, end0: Int) extends PatternPart(start0 + 1, end0 - 1) {
      override def start = start0
      override def end = end0

      def apply(content: String): Map[String, String] => String \/ String = {
        assert(content.length == effectiveEnd - effectiveStart)
        val inner = Literal(effectiveStart, effectiveEnd).apply(content)

        variables =>
          \/-(inner(variables).fold(_ => "", x => x))
      }
    }
  }


  def substituteProperties(s: String, properties: Map[String, String]): String =
    propertyRegex.findAllMatchIn(s).toVector.foldRight(s) { case (m, s0) =>
      val key = s0.substring(m.start + "${".length, m.end - "}".length)
      val value = properties.getOrElse(key, "")
      s0.take(m.start) + value + s0.drop(m.end)
    }

}

final case class Pattern(
  pattern: String,
  properties: Map[String, String]
) {

  import Pattern._

  private val pattern0 = substituteProperties(pattern, properties)

  val parts = {
    val optionalParts = optionalPartRegex.findAllMatchIn(pattern0).toList.map { m =>
      PatternPart.Optional(m.start, m.end)
    }

    val len = pattern0.length

    @tailrec
    def helper(
      idx: Int,
      opt: List[PatternPart.Optional],
      acc: List[PatternPart]
    ): Vector[PatternPart] =
      if (idx >= len)
        acc.toVector.reverse
      else
        opt match {
          case Nil =>
            helper(len, Nil, PatternPart.Literal(idx, len) :: acc)
          case (opt0 @ PatternPart.Optional(start0, end0)) :: rem =>
            if (idx < start0)
              helper(start0, opt, PatternPart.Literal(idx, start0) :: acc)
            else {
              assert(idx == start0, s"idx: $idx, start0: $start0")
              helper(end0, rem, opt0 :: acc)
            }
        }

    helper(0, optionalParts, Nil)
  }

  assert(pattern0.isEmpty == parts.isEmpty)
  if (pattern0.nonEmpty) {
    for ((a, b) <- parts.zip(parts.tail))
      assert(a.end == b.start)
    assert(parts.head.start == 0)
    assert(parts.last.end == pattern0.length)
  }

  private val substituteHelpers = parts.map { part =>
    part(pattern0.substring(part.effectiveStart, part.effectiveEnd))
  }

  def substitute(variables: Map[String, String]): String \/ String =
    substituteHelpers.foldLeft[String \/ String](\/-("")) {
      case (acc0, helper) =>
        for {
          acc <- acc0
          s <- helper(variables)
        } yield acc + s
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy