我刚刚注意到您不需要像 shapeless 这样的库。如果有什么安慰的话,这是一个最终将取代 scala 反射宏的库,因此它与您在不重新发明轮子的情况下获得的纯 scala 一样接近。
我想我可能有一些可能对此有所帮助的东西。这是一种繁重的解决方案,但我认为它会满足您的要求。
这使用了奇妙的 scalameta (http://www.scalameta.org) 库来创建静态注释。您将注释您的案例类,然后此内联宏将为您的命令行参数生成适当的 scopt 解析器。
您的 build.sbt 将需要宏天堂插件以及 scalameta 库。您可以将这些添加到您的项目中。
addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full)
libraryDependencies ++= Seq(
"org.scalameta" %% "scalameta" % meta % Provided,
)
将这些依赖项添加到构建后,您必须为宏创建一个单独的项目。
完整的 SBT 项目定义如下所示:
lazy val macros = project
.in(file("macros"))
.settings(
addCompilerPlugin("org.scalameta" % "paradise" % paradise cross CrossVersion.full),
libraryDependencies ++= Seq(
"org.scalameta" %% "scalameta" % "1.8.0" % Provided,
)
)
如果模块本身被命名为“宏”,则创建一个类,这里是静态注解。
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.meta._
@compileTimeOnly("@Opts not expanded")
class Opts extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
defn match {
case q"..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) extends $template" =>
val opttpe = Type.Name(tname.value)
val optName = Lit.String(tname.value)
val opts = paramss.flatten.map {
case param"..${_} $name: ${tpeopt: Option[Type]} = $expropt" =>
val tpe = Type.Name(tpeopt.get.toString())
val litName = Lit.String(name.toString())
val errMsg = Lit.String(s"${litName.value} is required.")
val tname = Term.Name(name.toString())
val targ = Term.Arg.Named(tname, q"x")
q"""
opt[$tpe]($litName)
.required()
.action((x, c) => c.copy($targ))
.text($errMsg)
"""
}
val stats = template.stats.getOrElse(Nil) :+ q"def options: OptionParser[$opttpe] = new OptionParser[$opttpe]($optName){ ..$opts }"
q"""..$mods class $tname[..$tparams] ..$ctorMods (...$paramss) {
import scopt._
..$stats
}"""
}
}
}
之后,您将使您的主模块依赖于您的宏模块。然后你可以像这样注释你的案例类......
@Opts
case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String)
这将在编译时扩展您的案例类以包含 scopt 定义。这是从上面生成的类的样子。
case class Options(name: String, job: String, age: Int, netWorth: Double, job_title: String) {
import scopt._
def options: OptionParser[Options] = new OptionParser[Options]("Options") {
opt[String]("name").required().action((x, c) => c.copy(name = x)).text("name is required.")
opt[String]("job").required().action((x, c) => c.copy(job = x)).text("job is required.")
opt[Int]("age").required().action((x, c) => c.copy(age = x)).text("age is required.")
opt[Double]("netWorth").required().action((x, c) => c.copy(netWorth = x)).text("netWorth is required.")
opt[String]("job_title").required().action((x, c) => c.copy(job_title = x)).text("job_title is required.")
}
}
这应该可以为您节省大量的样板文件,对于任何对内联宏有更多了解的人,请随时告诉我如何才能更好地编写此代码,因为我不是这方面的专家。
您可以在 http://scalameta.org/tutorial/#Macroannotations 找到相应的教程和文档,我也很乐意回答您可能对这种方法提出的任何问题!