<commit_before>package org.http4s
package server
package staticcontent

import cats.data.{Kleisli, OptionT}
import cats.effect.{ContextShift, Effect}
import scala.concurrent.ExecutionContext

/**
  * Constructs new services to serve assets from Webjars
  */
object WebjarService {

  /** [[org.http4s.server.staticcontent.WebjarService]] configuration
    *
    * @param blockingExecutionContext execution context for blocking I/O
    * @param filter To filter which assets from the webjars should be served
    * @param cacheStrategy strategy to use for caching purposes. Default to no caching.
    */
  final case class Config[F[_]](
      blockingExecutionContext: ExecutionContext,
      filter: WebjarAssetFilter = _ => true,
      cacheStrategy: CacheStrategy[F] = NoopCacheStrategy[F])

  /**
    * Contains the information about an asset inside a webjar
    *
    * @param library The webjar's library name
    * @param version The version of the webjar
    * @param asset The asset name inside the webjar
    */
  final case class WebjarAsset(library: String, version: String, asset: String) {

    /**
      * Constructs a full path for an asset inside a webjar asset
      *
      * @return The full name in the Webjar
      */
    private[staticcontent] lazy val pathInJar: String =
      s"/META-INF/resources/webjars/$library/$version/$asset"

  }

  /**
    * A filter callback for Webjar asset
    * It's a function that takes the WebjarAsset and returns whether or not the asset
    * should be served to the client.
    */
  type WebjarAssetFilter = WebjarAsset => Boolean

  /**
    * Creates a new [[HttpRoutes]] that will filter the webjars
    *
    * @param config The configuration for this service
    * @return The HttpRoutes
    */
  def apply[F[_]: Effect: ContextShift](config: Config[F]): HttpRoutes[F] = Kleisli {
    // Intercepts the routes that match webjar asset names
    case request if request.method == Method.GET =>
      OptionT
        .pure[F](request.pathInfo)
        .map(Uri.removeDotSegments)
        .subflatMap(toWebjarAsset)
        .filter(config.filter)
        .flatMap(serveWebjarAsset(config, request)(_))
    case _ => OptionT.none
  }

  /**
    * Returns an Option(WebjarAsset) for a Request, or None if it couldn't be mapped
    *
    * @param subPath The request path without the prefix
    * @return The WebjarAsset, or None if it couldn't be mapped
    */
  private def toWebjarAsset(subPath: String): Option[WebjarAsset] =
    Option(subPath)
      .map(_.split("/", 4))
      .collect {
        case Array("", library, version, asset)
            if library.nonEmpty && version.nonEmpty && asset.nonEmpty =>
          WebjarAsset(library, version, asset)
      }

  /**
    * Returns an asset that matched the request if it's found in the webjar path<commit_msg>qWebjarService: Simplify code<commit_after>package org.http4s
package server
package staticcontent

import cats.data.{Kleisli, OptionT}
import cats.effect.{ContextShift, Effect}
import scala.concurrent.ExecutionContext

/**
  * Constructs new services to serve assets from Webjars
  */
object WebjarService {

  /** [[org.http4s.server.staticcontent.WebjarService]] configuration
    *
    * @param blockingExecutionContext execution context for blocking I/O
    * @param filter To filter which assets from the webjars should be served
    * @param cacheStrategy strategy to use for caching purposes. Default to no caching.
    */
  final case class Config[F[_]](
      blockingExecutionContext: ExecutionContext,
      filter: WebjarAssetFilter = _ => true,
      cacheStrategy: CacheStrategy[F] = NoopCacheStrategy[F])

  /**
    * Contains the information about an asset inside a webjar
    *
    * @param library The webjar's library name
    * @param version The version of the webjar
    * @param asset The asset name inside the webjar
    */
  final case class WebjarAsset(library: String, version: String, asset: String) {

    /**
      * Constructs a full path for an asset inside a webjar asset
      *
      * @return The full name in the Webjar
      */
    private[staticcontent] lazy val pathInJar: String =
      s"/META-INF/resources/webjars/$library/$version/$asset"

  }

  /**
    * A filter callback for Webjar asset
    * It's a function that takes the WebjarAsset and returns whether or not the asset
    * should be served to the client.
    */
  type WebjarAssetFilter = WebjarAsset => Boolean

  /**
    * Creates a new [[HttpRoutes]] that will filter the webjars
    *
    * @param config The configuration for this service
    * @return The HttpRoutes
    */
  def apply[F[_]: Effect: ContextShift](config: Config[F]): HttpRoutes[F] = Kleisli {
    // Intercepts the routes that match webjar asset names
    case request if request.method == Method.GET =>
      val uri = Uri.removeDotSegments(request.pathInfo)
      toWebjarAsset(uri) match {
        case Some(asset) if config.filter(asset) =>
          serveWebjarAsset(config, request)(asset)
        case _ => OptionT.none
      }
    case _ => OptionT.none
  }

  /**
    * Returns an Option(WebjarAsset) for a Request, or None if it couldn't be mapped
    *
    * @param subPath The request path without the prefix
    * @return The WebjarAsset, or None if it couldn't be mapped
    */
  private def toWebjarAsset(subPath: String): Option[WebjarAsset] =
    Option(subPath)
      .map(_.split("/", 4))
      .collect {
        case Array("", library, version, asset)
            if library.nonEmpty && version.nonEmpty && asset.nonEmpty =>
          WebjarAsset(library, version, asset)
      }

  /**
    * Returns an asset that matched the request if it's found in the webjar path
{'fraction_non_alphanumeric': 0.09060579455662862, 'fraction_numerical': 0.0010535557506584723, 'mean_word_length': 3.0084447572132302, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5529470', 'n_tokens_mistral': 1621, 'n_tokens_neox': 1585, 'n_words': 559}