Skip to content

edadma/hocon

Repository files navigation

hocon

Maven Central Last Commit GitHub Scala Version ScalaJS Version Scala Native Version

A pure-Scala, cross-platform implementation of HOCON (Human-Optimized Config Object Notation) — the config format popularized by Lightbend's com.typesafe:config.

Unlike com.typesafe:config, which is JVM-only, hocon is written in pure Scala 3 with no java.* dependencies in its core, so the same parser runs on the JVM, in the browser/Node.js via Scala.js, and as a native binary via Scala Native.

Status: Phases 1–4 complete — the lexer, parser, untyped Config API, object merging, substitutions, value concatenation, and durations/sizes are in and tested on all three platforms. include directives and a typed (case-class) decoder follow. See the roadmap below.

Usage

import io.github.edadma.hocon.*

val config = Hocon.parse("""
  en {
    greeting = "Hello, world"
    nav { home = "Home", about = "About" }
    cart.items = "{count} items"   # path-expression key
  }
""")

config.getString("en.greeting")   // "Hello, world"
config.getString("en.nav.home")   // "Home"
config.getConfig("en").getInt("...")
config.hasPath("en.missing")      // false

// i18n placeholder helper
val msg = Messages(config.getConfig("en"))
msg("cart.items", "count" -> 3)   // "3 items"

Merging and fallback

A partial config falls back to a base — perfect for a base locale with per-locale overrides:

val base = Hocon.parse("""greeting = "Hello", farewell = "Goodbye"""")
val fr   = Hocon.parse("""greeting = "Bonjour"""")

val messages = Messages(fr.withFallback(base))
messages("greeting")   // "Bonjour"  (translated)
messages("farewell")   // "Goodbye"  (from base)

// Merge several layers; later arguments win:
val effective = Hocon.load(base, fr, userOverrides)

Objects merge recursively; arrays and scalars replace. A null in the override shadows (unsets) the fallback value at that key.

Substitutions

${path} references another value in the same document; ${?path} is optional and disappears when nothing is found. Substitutions resolve against the merged root, so they are order-independent.

val config = Hocon.parse("""
  host    = localhost
  url     = ${host}              # → "localhost"
  service = ${defaults}          # copies the whole object
  defaults { timeout = 30 }
""")

// A missing required substitution throws; an optional one is simply absent:
Hocon.parse("x = ${?missing}").hasPath("x")   // false

// Fall back to the environment for anything not in the config:
Hocon.parse("home = ${HOME}", EnvSource.fromMap(sys.env))

Circular references throw CircularReferenceException.

Value concatenation

Pieces written side by side with only whitespace between them concatenate. Strings, numbers, and substitutions join into one string (interior whitespace preserved); arrays concatenate element-wise; objects deep-merge left to right.

val config = Hocon.parse("""
  host = example.com
  port = 8080
  url  = "http://"${host}":"${port}    // → "http://example.com:8080"
  xs   = [1, 2] [3, 4]                 // → [1, 2, 3, 4]
""")

Durations and sizes

Read unit-suffixed values with getDuration (a cross-platform FiniteDuration) and getBytes (a Long):

val config = Hocon.parse("timeout = 10s, cache = 512K")
config.getDuration("timeout")   // 10.seconds
config.getBytes("cache")        // 524288

Duration units are ns/us/ms/s/m/h/d (bare number = milliseconds); size units distinguish powers of 1024 (K, Ki, KiB) from powers of 1000 (kB, MB).

A note on quoting: HOCON allows unquoted strings, but forbids the characters $ " { } [ ] : = , + # ^ ? ! @ * &and` inside them. Most UI strings (Hello, world, Are you sure?, {count} items) hit one of these, so quote your translation strings. This is spec-correct HOCON, not a limitation of this library.

Why

HOCON is a comfortable format for configuration and, in particular, for internationalization (i18n) message files — nested, commented, human-friendly:

# messages.en.conf
en {
  greeting = "Hello, world"
  nav { home = "Home", about = "About" }
  cart.items = "{count} items"      # path-expression key
}

Until now there has been no good way to read these on Scala.js or Scala Native. That's the gap hocon fills.

Roadmap

Phase Scope Status
1 Lexer + parser → untyped Config (comments, quoted/unquoted strings, nested objects, path-expression keys, arrays). i18n-usable.
2 Object merging + withFallback (base locale + overrides).
3 Substitutions: ${path}, ${?path}, env fallback, cycle detection.
4 Value concatenation + durations (10s) and sizes (512K, 10MB).
5 include directives behind a pluggable, per-platform IO source.
6 Typed decoder with case-class derivation (config.as[A]).
7 Conformance against the Typesafe spec's test corpus.

Building and Testing

sbt hoconJVM/test
sbt hoconJS/test
sbt hoconNative/test

License

ISC — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages