~jan0sch/smederee

Showing details for patch 29a5b88cc3fe438675a3ca2c1f7b6d323cd0a35f.
2022-08-30 (Tue), 2:44 PM - Jens Grassel - 29a5b88cc3fe438675a3ca2c1f7b6d323cd0a35f

VCS: More work on the overview page

- include commonmark-java for markdown rendering
- render readme file on overview page
  - markdown will be rendered to html
- include last 3 patches in the overview
- include clone links in the overview
Summary of changes
4 files modified with 101 lines added and 16 lines removed
  • build.sbt with 3 added and 0 removed lines
  • modules/hub/src/main/resources/assets/css/main.css with 11 added and 0 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala with 63 added and 1 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryOverview.scala.html with 24 added and 15 removed lines
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>