~jan0sch/smederee
Showing details for patch 29a5b88cc3fe438675a3ca2c1f7b6d323cd0a35f.
diff -rN -u old-smederee/build.sbt new-smederee/build.sbt --- old-smederee/build.sbt 2025-02-02 14:38:48.690082713 +0000 +++ new-smederee/build.sbt 2025-02-02 14:38:48.690082713 +0000 @@ -169,6 +169,7 @@ library.circeCore, library.circeGeneric, library.circeParser, + library.commonMark, library.doobieCore, library.doobieHikari, library.doobiePostgres, @@ -324,6 +325,7 @@ val cats = "2.8.0" val catsEffect = "3.3.14" val circe = "0.14.2" + val commonMark = "0.19.0" val doobie = "1.0.0-RC2" val flyway = "8.5.13" val http4s = "1.0.0-M35" @@ -349,6 +351,7 @@ val circeCore = "io.circe" %% "circe-core" % Version.circe val circeGeneric = "io.circe" %% "circe-generic" % Version.circe val circeParser = "io.circe" %% "circe-parser" % Version.circe + val commonMark = "org.commonmark" % "commonmark" % Version.commonMark val doobieCore = "org.tpolecat" %% "doobie-core" % Version.doobie val doobieHikari = "org.tpolecat" %% "doobie-hikari" % Version.doobie val doobiePostgres = "org.tpolecat" %% "doobie-postgres" % Version.doobie diff -rN -u old-smederee/modules/hub/src/main/resources/assets/css/main.css new-smederee/modules/hub/src/main/resources/assets/css/main.css --- old-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-02-02 14:38:48.690082713 +0000 +++ new-smederee/modules/hub/src/main/resources/assets/css/main.css 2025-02-02 14:38:48.690082713 +0000 @@ -60,6 +60,17 @@ background: rgb(223, 117, 20); } +dl.clone-links { + list-style-type: none; + list-style-position:inside; + margin: 0; + padding: 0; +} + +dl.clone-links dt { + font-weight: bold; +} + .home-menu { padding: 0.5em; text-align: center; diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 14:38:48.690082713 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-02 14:38:48.690082713 +0000 @@ -10,6 +10,7 @@ package de.smederee.hub import java.nio.file._ +import java.util.Locale import cats._ import cats.data._ @@ -19,6 +20,8 @@ import de.smederee.hub.RequestHelpers.instances.given import de.smederee.hub.config._ import de.smederee.hub.forms.types.FormErrors +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer import org.http4s._ import org.http4s.dsl.Http4sDsl import org.http4s.dsl.impl._ @@ -105,6 +108,33 @@ ) ) ) + directory <- Sync[F].delay( + os.Path( + Paths.get( + config.repositoriesDirectory.toPath.toString, + repositoryOwnerName.toString + ) + ) + ) + log <- darcs.log(directory.toNIO)(repositoryName.toString)(Chain(s"--max-count=3")) + readmeData <- repo.traverse(repo => doLoadReadme(repo)) + readme <- readmeData match { + case Some((lines, Some(filename))) => + if (filename.matches("(?iu).*\\.(md|markdown)$")) { + for { + parser <- Sync[F].delay(Parser.builder().build()) + markdown <- Sync[F].delay( + parser.parse(lines.mkString("\n")) + ) // Line breaks are important for markdown + renderer <- Sync[F].delay(HtmlRenderer.builder().escapeHtml(true).sanitizeUrls(true).build()) + html <- Sync[F].delay(renderer.render(markdown)) // TODO Correct links to repo files. + } yield html.some + } else { + Sync[F].delay(lines.mkString("\n").some) + } + case _ => Sync[F].delay(None) + } + readmeName = readmeData.flatMap(_._2) resp <- repo match { case None => NotFound("Repository not found!") case Some(repo) => @@ -114,7 +144,12 @@ csrf, s"Smederee/~$repositoryOwnerName/$repositoryName".some, user - )(repo.some) + )( + repo.some, + vcsRepositoryHistory = log.stdout.toList.mkString("\n").some, + vcsRepositoryReadme = readme, + vcsRepositoryReadmeFilename = readmeName + ) ) } } yield resp @@ -348,6 +383,33 @@ Sync[F].delay(IndexedSeq.empty) } yield listing + /** Load the content of the first file matching our "readme file" heuristic which does not exceed the + * maximum file size and return it. + * + * @param repo + * A repository in which we should search. + * @return + * A tuple containing a list of strings (lines) that may be empty and an option to the file name. + */ + private def doLoadReadme(repo: VcsRepository): F[(Vector[String], Option[String])] = + for { + path <- Sync[F].delay( + Paths.get(config.repositoriesDirectory.toPath.toString, repo.owner.name.toString, repo.name.toString) + ) + _ <- Sync[F].delay(log.debug(s"Trying to find README file in $path.")) + files <- Sync[F].delay(os.list(os.Path(path))) + readme = files.find(_.last.matches("(?iu)^readme(\\..+)?$")) + _ <- Sync[F].delay(log.debug(s"Found README at $readme.")) + size <- readme.traverse(path => Sync[F].delay(os.size(path))) + stream <- readme.traverse { path => + if (size.getOrElse(0L) <= MaximumFileSize) + Sync[F].delay(os.read.lines.stream(path)) + else + Sync[F].delay(os.Generator("")) + } + lines <- Sync[F].delay(stream.map(_.toVector).getOrElse(Vector.empty)) + } yield (lines, readme.map(_.last)) + private val cloneRepository: HttpRoutes[F] = HttpRoutes.of { case req @ GET -> UsernamePathParameter(repositoryOwnerName) /: VcsRepositoryClonePathParameter( repositoryName diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 14:38:48.690082713 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html 2025-02-02 14:38:48.690082713 +0000 @@ -1,4 +1,4 @@ -@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: Option[VcsRepository]) +@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, title: Option[String] = None, user: Option[Account])(vcsRepository: Option[VcsRepository], vcsRepositoryHistory: Option[String], vcsRepositoryReadme: Option[String] = None, vcsRepositoryReadmeFilename: Option[String] = None) @main(lang, pathPrefix)()(csrf, title, user) { @defining(lang.toLocale) { implicit locale => @for(repo <- vcsRepository) { @@ -18,22 +18,31 @@ </div> <div class="content"> <div class="pure-g"> - <div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-4"> - - <h3 class="content-subhead"><i class="fa-solid fa-file-code"></i>@Messages("landingpage.index.pitch.header.first")</h3> - <p>@Messages("landingpage.index.pitch.teaser.first")</p> + <div class="l-box pure-u-1 pure-u-md-1-2"> + <h3>Latest changes</h3> + <pre><code>@vcsRepositoryHistory</code></pre> </div> - <div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-4"> - <h3 class="content-subhead"><i class="fa-solid fa-circle-nodes"></i>@Messages("landingpage.index.pitch.header.second")</h3> - <p>@Messages("landingpage.index.pitch.teaser.second")</p> + <div class="l-box pure-u-1 pure-u-md-1-2"> + <h3>Clone</h3> + <dl class="clone-links"> + <dt>read-only</dt> + <dd><a href="@{createFullPath(pathPrefix)(actionBaseUri)}.darcs">@{createFullPath(pathPrefix)(actionBaseUri)}.darcs</a></dd> + <dt>read-write</dt> + <dd>TODO</dd> + </dl> </div> - <div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-4"> - <h3 class="content-subhead"><i class="fa-solid fa-code-compare"></i>@Messages("landingpage.index.pitch.header.third")</h3> - <p>@Messages("landingpage.index.pitch.teaser.third")</p> - </div> - <div class="l-box pure-u-1 pure-u-md-1-2 pure-u-lg-1-4"> - <h3 class="content-subhead"><i class="fa-solid fa-code-merge"></i>@Messages("landingpage.index.pitch.header.fourth")</h3> - <p>@Messages("landingpage.index.pitch.teaser.fourth")</p> + </div> + </div> + <div class="content"> + <div class="pure-g"> + <div class="l-box pure-u-1 pure-u-md-1-1 repo-summary-readme"> + @for(content <- vcsRepositoryReadme) { + @if(vcsRepositoryReadmeFilename.exists(_.matches("(?iu).*\\.(md|markdown)$"))) { + @Html(content) + } else { + @content + } + } </div> </div> </div>