An sbt plugin to generate Scala objects containing the contents of glob-specified files as strings or byte-arrays.
The accompanying macro allowing to access those objects more easily: embedded-files-macro.
addSbtPlugin("com.yurique" % "sbt-embedded-files" % "0.4.0") libraryDependencies += "com.yurique" %%% "embedded-files-macro" % "{embedded-files-macro-version}"Put a file into src/main/resources/docs/test.txt:
I'm a test text.
Add embedFiles to the Compile / sourceGenerators:
  project
    // ...
    .enablePlugins(EmbeddedFilesPlugin)
    // ...
    .settings(
      (Compile / sourceGenerators) += embedFiles
    )In the code:
import com.yurique.embedded.FileAsString
val testTxtContents = FileAsString("/docs/test.txt") // "I'm a test text."The sbt plugin has the following configuration keys:
  project
    // ...
    .settings(
      // default is __embedded_files, which is assumed by the macro
      // the generated objects and classes are put into this package (and sub-packages)
      embedRootPackage := "custom_root_package",
      // a list of directories to look for files to embed in
      // default is (Compile / unmanagedResourceDirectories)
      embedDirectories ++= (Compile / unmanagedSourceDirectories).value,
      // a list of globs for text files
      // default is Seq("**/*.txt")
      embedTextGlobs := Seq("**/*.txt", "**/*.md"),
      // a list of globs for binary files
      // default is Seq.empty
      embedBinGlobs := Seq("**/*.bin"),
      // whether or not to generate a EmbeddedFilesIndex object containing references to all embedded files
      // default is false
      embedGenerateIndex := true,
      // the intended usage is to use the output of the embedFiles task as generated sources
      (Compile / sourceGenerators) += embedFiles
    )The embedFiles always generates these two abstract classes:
package ${embedRootPackage.value}
abstract class EmbeddedTextFile {
  def path: String
  def content: String
}
and
package ${embedRootPackage.value}
abstract class EmbeddedBinFile {
  def path: String
  def content: Array[Byte]
}For each file in the embedDirectories that matches any of the embedTextGlobs a file like the following is generated:
package ${embedRootPackage.value}.${subPackage}
object ${className} extends __embedded_files.EmbeddedTextFile {
  val path: String = """path/to/the/file/filename.txt"""
  val content: String = """the content of the file"""
}For each file in the embedDirectories that matches any of the embedBinGlobs a file like the following is generated:
package ${embedRootPackage.value}.${subPackage}
object ${className} extends __embedded_files.EmbeddedBinFile {
  val path: String = """path/to/the/file/filename.bin"""
  val content: Array[Byte] = Array(
    // example bytes
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
  )
}For each file, its path is taken relative to the one of the embedDirectories and converted into the package name and the class name.
For example, if the file was /home/user/.../.../project/src/main/resources/some-dir/1 some sub dir/some things & other things.txt:
- a relative path is 
some-dir/1 some sub dir/some things & other things.txt(relative toCompile / unmanagedSourceDirectoriesin this example) - the dirname is 
some-dir/1 some sub dir, it is split by/, every part is converted to a valid Scala ID (by replacing non alpha-numerics by_and prepending_is the path starts with a digit) - the resulting package name is 
some_dir._1_some_sub_dir - the class name is derived from the file name: 
some_things_other_things 
if embedGenerateIndex is set to true, the index file is generated like the following:
package ${embedRootPackage.value}
object EmbeddedFilesIndex {
  val textFiles: Seq[(String, EmbeddedTextFile)] = Seq(
    "test/test-resource.txt" -> __embedded_files.test.test_resource_txt,
    "com/company/test_file_1.txt" -> __embedded_files.com.company.test_file_1_txt
  )
  val binFiles: Seq[(String, EmbeddedBinFile)] = Seq(
    "test/test-bin-resource.bin" -> __embedded_files.test.test_bin_resource_bin,
    "com/company/test_bin_file_1.bin" -> __embedded_files.com.company.test_bin_file_1_bin
  )
}It is possible to configure a basic String => String transformation for text files.
project
  // ...
  .settings(
    embedDirectories ++= (Compile / unmanagedSourceDirectories).value,
    embedTextGlobs := Seq("**/*.md"),
    embedTransform := Seq(
      TransformConfig(
        when = _.getFileName.toString.endsWith(".md"),
        transform = _.toUpperCase          
      )
    ),
    (Compile / sourceGenerators) += embedFiles
  )For each text file, the .transform function of the first of the TransformConfig-s that matches the path (.when returns true)
will be applied to the contents of the file.
If none of the configuration matches - the file contents will be used as is.
If more than one configuration matches, only the first one will be used.
Assuming the embedFiles is used as a source generator with the default root package, you can use the macros provided by the embedded-files-macro library to get the string/byte-array content of the files like this:
import com.yurique.embedded._
val s: String = FileAsString("/docs/test.txt")
val b: Array[Byte] = FileAsByteArray("/bins/test.bin")If the path passed to the macros starts with a slash, it is used as is.
If it doesn't start with a slash, the macro does the following:
/home/user/.../project/src/main/scala/com/company/MyClass.scala
package com.company.MyClass
object MyClass {
  val s: String = FileAsString("dir/data.txt")
}Here, the file name doesn't start with a / – dir/data.txt.
The calling site is in the /home/user/.../project/src/main/scala/com/company/ directory.
The requested file name is appended to this directory, resulting in /home/user/.../project/src/main/scala/com/company/dir/data.txt.
This file is taken relative to the first scala directory in the path, resulting in /com/company/dir/data.txt.
The macros are not currently doing any checks for whether the embedded files exist. If they don't, scalac will just fail to compile.