我只注意到你想要不一樣的庫無形。如果有任何安慰的話,這是一個將最終取代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找到相應的教程和相關文檔,我也很樂意回答您對此方法可能存在的任何問題!
我想通過「scala選項解析器」,你的意思是[scopt](https://github.com/scopt/scopt)? – thibr
如果你想這樣做,你會發現自己重塑無形。您正在尋找類似'LabelledGeneric'的東西。 – Alec
@Alec,只要我只需要寫一小段代碼,重新實現輪子就完全可以接受。 – zinking