Skip to content

Commit

Permalink
Respect XDG base directory specification for config files
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenceWarne committed Oct 5, 2024
1 parent 72cf9e6 commit 7281d55
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 36 deletions.
29 changes: 21 additions & 8 deletions finito/main/it/src/fin/ConfigTest.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fin

import scala.collection.immutable

import cats.effect._
import cats.effect.std.Env
import cats.implicits._
import fs2._
import fs2.io.file._
Expand All @@ -14,17 +17,25 @@ object ConfigTest extends SimpleIOSuite {

implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
val testDir = Path("./out/conf-test").normalize.absolute
val configPath = testDir / "libro-finito"

override def maxParallelism = 1

val testEnv = new Env[IO] {
private val mp = Map("XDG_CONFIG_HOME" -> testDir.toString)
override def get(name: String): IO[Option[String]] = IO.pure(mp.get(name))
override def entries: IO[immutable.Iterable[(String, String)]] =
IO.pure(mp.toList)
}

test("creates config directory if not exists") {
for {
_ <- Files[IO].deleteRecursively(testDir).recover {
case _: NoSuchFileException => ()
}
_ <- Config(testDir.toString)
_ <- Config(testEnv)
exists <- Files[IO].exists(testDir)
_ <- Files[IO].deleteIfExists(testDir)
_ <- Files[IO].deleteRecursively(testDir)
} yield expect(exists)
}

Expand All @@ -33,8 +44,8 @@ object ConfigTest extends SimpleIOSuite {
_ <- Files[IO].deleteRecursively(testDir).recover {
case _: NoSuchFileException => ()
}
_ <- Config(testDir.toString)
_ <- Files[IO].deleteIfExists(testDir)
_ <- Config(testEnv)
_ <- Files[IO].deleteRecursively(testDir)
} yield success
}

Expand All @@ -47,18 +58,20 @@ object ConfigTest extends SimpleIOSuite {
| default-collection = $defaultCollection
|}""".stripMargin
for {
_ <- Files[IO].createDirectories(testDir)
_ <- Files[IO].createDirectories(configPath)
_ <-
Stream
.emits(configContents.getBytes("UTF-8"))
.through(Files[IO].writeAll(testDir / "service.conf"))
.through(
Files[IO].writeAll(configPath / "service.conf")
)
.compile
.drain
conf <- Config(testDir.toString)
conf <- Config(testEnv)
_ <- Files[IO].deleteRecursively(testDir)
} yield expect(
ServiceConfig(
ServiceConfig.defaultDatabasePath(testDir.toString),
ServiceConfig.defaultDatabasePath(configPath.toString),
ServiceConfig.defaultDatabaseUser,
ServiceConfig.defaultDatabasePassword,
ServiceConfig.defaultHost,
Expand Down
8 changes: 0 additions & 8 deletions finito/main/src/fin/CliOptions.scala

This file was deleted.

20 changes: 10 additions & 10 deletions finito/main/src/fin/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import scala.concurrent.duration._
import _root_.cats.effect._
import _root_.cats.effect.std.Dispatcher
import _root_.cats.implicits._
import caseapp._
import caseapp.cats._
import cats.effect.std.Env
import doobie._
import org.http4s.blaze.client.BlazeClientBuilder
import org.http4s.blaze.server.BlazeServerBuilder
Expand All @@ -19,7 +18,7 @@ import zio.Runtime
import fin.config._
import fin.persistence._

object Main extends IOCaseApp[CliOptions] {
object Main extends IOApp {

implicit val zioRuntime: zio.Runtime[zio.Clock with zio.Console] =
Runtime.default.withEnvironment(
Expand All @@ -30,8 +29,10 @@ object Main extends IOCaseApp[CliOptions] {
)
implicit val logger: Logger[IO] = Slf4jLogger.getLogger

def run(options: CliOptions, arg: RemainingArgs): IO[ExitCode] = {
val server = serviceResources(options).use { serviceResources =>
val env = Env[IO]

def run(arg: List[String]): IO[ExitCode] = {
val server = serviceResources(env).use { serviceResources =>
implicit val dispatcherEv = serviceResources.dispatcher
val config = serviceResources.config
val timer = Temporal[IO]
Expand All @@ -49,9 +50,8 @@ object Main extends IOCaseApp[CliOptions] {
_ <- logger.debug("Bootstrapping caliban...")
interpreter <- CalibanSetup.interpreter[IO](services)

debug <- IO.blocking(
sys.env.get("LOG_LEVEL").exists(CIString(_) === ci"DEBUG")
)
logLevel <- env.get("LOG_LEVEL")
debug = logLevel.exists(CIString(_) === ci"DEBUG")
refresherIO = (timer.sleep(1.minute) >> Routes.keepFresh[IO](
serviceResources.client,
timer,
Expand All @@ -74,11 +74,11 @@ object Main extends IOCaseApp[CliOptions] {
}

private def serviceResources(
options: CliOptions
env: Env[IO]
): Resource[IO, ServiceResources[IO]] =
for {
client <- BlazeClientBuilder[IO].resource
config <- Resource.eval(Config[IO](options.config))
config <- Resource.eval(Config[IO](env))
transactor <- ExecutionContexts.fixedThreadPool[IO](4).flatMap { ec =>
TransactorSetup.sqliteTransactor[IO](
config.databaseUri,
Expand Down
34 changes: 24 additions & 10 deletions finito/main/src/fin/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fin.config
import scala.annotation.nowarn

import cats.effect.Async
import cats.effect.std.Env
import cats.syntax.all._
import fs2.io.file._
import io.circe._
Expand All @@ -16,18 +17,16 @@ import fin.service.collection._
object Config {

def apply[F[_]: Async: Logger](
configDirectoryStr: String
): F[ServiceConfig] = {
env: Env[F]
): F[ServiceConfig] =
for {
home <- Async[F].delay(System.getProperty("user.home"))
// Java in 2021 :O
expandedPathStr = configDirectoryStr.replaceFirst("^~", home)
configDirectory = Path(expandedPathStr).absolute
_ <- Logger[F].info(show"Using config directory $configDirectory")
_ <- Files[F].createDirectories(configDirectory)
configPath = configDirectory / "service.conf"
configDir <- xdgDirectory(env, "XDG_CONFIG_HOME")
_ <- Logger[F].info(show"Using config directory $configDir")
_ <- Files[F].createDirectories(configDir)
configPath = configDir / "service.conf"

configPathExists <- Files[F].exists(configPath)
_ = println(configPath)
(config, msg) <-
if (configPathExists)
readUserConfig[F](configPath).tupleRight(
Expand All @@ -37,14 +36,29 @@ object Config {
Async[F].pure(
(
emptyServiceConfig.toServiceConfig(
configDirectory = configDirectory.toString,
configDirectory = configDir.toString,
configExists = false
),
show"No config file found at $configPath, using defaults"
)
)
_ <- Logger[F].info(msg)
} yield config

private def xdgDirectory[F[_]: Async](
env: Env[F],
envVar: String
): F[Path] = {
lazy val fallbackConfigDir = Async[F]
.delay(System.getProperty("user.home"))
.map(s => Path(s) / ".config")

env
.get(envVar)
.flatMap { opt =>
opt.fold(fallbackConfigDir)(s => Async[F].pure(Path(s)))
}
.map(path => (path / "libro-finito").absolute)
}

private def readUserConfig[F[_]: Async: Logger](
Expand Down

0 comments on commit 7281d55

Please sign in to comment.