Skip to content

Commit

Permalink
add support for generating conference and meetup directory page
Browse files Browse the repository at this point in the history
  • Loading branch information
softinio committed Oct 26, 2024
1 parent 2644ef1 commit e2bd78d
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 6 deletions.
5 changes: 5 additions & 0 deletions core/src/main/scala/com/softinio/scalanews/ConfigLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ import pureconfig.*
import pureconfig.module.catseffect.syntax.*
import cats.effect.IO
import com.softinio.scalanews.algebra.Configuration
import com.softinio.scalanews.algebra.EventConfig

object ConfigLoader {
def load(filePath: String = "config.json"): IO[Configuration] = {
ConfigSource.file(filePath).loadF[IO, Configuration]()
}

def loadEventsConfig(filePath: String = "events.json"): IO[EventConfig] = {
ConfigSource.file(filePath).loadF[IO, EventConfig]()
}
}
171 changes: 171 additions & 0 deletions core/src/main/scala/com/softinio/scalanews/Events.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright 2024 Salar Rahmanian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.softinio.scalanews

import cats.effect.*
import fs2.io.file.*

import com.softinio.scalanews.algebra.Event
import com.softinio.scalanews.algebra.EventType
import com.softinio.scalanews.algebra.Location

object Events {
private val directoryMarkdownFilePath =
Path("docs/Resources/Event_Directory.md")

private def printLocations(locations: List[Location]): String = {
locations
.map { location =>
location.state match {
case Some(state) =>
s"- ${location.city}, ${state}, ${location.country}\n"
case None => s"- ${location.city}, ${location.country}\n"
}
}
.mkString("\n")
}

private def generateEvent(event: Event): String = {
s"""
|### ${event.name}
|
|${event.description}
|
|##### Links
|
|**Meetup:** <${event.meetupUrl.getOrElse("N/A")}>
|
|**Lu.ma:** <${event.lumaUrl.getOrElse("N/A")}>
|
|**Social Media:** <${event.socialMediaUrl.getOrElse("N/A")}>
|
|**Other:** <${event.otherUrl.getOrElse("N/A")}>
|
|##### Location(s)
|
|${printLocations(event.locations)}
|""".stripMargin
}

def generateDirectory(
eventList: List[Event],
eventType: EventType
): IO[String] = {
IO.blocking {

val directory = eventList.map(generateEvent)

s"${directory.mkString("\n")}".stripMargin
}
}

def cleanEventDirectory(): IO[ExitCode] = {
for {
exists <- Files[IO].exists(directoryMarkdownFilePath)
_ <- if (exists) Files[IO].delete(directoryMarkdownFilePath) else IO.unit
} yield ExitCode.Success
}

def addTopHeader(): IO[ExitCode] = {
val header = IO.blocking {
s"""
|# Meetups and Conferences
\n
""".stripMargin
}

for {
exists <- Files[IO].exists(directoryMarkdownFilePath)
headerValue <- header
_ <- fs2.Stream
.emits(List(headerValue))
.through(fs2.text.utf8.encode)
.through(
if (exists)
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Append))
else
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Create))
)
.compile
.drain
} yield ExitCode.Success
}

def addHeader(eventType: EventType): IO[ExitCode] = {
val header = IO.blocking {
s"""
|## ${eventType.toString} Directory
\n
""".stripMargin
}

for {
headerValue <- header
_ <- fs2.Stream
.emits(List(headerValue))
.through(fs2.text.utf8.encode)
.through(
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Append))
)
.compile
.drain
} yield ExitCode.Success
}

def addFooter(): IO[ExitCode] = {
val footer = IO.blocking {
s"""
|###### Do you run a Scala related conference or meetup? Add it to this Directory!

|See [README](https://github.com/softinio/scalanews/blob/main/README.md) for details.\n
""".stripMargin
}

for {
footerValue <- footer
_ <- fs2.Stream
.emits(List(footerValue))
.through(fs2.text.utf8.encode)
.through(
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Append))
)
.compile
.drain
} yield ExitCode.Success
}

def createEventDirectory(
eventList: List[Event],
eventType: EventType
): IO[ExitCode] = {
for {
exists <- Files[IO].exists(directoryMarkdownFilePath)
directory <- generateDirectory(eventList, eventType)
_ <- fs2.Stream
.emits(List(directory))
.through(fs2.text.utf8.encode)
.through(
if (exists)
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Append))
else
Files[IO].writeAll(directoryMarkdownFilePath, Flags(Flag.Create))
)
.compile
.drain
} yield ExitCode.Success
}
}
30 changes: 29 additions & 1 deletion core/src/main/scala/com/softinio/scalanews/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import cats.implicits.*
import com.monovore.decline.*
import com.monovore.decline.effect.*

import com.softinio.scalanews.algebra.EventType

object Main
extends CommandIOApp(
name = "scalanews",
Expand All @@ -40,6 +42,8 @@ object Main

private case class Blogger(directory: Boolean)

private case class Event(directory: Boolean)

private case class GenerateNextBlog(
startDate: String,
endDate: String
Expand Down Expand Up @@ -98,13 +102,21 @@ object Main
.map(Blogger.apply)
}

private val eventOpts: Opts[Event] =
Opts.subcommand("event", "Event tasks") {
Opts
.flag("directory", "create a new event directory page", short = "e")
.orFalse
.map(Event.apply)
}

private val generateNextBlogOpts: Opts[GenerateNextBlog] =
Opts.subcommand("generate", "Generate next blog") {
(startDateOps, endDateOps).mapN(GenerateNextBlog.apply)
}

override def main: Opts[IO[ExitCode]] =
(publishOpts orElse createOpts orElse generateNextBlogOpts orElse bloggerOpts)
(publishOpts orElse createOpts orElse generateNextBlogOpts orElse bloggerOpts orElse eventOpts)
.map {
case Publish(publishDate, archiveDate, archiveFolder) =>
FileHandler.publish(publishDate, archiveDate, archiveFolder)
Expand All @@ -121,5 +133,21 @@ object Main
result <- Bloggers.createBloggerDirectory(config.bloggers)
} yield result
} else IO(ExitCode.Success)
case Event(directory) =>
if (directory) {
for {
config <- ConfigLoader.loadEventsConfig()
_ <- Events.cleanEventDirectory()
_ <- Events.addTopHeader()
_ <- Events.addHeader(EventType.Meetup)
_ <- Events.createEventDirectory(config.meetups, EventType.Meetup)
_ <- Events.addHeader(EventType.Conference)
_ <- Events.createEventDirectory(
config.conferences,
EventType.Conference
)
_ <- Events.addFooter()
} yield ExitCode.Success
} else IO(ExitCode.Success)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ package com.softinio.scalanews.algebra
import java.util.Date
import org.http4s.Uri

case class Article(title: String, url: Uri, author: String, publishedDate: Date)
case class Article(
title: String,
url: Option[Uri],
author: String,
publishedDate: Date
)

object Article {
def apply(
Expand All @@ -28,7 +33,7 @@ object Article {
author: String,
publishedDate: Date
): Article = {
val parsedUrl = Uri.fromString(url).toOption.get
val parsedUrl = Uri.fromString(url).toOption
Article(title, parsedUrl, author, publishedDate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import java.net.URI

final case class Blog(name: String, url: URI, rss: URI) derives ConfigReader
final case class Configuration(bloggers: List[Blog]) derives ConfigReader
final case class EventConfig(meetups: List[Event], conferences: List[Event])
derives ConfigReader

object Config {
given urlReader: ConfigReader[URI] = ConfigReader[String].map(URI.create)
given ConfigReader[URI] = ConfigReader[String].map(URI.create)
}
42 changes: 42 additions & 0 deletions core/src/main/scala/com/softinio/scalanews/algebra/Event.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 Salar Rahmanian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.softinio.scalanews.algebra

import org.http4s.Uri
import pureconfig.*
import pureconfig.generic.derivation.default.*

enum EventType:
case Meetup, Conference

case class Location(city: String, state: Option[String], country: String)
derives ConfigReader

case class Event(
name: String,
description: String,
meetupUrl: Option[Uri],
lumaUrl: Option[Uri],
socialMediaUrl: Option[Uri],
otherUrl: Option[Uri],
locations: List[Location]
) derives ConfigReader

object Event {
given ConfigReader[Option[Uri]] =
ConfigReader[Option[String]].map(_.flatMap(Uri.fromString(_).toOption))
}
47 changes: 45 additions & 2 deletions core/src/test/scala/com/softinio/scalanews/ConfigLoaderSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ package com.softinio.scalanews
import java.nio.file.Files
import java.nio.file.Path
import java.nio.charset.StandardCharsets
import org.http4s.Uri

import munit.CatsEffectSuite

class ConfigLoaderSuite extends CatsEffectSuite {
val sampleConfig: FunFixture[Path] = FunFixture[Path](
val sampleBloggerConfig: FunFixture[Path] = FunFixture[Path](
setup = { test =>
val filename = test.name.replace(" ", "_")
val theFile = Files.createTempFile("tmp", s"$filename.json")
Expand All @@ -46,10 +47,52 @@ class ConfigLoaderSuite extends CatsEffectSuite {
()
}
)
sampleConfig.test("test loading json config") { file =>
val sampleEventConfig: FunFixture[Path] = FunFixture[Path](
setup = { test =>
val filename = test.name.replace(" ", "_")
val theFile = Files.createTempFile("tmp", s"$filename.json")
val sampleJson = """
{
"meetups": [
{
"name": "SF Scala",
"meetup-url": "http://www.meetup.com/SF-Scala/",
"luma-url": "https://lu.ma/scala",
"social-media-url": null,
"other-url": "https://www.sfscala.org/",
"locations": [
{
"city": "San Francisco",
"state": "California",
"country": "USA"
}
],
"description": "SF Scala is a group for functional programmers who use Scala to build software who are based in San Francisco or nearby. We welcome programmers of all skill levels to our events."
}
],
"conferences": []
}
"""
Files.write(theFile, sampleJson.getBytes(StandardCharsets.UTF_8))
},
teardown = { eventFile =>
Files.deleteIfExists(eventFile)
()
}
)
sampleBloggerConfig.test("test loading blogger json config") { file =>
val result = for {
conf <- ConfigLoader.load(file.toString)
} yield conf.bloggers.head.name == "Salar Rahmanian"
assertIO(result, true)
}
sampleEventConfig.test("test loading events json config") { eventFile =>
val result = for {
conf <- ConfigLoader.loadEventsConfig(eventFile.toString)
} yield conf.meetups.head.meetupUrl == Uri
.fromString("http://www.meetup.com/SF-Scala/")
.toOption
assertIO(result, true)
}
}
Loading

0 comments on commit e2bd78d

Please sign in to comment.