diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 85488d9..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/publish-blog.yml b/.github/workflows/publish-blog.yml new file mode 100644 index 0000000..ce30f63 --- /dev/null +++ b/.github/workflows/publish-blog.yml @@ -0,0 +1,63 @@ +name: Publish Blog to GitHub Pages + +on: + push: + branches: [ main, hkt ] + paths: + - 'src/main/resources/blog/**' + - 'src/main/scala/blog/**' + - '.github/workflows/publish-blog.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'sbt' + + - name: Install SBT + run: | + echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list + echo "deb https://repo.scala-sbt.org/scalasbt/debian /" | sudo tee /etc/apt/sources.list.d/sbt_old.list + curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823" | sudo apt-key add + sudo apt-get update + sudo apt-get install -y sbt + + - name: Build blog with SBT + run: sbt "runMain blog.BlogExpanded buildBlog" + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './src/main/resources/blog_out_v2' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 8dbc42a..997c7cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,190 +1,19 @@ -# Created by https://www.toptal.com/developers/gitignore/api/scala,sbt,metals,intellij,java,maven -# Edit at https://www.toptal.com/developers/gitignore?templates=scala,sbt,metals,intellij,java,maven +.idea +.fleet +.bloop +.bsp +.vscode +.eclipse +.metals +.scala -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -# Azure Toolkit for IntelliJ plugin -# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij -.idea/**/azureSettings.xml +target/ +logs -### Java ### -# Compiled class file *.class - -# Log file *.log +*.args +*.iml +metals.sbt -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar - -# Eclipse m2e generated files -# Eclipse Core -.project -# JDT-specific (Eclipse Java Development Tools) -.classpath - -### Metals ### -.metals/ -.bloop/ -project/**/metals.sbt - -### SBT ### -# Simple Build Tool -# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control - -dist/* -lib_managed/ -src_managed/ -project/boot/ -project/plugins/project/ -.history -.cache -.lib/ - -### SBT Patch ### -.bsp/ - -### Scala ### - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml - -# End of https://www.toptal.com/developers/gitignore/api/scala,sbt,metals,intellij,java,maven - -*.jpg -.idea/ \ No newline at end of file +.DS_Store diff --git a/.scalafmt.conf b/.scalafmt.conf index 6baa12c..6eded01 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,5 +1,22 @@ version = "3.5.9" +runner.dialect = scala3 -align.preset = more maxColumn = 120 -runner.dialect = scala3 +project.git = true +align.preset = more + +align.tokens = [ + "=>", "->", "<-", ":=", "//", "%", "%%", "%%%", "+=", + { + "code" = "=", + "owners" = [{ regex = ".*" }] + }, + { + "code" = ":", + "owners" = [{ regex = ".*" }] + }, + { + "code" = "=>" + "owners" = [{ regex = "(Importee.Rename|Case)" }] + } +] \ No newline at end of file diff --git a/build.sbt b/build.sbt index 20b17ea..dbbc159 100644 --- a/build.sbt +++ b/build.sbt @@ -5,11 +5,11 @@ version := "0.1" scalaVersion := "3.3.4" libraryDependencies ++= Seq( - "com.lihaoyi" %% "requests" % "0.9.0", - "com.lihaoyi" %% "upickle" % "4.1.0", - "com.lihaoyi" %% "os-lib" % "0.11.4", + "com.lihaoyi" %% "requests" % "0.9.0", + "com.lihaoyi" %% "upickle" % "4.1.0", + "com.lihaoyi" %% "os-lib" % "0.11.4", "com.lihaoyi" %% "scalatags" % "0.13.1", - "com.lihaoyi" %% "cask" % "0.10.2", + "com.lihaoyi" %% "cask" % "0.10.2", // Java libraries // scraping "org.jsoup" % "jsoup" % "1.19.1", @@ -18,35 +18,34 @@ libraryDependencies ++= Seq( // http apis "org.asynchttpclient" % "async-http-client" % "3.0.2", // mandelbrot - "org.openjfx" % "javafx-base" % "24.0.1", + "org.openjfx" % "javafx-base" % "24.0.1", "org.openjfx" % "javafx-controls" % "24.0.1", - "org.openjfx" % "javafx-fxml" % "24.0.1", - "org.openjfx" % "javafx-swing" % "24.0.1", - + "org.openjfx" % "javafx-fxml" % "24.0.1", + "org.openjfx" % "javafx-swing" % "24.0.1" ) // blog build pipeline // Custom tasks lazy val blogResourceDir = settingKey[File]("Directory containing blog resources") -lazy val buildBlog = taskKey[Unit]("Build the blog") -lazy val runBlog = taskKey[Unit]("Run the blog on a local server") -lazy val watchBlog = taskKey[Unit]("Watch for changes and rebuild blog") +lazy val buildBlog = taskKey[Unit]("Build the blog") +lazy val runBlog = taskKey[Unit]("Run the blog on a local server") +lazy val watchBlog = taskKey[Unit]("Watch for changes and rebuild blog") // Set resource directory blogResourceDir := (Compile / resourceDirectory).value // Build blog task buildBlog := { - (Compile / runMain).toTask(" blog.Blog_V2 buildBlog").value + (Compile / runMain).toTask(" blog.BlogExpanded buildBlog").value } // Watch and reload task watchBlog := { - val log = streams.value.log + val log = streams.value.log val resourceDir = blogResourceDir.value val markdownDir = resourceDir / "blog" - val state = Keys.state.value // Get the current state + val state = Keys.state.value // Get the current state log.info(s"Watching for changes in $markdownDir") @@ -72,4 +71,4 @@ runBlog := { log.info("Starting blog server...") (Compile / runMain).toTask(" blog.RunServer").value -} \ No newline at end of file +} diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store deleted file mode 100644 index b96a6df..0000000 Binary files a/src/main/resources/.DS_Store and /dev/null differ diff --git a/src/main/scala/blog/BlogExpanded.scala b/src/main/scala/blog/BlogExpanded.scala index 08e8d64..5bdee45 100644 --- a/src/main/scala/blog/BlogExpanded.scala +++ b/src/main/scala/blog/BlogExpanded.scala @@ -16,11 +16,11 @@ import scalatags.Text.all.* object BlogExpanded { // Config section - val siteName = "Daniel's Programming Blog" + val siteName = "Daniel's Programming Blog" val siteDescription = "Thoughts on Scala, Java, and functional programming" - val githubUrl = "https://github.com/daniel-ciocirlan" - val twitterUrl = "#" - val linkedinUrl = "#" + val githubUrl = "https://github.com/daniel-ciocirlan" + val twitterUrl = "#" + val linkedinUrl = "#" // File helpers def mdNameToHtml(name: String): String = name.replace(" ", "-").toLowerCase + ".html" @@ -29,10 +29,10 @@ object BlogExpanded { def navbar(currentPage: String = ""): Tag = { div(cls := "navbar navbar-expand-lg navbar-dark bg-primary")( div(cls := "container")( - a(cls := "navbar-brand", href := "/index.html")(siteName), + a(cls := "navbar-brand", href := "index.html")(siteName), button( - cls := "navbar-toggler", - attr("type") := "button", + cls := "navbar-toggler", + attr("type") := "button", attr("data-toggle") := "collapse", attr("data-target") := "#navbarNav" )( @@ -41,16 +41,16 @@ object BlogExpanded { div(cls := "collapse navbar-collapse", id := "navbarNav")( ul(cls := "navbar-nav ml-auto")( li(cls := s"nav-item ${if (currentPage == "home") "active" else ""}")( - a(cls := "nav-link", href := "/index.html")("Home") + a(cls := "nav-link", href := "index.html")("Home") ), li(cls := s"nav-item ${if (currentPage == "articles") "active" else ""}")( - a(cls := "nav-link", href := "/articles.html")("Articles") + a(cls := "nav-link", href := "articles.html")("Articles") ), li(cls := s"nav-item ${if (currentPage == "about") "active" else ""}")( - a(cls := "nav-link", href := "/about.html")("About") + a(cls := "nav-link", href := "about.html")("About") ), li(cls := s"nav-item ${if (currentPage == "contact") "active" else ""}")( - a(cls := "nav-link", href := "/contact.html")("Contact") + a(cls := "nav-link", href := "contact.html")("Contact") ) ) ) @@ -62,13 +62,13 @@ object BlogExpanded { footer(cls := "footer mt-auto py-3 bg-light")( div(cls := "container text-center")( div(cls := "social-links mb-3")( - a(cls := "mx-2", href := githubUrl, target := "_blank")( + a(cls := "mx-2", href := githubUrl, target := "_blank")( i(cls := "fab fa-github fa-2x") ), - a(cls := "mx-2", href := twitterUrl, target := "_blank")( + a(cls := "mx-2", href := twitterUrl, target := "_blank")( i(cls := "fab fa-twitter fa-2x") ), - a(cls := "mx-2", href := linkedinUrl, target := "_blank")( + a(cls := "mx-2", href := linkedinUrl, target := "_blank")( i(cls := "fab fa-linkedin fa-2x") ) ), @@ -84,11 +84,14 @@ object BlogExpanded { head( title := s"$t | $siteName", meta(charset := "UTF-8"), - meta(name := "viewport", content := "width=device-width, initial-scale=1.0"), - meta(name := "description", content := siteDescription), + meta(name := "viewport", content := "width=device-width, initial-scale=1.0"), + meta(name := "description", content := siteDescription), link(rel := "stylesheet", href := "https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"), link(rel := "stylesheet", href := "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"), - link(rel := "stylesheet", href := "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css"), + link( + rel := "stylesheet", + href := "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css" + ), link(rel := "stylesheet", href := "/css/style.css") ), body(cls := "d-flex flex-column h-100")( @@ -123,12 +126,12 @@ object BlogExpanded { featuredPosts.take(3).map { case (_, suffix, _, timestamp) => div(cls := "card mb-4")( div(cls := "card-body")( - h3(cls := "card-title")(a(href := s"/post/${mdNameToHtml(suffix)}")(suffix)), - p(cls := "card-text text-muted")( + h3(cls := "card-title")(a(href := s"post/${mdNameToHtml(suffix)}")(suffix)), + p(cls := "card-text text-muted")( i(cls := "far fa-calendar-alt mr-2"), timestamp.toString ), - a(href := s"/post/${mdNameToHtml(suffix)}", cls := "btn btn-primary")("Read More") + a(href := s"post/${mdNameToHtml(suffix)}", cls := "btn btn-primary")("Read More") ) ) } @@ -137,7 +140,9 @@ object BlogExpanded { div(cls := "card")( div(cls := "card-body")( h4(cls := "card-title")("About Me"), - p(cls := "card-text")("I'm a passionate programmer focusing on functional programming and Scala ecosystem."), + p(cls := "card-text")( + "I'm a passionate programmer focusing on functional programming and Scala ecosystem." + ), a(href := "/about.html", cls := "btn btn-outline-primary")("Learn More") ) ) @@ -159,14 +164,14 @@ object BlogExpanded { div(cls := "col-md-6 mb-4")( div(cls := "card h-100")( div(cls := "card-body")( - h3(cls := "card-title")(a(href := s"/post/${mdNameToHtml(suffix)}")(suffix)), - p(cls := "card-text text-muted")( + h3(cls := "card-title")(a(href := s"post/${mdNameToHtml(suffix)}")(suffix)), + p(cls := "card-text text-muted")( i(cls := "far fa-calendar-alt mr-2"), timestamp.toString ) ), div(cls := "card-footer bg-white border-0")( - a(href := s"/post/${mdNameToHtml(suffix)}", cls := "btn btn-primary")("Read More") + a(href := s"post/${mdNameToHtml(suffix)}", cls := "btn btn-primary")("Read More") ) ) ) @@ -184,11 +189,13 @@ object BlogExpanded { val content = Seq( h1(cls := "mb-4")("About Me"), div(cls := "row")( - div(cls := "col-md-4")( + div(cls := "col-md-4")( img(cls := "img-fluid rounded mb-4", src := "/img/profile.jpg", alt := "Profile Picture") ), div(cls := "col-md-8")( - p("I'm a software developer passionate about functional programming, Scala, and building robust applications."), + p( + "I'm a software developer passionate about functional programming, Scala, and building robust applications." + ), p("With experience in backend development, I enjoy writing clean code and solving complex problems."), h3("Skills"), ul( @@ -199,7 +206,9 @@ object BlogExpanded { li("Web Development") ), h3("Background"), - p("I have been working with Scala for several years, focusing on building scalable and maintainable applications. My background includes experience with Akka, Play Framework, and other JVM technologies.") + p( + "I have been working with Scala for several years, focusing on building scalable and maintainable applications. My background includes experience with Akka, Play Framework, and other JVM technologies." + ) ) ) ) @@ -217,19 +226,19 @@ object BlogExpanded { div(cls := "col-md-6")( p("Feel free to reach out to me through any of the following channels:"), ul(cls := "list-unstyled")( - li(cls := "mb-3")( + li(cls := "mb-3")( i(cls := "fas fa-envelope mr-2"), a(href := "mailto:contact@example.com")("contact@example.com") ), - li(cls := "mb-3")( + li(cls := "mb-3")( i(cls := "fab fa-github mr-2"), a(href := githubUrl, target := "_blank")("GitHub") ), - li(cls := "mb-3")( + li(cls := "mb-3")( i(cls := "fab fa-linkedin mr-2"), a(href := linkedinUrl, target := "_blank")("LinkedIn") ), - li(cls := "mb-3")( + li(cls := "mb-3")( i(cls := "fab fa-twitter mr-2"), a(href := twitterUrl, target := "_blank")("Twitter") ) @@ -355,7 +364,7 @@ object BlogExpanded { val content = Seq( div(cls := "post-header mb-4")( h1(cls := "display-4")(suffix), - p(cls := "text-muted")( + p(cls := "text-muted")( i(cls := "far fa-calendar-alt mr-2"), timestamp.toString ) @@ -369,13 +378,13 @@ object BlogExpanded { a(href := "/articles.html", cls := "btn btn-outline-primary")("← All Articles"), div(cls := "social-share")( span(cls := "mr-2")("Share:"), - a(cls := "btn btn-sm btn-outline-secondary mr-1", href := "#")( + a(cls := "btn btn-sm btn-outline-secondary mr-1", href := "#")( i(cls := "fab fa-twitter") ), - a(cls := "btn btn-sm btn-outline-secondary mr-1", href := "#")( + a(cls := "btn btn-sm btn-outline-secondary mr-1", href := "#")( i(cls := "fab fa-facebook") ), - a(cls := "btn btn-sm btn-outline-secondary", href := "#")( + a(cls := "btn btn-sm btn-outline-secondary", href := "#")( i(cls := "fab fa-linkedin") ) ) @@ -390,11 +399,12 @@ object BlogExpanded { } def buildBlog(): Unit = { - val resourcePath = "/Users/daniel/dev/rockthejvm/courses-playground/scala-projects-playground/src/main/resources" + val resourcePath = (os.pwd / "src/main/resources").toString val blogRoot: Path = os.Path(resourcePath) / "blog" - val outPath: Path = os.Path(resourcePath) / "blog_out_v2" + val outPath: Path = os.Path(resourcePath) / "blog_out_v2" - val postInfo = os.list.apply(blogRoot) + val postInfo = os.list + .apply(blogRoot) .map { p => val s"$prefix - $suffix.md" = p.last val publishDate = java.time.LocalDate.ofInstant( @@ -422,14 +432,13 @@ object BlogExpanded { generateCustomCss(outPath) // Generate blog post pages - postInfo.foreach { - case (_, suffix, path, timestamp) => - val parser = org.commonmark.parser.Parser.builder().build() - val document = parser.parse(os.read(path)) - val renderer = org.commonmark.renderer.html.HtmlRenderer.builder().build() - val output = renderer.render(document) + postInfo.foreach { case (_, suffix, path, timestamp) => + val parser = org.commonmark.parser.Parser.builder().build() + val document = parser.parse(os.read(path)) + val renderer = org.commonmark.renderer.html.HtmlRenderer.builder().build() + val output = renderer.render(document) - generatePostPage(outPath, suffix, output, timestamp) + generatePostPage(outPath, suffix, output, timestamp) } // Generate static pages @@ -442,7 +451,7 @@ object BlogExpanded { def main(args: Array[String]): Unit = { args.headOption match { case Some("buildBlog") => buildBlog() - case _ => println("Unknown command") + case _ => println("Unknown command") } } -} \ No newline at end of file +}