Skip to content

Commit 711b8d4

Browse files
committed
Update and modernize
1 parent ec25d96 commit 711b8d4

File tree

27 files changed

+261
-127
lines changed

27 files changed

+261
-127
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ test*.log
1313
static-examples/
1414
lmdb-examples/
1515
.scala-build/
16+
test.sh

.scalafmt.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = 3.7.3
1+
version = 3.8.3
22
runner.dialect = scala3
33
align.preset = most
44
maxColumn = 250

build.sbt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
name := "zwords"
22
description := "Guess a word everyday game"
3-
scalaVersion := "3.2.2"
3+
scalaVersion := "3.5.1"
44

55
val versions = new {
6-
val zio = "2.0.13"
7-
val zionio = "2.0.1"
8-
val zioconfig = "3.0.7"
6+
val zio = "2.1.9"
7+
val zionio = "2.0.2"
8+
val zioconfig = "4.0.2"
99
val ziocli = "0.2.2"
10-
val ziojson = "0.5.0"
11-
val ziologging = "2.1.12"
12-
val ziolmdb = "1.0.6"
13-
val tapir = "1.2.13"
14-
val logback = "1.4.7"
10+
val ziojson = "0.7.3"
11+
val ziologging = "2.3.1"
12+
val ziolmdb = "1.8.2"
13+
val tapir = "1.11.5"
14+
val logback = "1.5.8"
1515
}
1616

1717
val sharedSettings = Seq(
18-
scalaVersion := "3.2.2",
18+
scalaVersion := "3.5.1",
1919
Test / fork := true,
2020
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"),
2121
scalacOptions ++= Seq("-deprecation"), // "-Xfatal-warnings",

console/src/main/scala/fr/janalyse/zwords/console/Main.scala

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
package fr.janalyse.zwords.console
22

3+
import com.typesafe.config.ConfigFactory
34
import fr.janalyse.zwords.gamelogic.{Game, GameInternalIssue, GameIssue, GoodPlaceCell}
45
import fr.janalyse.zwords.dictionary.DictionaryService
56
import fr.janalyse.zwords.dictionary.DictionaryConfig
67
import fr.janalyse.zwords.wordgen.{WordGeneratorLanguageNotSupported, WordGeneratorService}
78
import zio.*
9+
import zio.Runtime.removeDefaultLoggers
10+
import zio.config.typesafe.TypesafeConfigProvider
811

912
import scala.Console.{RED, RESET, YELLOW}
1013
import java.io.IOException
1114

1215
object Main extends ZIOAppDefault {
16+
override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] = {
17+
val configProviderLayer = {
18+
val config = ConfigFactory.load()
19+
val provider = TypesafeConfigProvider.fromTypesafeConfig(config).kebabCase
20+
Runtime.setConfigProvider(provider)
21+
}
22+
configProviderLayer
23+
}
1324

1425
def playLogic(game: Game): ZIO[WordGeneratorService, GameIssue | WordGeneratorLanguageNotSupported | IOException, Game] =
1526
for
@@ -42,8 +53,7 @@ object Main extends ZIOAppDefault {
4253

4354
override def run = consoleBasedGame.provide(
4455
DictionaryService.live,
45-
WordGeneratorService.live,
46-
DictionaryConfig.layer
56+
WordGeneratorService.live
4757
)
4858

4959
}

dictionary/src/main/resources/reference.conf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
dictionaries-config {
22
dictionaries {
33
fr {
4-
name = "french"
4+
name = french
55
aff-filename = ${?ZWORDS_FR_AFF_FILENAME}
66
dic-filename = ${?ZWORDS_FR_DIC_FILENAME}
77
}
88
fr-common {
9-
name = "french-common"
9+
name = french-common
1010
aff-filename = ${?ZWORDS_FR_AFF_FILENAME}
1111
dic-filename = ${?ZWORDS_FR_DIC_FILENAME}
1212
subset-filename = ${?ZWORDS_FR_SUBSET_FILENAME}
1313
}
1414
en {
15-
name = "english"
15+
name = english
1616
aff-filename = ${?ZWORDS_EN_AFF_FILENAME}
1717
dic-filename = ${?ZWORDS_EN_DIC_FILENAME}
1818
}
1919
en-common {
20-
name = "english-common"
20+
name = english-common
2121
aff-filename = ${?ZWORDS_EN_AFF_FILENAME}
2222
dic-filename = ${?ZWORDS_EN_DIC_FILENAME}
2323
subset-filename = ${?ZWORDS_EN_SUBSET_FILENAME}

dictionary/src/main/scala/fr/janalyse/zwords/dictionary/DictionaryConfig.scala

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,16 @@ package fr.janalyse.zwords.dictionary
22

33
import zio.*
44
import zio.config.*
5-
import zio.config.typesafe.*
65
import zio.config.magnolia.*
7-
import zio.config.ConfigDescriptor.*
6+
import zio.config.typesafe.*
7+
import zio.config.toKebabCase
88

99
case class DictionaryConfig(name: String, affFilename: Option[String], dicFilename: Option[String], subsetFilename: Option[String])
1010

1111
case class DictionariesConfig(dictionaries: Map[String, DictionaryConfig])
1212

13-
case class RootConfig(dictionariesConfig: DictionariesConfig)
14-
1513
object DictionaryConfig {
16-
def layer = ZLayer.fromZIO(
17-
read(
18-
descriptor[RootConfig]
19-
.mapKey(toKebabCase)
20-
.from(TypesafeConfigSource.fromResourcePath)
21-
).map(_.dictionariesConfig)
22-
)
14+
val config =
15+
deriveConfig[DictionariesConfig]
16+
.nested("dictionaries-config")
2317
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package fr.janalyse.zwords.dictionary
2+
3+
enum DictionaryIssue(message:String) extends Exception(message) {
4+
case LanguageNotSupported(language: String) extends DictionaryIssue(s"language $language not supported")
5+
case MissingConfiguration(message: String) extends DictionaryIssue(message)
6+
case ResourceNotFound(resource: String) extends DictionaryIssue(s"$resource not found")
7+
case InternalIssue(message: String, throwable: Option[Throwable]=None) extends DictionaryIssue(message)
8+
}

dictionary/src/main/scala/fr/janalyse/zwords/dictionary/DictionaryIssues.scala

Lines changed: 0 additions & 5 deletions
This file was deleted.

dictionary/src/main/scala/fr/janalyse/zwords/dictionary/DictionaryService.scala

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,61 +21,62 @@ import zio.ZIOAspect.*
2121
trait DictionaryService {
2222
def languages: UIO[List[String]]
2323

24-
def count(language: String): IO[DictionaryLanguageNotSupported, Int]
24+
def count(language: String): IO[DictionaryIssue, Int]
2525

26-
def entries(language: String, all: Boolean): IO[DictionaryLanguageNotSupported, List[HunspellEntry]]
26+
def entries(language: String, all: Boolean): IO[DictionaryIssue, List[HunspellEntry]]
2727

28-
def find(language: String, word: String): IO[DictionaryLanguageNotSupported, Option[HunspellEntry]]
28+
def find(language: String, word: String): IO[DictionaryIssue, Option[HunspellEntry]]
2929

30-
def generateWords(language: String, entry: HunspellEntry): IO[DictionaryLanguageNotSupported, List[HunspellEntry]]
30+
def generateWords(language: String, entry: HunspellEntry): IO[DictionaryIssue, List[HunspellEntry]]
3131
}
3232

3333
object DictionaryService {
3434
def languages: URIO[DictionaryService, Iterable[String]] =
3535
ZIO.serviceWithZIO(_.languages)
3636

37-
def count(language: String): ZIO[DictionaryService, DictionaryLanguageNotSupported, Int] =
37+
def count(language: String): ZIO[DictionaryService, DictionaryIssue, Int] =
3838
ZIO.serviceWithZIO(_.count(language))
3939

40-
def entries(language: String, all: Boolean): ZIO[DictionaryService, DictionaryLanguageNotSupported, List[HunspellEntry]] =
40+
def entries(language: String, all: Boolean): ZIO[DictionaryService, DictionaryIssue, List[HunspellEntry]] =
4141
ZIO.serviceWithZIO(_.entries(language, all))
4242

43-
def find(language: String, word: String): ZIO[DictionaryService, DictionaryLanguageNotSupported, Option[HunspellEntry]] =
43+
def find(language: String, word: String): ZIO[DictionaryService, DictionaryIssue, Option[HunspellEntry]] =
4444
ZIO.serviceWithZIO(_.find(language, word))
4545

46-
def generateWords(language: String, entry: HunspellEntry): ZIO[DictionaryService, DictionaryLanguageNotSupported, List[HunspellEntry]] =
46+
def generateWords(language: String, entry: HunspellEntry): ZIO[DictionaryService, DictionaryIssue, List[HunspellEntry]] =
4747
ZIO.serviceWithZIO(_.generateWords(language, entry))
4848

49-
val live = ZLayer.fromZIO(
50-
for {
51-
dictionaryConfig <- ZIO.service[DictionariesConfig]
52-
dictionaries <- ZIO.foreach(dictionaryConfig.dictionaries) { (key, dict) =>
53-
Hunspell.loadHunspellDictionary(dict).map(d => key -> d) @@ annotated("language" -> key)
54-
}
55-
dictionary <- ZIO.from(dictionaries)
49+
val live: ZLayer[Any, Nothing, DictionaryServiceLive] = ZLayer.fromZIO {
50+
val service = for {
51+
dictionaryConfig <- ZIO.config(DictionaryConfig.config)
52+
dictionaries <- ZIO.foreach(dictionaryConfig.dictionaries) { (key, dict) =>
53+
Hunspell.loadHunspellDictionary(dict).map(d => key -> d) @@ annotated("language" -> key)
54+
}
55+
dictionary <- ZIO.from(dictionaries)
5656
} yield DictionaryServiceLive(dictionary)
57-
)
57+
service.orDie
58+
}
5859
}
5960

6061
case class DictionaryServiceLive(dictionaries: Map[String, Hunspell]) extends DictionaryService {
6162

6263
private def getDictionary(language: String) =
63-
ZIO.from(dictionaries.get(language)).orElseFail(DictionaryLanguageNotSupported(language))
64+
ZIO.from(dictionaries.get(language)).orElseFail(DictionaryIssue.LanguageNotSupported(language))
6465

6566
override val languages: UIO[List[String]] = ZIO.succeed(dictionaries.keys.toList)
6667

67-
override def count(language: String): IO[DictionaryLanguageNotSupported, Int] = getDictionary(language).map(_.entries.size)
68+
override def count(language: String): IO[DictionaryIssue, Int] = getDictionary(language).map(_.entries.size)
6869

69-
override def entries(language: String, all: Boolean): IO[DictionaryLanguageNotSupported, List[HunspellEntry]] = for {
70+
override def entries(language: String, all: Boolean): IO[DictionaryIssue, List[HunspellEntry]] = for {
7071
dictionary <- getDictionary(language)
7172
entries <- if (all) ZIO.succeed(dictionary.entries.flatMap(entry => dictionary.generateWords(entry))) else ZIO.succeed(dictionary.entries)
7273
} yield entries
7374

74-
override def find(language: String, word: String): IO[DictionaryLanguageNotSupported, Option[HunspellEntry]] = for {
75+
override def find(language: String, word: String): IO[DictionaryIssue, Option[HunspellEntry]] = for {
7576
dictionary <- getDictionary(language)
7677
} yield dictionary.entries.find(_.word == word)
7778

78-
override def generateWords(language: String, entry: HunspellEntry): IO[DictionaryLanguageNotSupported, List[HunspellEntry]] = for {
79+
override def generateWords(language: String, entry: HunspellEntry): IO[DictionaryIssue, List[HunspellEntry]] = for {
7980
dictionary <- getDictionary(language)
8081
} yield dictionary.generateWords(entry)
8182
}

dictionary/src/main/scala/fr/janalyse/zwords/dictionary/Hunspell.scala

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import zio.{Chunk, Console, ZIO}
2424
import scala.util.matching.Regex
2525

2626
case class HunspellEntry(word: String, flags: Option[String], attributes: Map[String, String]) {
27-
val isDiv = attributes.get("po") == Some("div") // Separator
27+
val isDiv = attributes.get("po").contains("div") // Separator
2828
val isCommonWord = word.head.isLower // Nom commun
2929
val isCompound = word.contains('-') || word.contains('\'')
3030
val isProperNoun = word.head.isUpper
@@ -112,40 +112,40 @@ case class Hunspell(entries: List[HunspellEntry], affixRules: AffixRules) {
112112
}
113113

114114
object Hunspell {
115-
def loadAff(filename: String): IO[DictionaryInternalIssue, AffixRules] = for {
116-
file <- ZIO.attempt(Path(filename)).orElseFail(DictionaryInternalIssue(s"Filename '$filename' is invalid"))
117-
bytes <- Files.readAllBytes(file).orElseFail(DictionaryInternalIssue(s"Couldn't read aff file content $file"))
115+
def loadAff(filename: String): IO[DictionaryIssue, AffixRules] = for {
116+
file <- ZIO.attempt(Path(filename)).mapError(th => DictionaryIssue.InternalIssue(s"Filename '$filename' is invalid", Some(th)))
117+
bytes <- Files.readAllBytes(file).orElseFail(DictionaryIssue.ResourceNotFound(filename))
118118
charset = Charset.Standard.utf8
119119
content <- charset.decodeString(bytes)
120120
} yield AffixRules(content)
121121

122-
def loadDic(filename: String): IO[DictionaryInternalIssue, List[HunspellEntry]] = for {
123-
file <- ZIO.attempt(Path(filename)).orElseFail(DictionaryInternalIssue(s"Filename '$filename' is invalid"))
124-
bytes <- Files.readAllBytes(file).orElseFail(DictionaryInternalIssue(s"Couldn't file content $file"))
122+
def loadDic(filename: String): IO[DictionaryIssue, List[HunspellEntry]] = for {
123+
file <- ZIO.attempt(Path(filename)).mapError(th => DictionaryIssue.InternalIssue(s"Filename '$filename' is invalid", Some(th)))
124+
bytes <- Files.readAllBytes(file).orElseFail(DictionaryIssue.ResourceNotFound(filename))
125125
charset = Charset.Standard.utf8
126126
content <- charset.decodeString(bytes)
127127
lines = content.split("\n").toList
128-
count <- ZIO.attempt(lines.headOption.map(_.toInt).getOrElse(0)).orElseFail(DictionaryInternalIssue("Couldn't extract words count"))
128+
count <- ZIO.attempt(lines.headOption.map(_.toInt).getOrElse(0)).orElseFail(DictionaryIssue.InternalIssue("Couldn't extract words count"))
129129
// _ <- ZIO.log(s"Expecting to find $count hunspell entries")
130130
specs = lines.tail
131131
} yield specs.flatMap(HunspellEntry.fromLine)
132132

133-
def loadSubsetWords(filename: String): IO[DictionaryInternalIssue, Set[String]] = for {
134-
file <- ZIO.attempt(Path(filename)).orElseFail(DictionaryInternalIssue(s"Filename '$filename' is invalid"))
135-
dicBytes <- Files.readAllBytes(file).orElseFail(DictionaryInternalIssue(s"Couldn't read file content $file"))
133+
def loadSubsetWords(filename: String): IO[DictionaryIssue, Set[String]] = for {
134+
file <- ZIO.attempt(Path(filename)).mapError(th => DictionaryIssue.InternalIssue(s"Filename '$filename' is invalid", Some(th)))
135+
dicBytes <- Files.readAllBytes(file).mapError(th => DictionaryIssue.InternalIssue(s"Couldn't read file content $file", Some(th)))
136136
charset = Charset.Standard.utf8
137137
dicContent <- charset.decodeString(dicBytes)
138138
} yield dicContent.split("\n").map(_.trim).toSet
139139

140-
def loadHunspellDictionary(dictionaryConfig: DictionaryConfig): IO[DictionaryInternalIssue, Hunspell] =
140+
def loadHunspellDictionary(dictionaryConfig: DictionaryConfig): IO[DictionaryIssue, Hunspell] =
141141
ZIO.logSpan("Hunspell dictionary") {
142142
for {
143143
_ <- ZIO.log("loading")
144144
// ---------------------------------------------
145-
affFilename <- ZIO.from(dictionaryConfig.affFilename).orElseFail(DictionaryInternalIssue("Aff filename not provided"))
145+
affFilename <- ZIO.from(dictionaryConfig.affFilename).orElseFail(DictionaryIssue.MissingConfiguration("Aff filename not provided"))
146146
affixRules <- loadAff(affFilename)
147147
// ---------------------------------------------
148-
dicFilename <- ZIO.from(dictionaryConfig.dicFilename).orElseFail(DictionaryInternalIssue("Dic filename not provided"))
148+
dicFilename <- ZIO.from(dictionaryConfig.dicFilename).orElseFail(DictionaryIssue.MissingConfiguration("Dic filename not provided"))
149149
entries <- loadDic(dicFilename)
150150
// ---------------------------------------------
151151
mayBeSubsetFilename = dictionaryConfig.subsetFilename

0 commit comments

Comments
 (0)