Skip to content

Commit

Permalink
Good part of #312 (doc comments) implemented (#314)
Browse files Browse the repository at this point in the history
implement good part of #312 "doc comments"
  • Loading branch information
carueda authored Dec 18, 2024
1 parent 6adb2e6 commit 2368ac4
Show file tree
Hide file tree
Showing 41 changed files with 1,038 additions and 24 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
2024-12

1.2.0

- Advancing #312 "Reflect doc comments in generated code"
- Most cases already covered, both for scala and java records/POJOs
- Note: `@define`s not yet addressed.
- Comments used for annotations, i.e., starting with `@`, are not considered.
- Also ignored are comments starting with `!`.
- Doc generation processing is always performed.
(In retrospect, this should have been implemented long ago, but better late than never 😅)
- however, for now, `--no-doc` can be used to opt out of doc generation

1.1.5

- Fixed #309 "Empty object not reflected in generated wrapper."
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ enablePlugins(BuildInfoPlugin)

organization := "com.github.carueda"
name := "tscfg"
version := "1.1.5"
version := "1.2.0"
scalaVersion := "3.3.4"
crossScalaVersions := Seq("2.13.9", "3.3.4")

Expand Down
45 changes: 42 additions & 3 deletions src/main/java/tscfg/example/JavaExampleCfg.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,58 @@
// generated by tscfg 0.9.92 on Thu Aug 08 12:07:41 PDT 2019
// generated by tscfg 1.2.0 on Tue Dec 17 21:46:38 PST 2024
// source: src/main/tscfg/example/example.spec.conf

package tscfg.example;

public class JavaExampleCfg {

/**
* Description of the required endpoint section.
*/
public final JavaExampleCfg.Endpoint endpoint;

/**
* Description of the required endpoint section.
*/
public static class Endpoint {

/**
* a required int
*/
public final int intReq;

/**
* Interface definition
*/
public final Endpoint.Interface_ interface_;

/**
* a required String
*/
public final java.lang.String path;

/**
* an optional Integer with default value null
*/
public final java.lang.Integer serial;

/**
* a String with default value "https://example.net"
*/
public final java.lang.String url;

/**
* Interface definition
*/
public static class Interface_ {

/**
* an int with default value 8080
*/
public final int port;

/**
* Interface type
*/
public final java.lang.String type;

public Interface_(com.typesafe.config.Config c, java.lang.String parentPath, $TsCfgValidator $tsCfgValidator) {
Expand All @@ -26,7 +66,7 @@ public Endpoint(com.typesafe.config.Config c, java.lang.String parentPath, $TsCf
this.interface_ = c.hasPathOrNull("interface") ? new Endpoint.Interface_(c.getConfig("interface"), parentPath + "interface.", $tsCfgValidator) : new Endpoint.Interface_(com.typesafe.config.ConfigFactory.parseString("interface{}"), parentPath + "interface.", $tsCfgValidator);
this.path = $_reqStr(parentPath, c, "path", $tsCfgValidator);
this.serial = c.hasPathOrNull("serial") ? c.getInt("serial") : null;
this.url = c.hasPathOrNull("url") ? c.getString("url") : "http://example.net";
this.url = c.hasPathOrNull("url") ? c.getString("url") : "https://example.net";
}
private static int $_reqInt(java.lang.String parentPath, com.typesafe.config.Config c, java.lang.String path, $TsCfgValidator $tsCfgValidator) {
if (c == null) return 0;
Expand Down Expand Up @@ -76,4 +116,3 @@ void validate() {
}
}
}

6 changes: 6 additions & 0 deletions src/main/scala/tscfg/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ object Main {
| --scala generate scala code (java)
| --scala:bt use backticks (see #30) (false)
| --durations use java.time.Duration (false)
| --no-doc do not capture doc comments (see #312)
| --all-required assume all properties are required (see #47)
| --tpl <filename> generate config template (no default)
| --tpl.ind <string> template indentation string ("${templateOpts.indent}")
Expand All @@ -57,6 +58,7 @@ object Main {
packageName: String = defaultGenOpts.packageName,
className: String = defaultGenOpts.className,
destDir: String = defaultDestDir,
genDoc: Boolean = true,
assumeAllRequired: Boolean = false,
language: String = "java",
useBackticks: Boolean = false,
Expand Down Expand Up @@ -121,6 +123,9 @@ object Main {
traverseList(rest, opts.copy(destDir = destDir))
else None

case "--no-doc" :: rest =>
traverseList(rest, opts.copy(genDoc = false))

case "--all-required" :: rest =>
traverseList(rest, opts.copy(assumeAllRequired = true))

Expand Down Expand Up @@ -197,6 +202,7 @@ object Main {
val genOpts = GenOpts(
opts.packageName,
opts.className,
genDoc = opts.genDoc,
assumeAllRequired = opts.assumeAllRequired,
useBackticks = opts.useBackticks,
genGetters = opts.genGetters,
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/tscfg/ModelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class ModelBuilder(
effOptional,
effDefault,
childStruct.defineCaseOpt,
docComments = childStruct.docComments,
commentsOpt,
parentClassMembers,
)
Expand Down Expand Up @@ -167,6 +168,7 @@ class ModelBuilder(
effOptional: Boolean,
effDefault: Option[String],
defineCase: Option[DefineCase],
docComments: List[String],
commentsOpt: Option[String],
parentClassMembers: Option[Map[String, model.AnnType]]
): AnnType = {
Expand All @@ -192,6 +194,7 @@ class ModelBuilder(
optional = effOptional,
default = effDefault,
defineCase = defineCase,
docComments = docComments,
comments = commentsOpt,
parentClassMembers = parentClassMembers.map(_.toMap),
)
Expand Down Expand Up @@ -369,7 +372,7 @@ object ModelBuilder {
}

/** build model from TS Config object */
def fromConfig(
private def fromConfig(
rootNamespace: NamespaceMan,
config: Config,
assumeAllRequired: Boolean = false
Expand Down
7 changes: 7 additions & 0 deletions src/main/scala/tscfg/Struct.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ case class Struct(
val comments: List[String] =
cv.origin().comments().asScala.toList

val docComments: List[String] =
comments
.map(_.trim)
.filterNot(c =>
c.startsWith("@") || c.startsWith("!") || c.startsWith("GenOpts:")
)

// Non-None when this is a `@define`
val defineCaseOpt: Option[DefineCase] = {
val defineLines = comments.map(_.trim).filter(_.startsWith("@define"))
Expand Down
26 changes: 24 additions & 2 deletions src/main/scala/tscfg/example/ScalaExampleCfg.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// generated by tscfg 0.9.994 on Sat Oct 30 17:07:40 PDT 2021
// generated by tscfg 1.2.0 on Tue Dec 17 21:46:40 PST 2024
// source: src/main/tscfg/example/example.spec.conf

package tscfg.example
Expand All @@ -7,6 +7,20 @@ final case class ScalaExampleCfg(
endpoint: ScalaExampleCfg.Endpoint
)
object ScalaExampleCfg {

/** Description of the required endpoint section.
*
* @param serial
* an optional Integer with default value null
* @param path
* a required String
* @param url
* a String with default value "https://example.net"
* @param interface
* Interface definition
* @param intReq
* a required int
*/
final case class Endpoint(
intReq: scala.Int,
interface: ScalaExampleCfg.Endpoint.Interface,
Expand All @@ -15,6 +29,14 @@ object ScalaExampleCfg {
url: java.lang.String
)
object Endpoint {

/** Interface definition
*
* @param `type`
* Interface type
* @param port
* an int with default value 8080
*/
final case class Interface(
port: scala.Int,
`type`: scala.Option[java.lang.String]
Expand Down Expand Up @@ -51,7 +73,7 @@ object ScalaExampleCfg {
if (c.hasPathOrNull("serial")) Some(c.getInt("serial")) else None,
url =
if (c.hasPathOrNull("url")) c.getString("url")
else "http://example.net"
else "https://example.net"
)
}
private def $_reqInt(
Expand Down
15 changes: 15 additions & 0 deletions src/main/scala/tscfg/files.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tscfg

import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}
import scala.util.{Try, Using}

object files {
def readFile(file: File): Try[String] =
Using(io.Source.fromFile(file))(_.mkString)

def writeFile(path: Path, content: String): Try[Unit] = Try {
Files.writeString(path, content, StandardCharsets.UTF_8)
}
}
11 changes: 11 additions & 0 deletions src/main/scala/tscfg/gen4tests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ object gen4tests {
case opt @ "--java:getters" =>
OptFromFile(opt, Some("java"))

case opt @ "--java" =>
OptFromFile(opt, Some("java"))

case opt @ "--java:records" =>
OptFromFile(opt, Some("java"))

Expand All @@ -83,6 +86,9 @@ object gen4tests {
case opt @ "--durations" =>
OptFromFile(opt, None)

case opt @ "--no-doc" =>
OptFromFile(opt, None)

case opt @ "--all-required" =>
OptFromFile(opt, None)

Expand Down Expand Up @@ -111,6 +117,8 @@ object gen4tests {

case "--scala:bt" => genOpts = genOpts.copy(useBackticks = true)

case "--java" => ()

case "--java:getters" => genOpts = genOpts.copy(genGetters = true)

case "--java:records" => genOpts = genOpts.copy(genRecords = true)
Expand All @@ -122,6 +130,9 @@ object gen4tests {
case "--all-required" =>
genOpts = genOpts.copy(assumeAllRequired = true)

case "--no-doc" =>
genOpts = genOpts.copy(genDoc = false)

case opt =>
warn(s"$confFile: ignoring unrecognized GenOpts argument: `$opt'")
}
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/tscfg/generators/Generator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ abstract class Generator(genOpts: GenOpts) {
case class GenOpts(
packageName: String,
className: String,
genDoc: Boolean = true,
assumeAllRequired: Boolean = false,
useBackticks: Boolean = false,
genGetters: Boolean = false,
Expand Down
72 changes: 72 additions & 0 deletions src/main/scala/tscfg/generators/docUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package tscfg.generators

import tscfg.model.{AnnType, ObjectType}

object docUtil {
def getDoc(
a: AnnType,
genOpts: GenOpts,
symbol2id: String => String,
onlyField: Boolean = false,
genScala: Boolean = false,
indent: String = "",
): String = {
if (!genOpts.genDoc) ""
else {
val withParams = genScala || genOpts.genRecords
a.t match {
case ot: ObjectType =>
val paramDocs = if (withParams) {
// only reflect params with comments
ot.members.toList.flatMap { case (k, memberAnnType) =>
val paramComments = memberAnnType.docComments
if (paramComments.isEmpty) None
else Some(ParamDoc(symbol2id(k), paramComments))
}
}
else Nil
if (a.docComments.isEmpty && paramDocs.isEmpty) ""
else formatDocComment(a.docComments, paramDocs, genScala, indent)

case _ if onlyField =>
if (a.docComments.isEmpty) ""
else formatDocComment(a.docComments, Nil, genScala, indent)

case _ => ""
}
}
}

private case class ParamDoc(name: String, docLines: List[String])

private def formatDocComment(
docComments: List[String],
paramDocs: List[ParamDoc],
genScala: Boolean = false,
indent: String,
): String = {
val lines = collection.mutable.ArrayBuffer[String]()
val (start, sep, end) = if (genScala) {
("\n/** ", "\n * ", "\n */\n")
}
else {
lines += ""
("\n/**", "\n * ", "\n */\n")
}
lines.addAll(docComments.map(_.trim).filter(_.nonEmpty))
if (paramDocs.nonEmpty) {
lines += ""
paramDocs foreach { pd =>
lines += s"@param ${pd.name}"
pd.docLines.foreach(lines += " " + _)
}
}
val res = lines.map(escapeForDoc).mkString(start, sep, end)
if (res.isEmpty) ""
else indent + res.replaceAll("\n", "\n" + indent)
}

private def escapeForDoc(content: String): String = content
.replace("/*", "/\\*") // start of comment
.replace("*/", "*\\/") // end of comment
}
Loading

0 comments on commit 2368ac4

Please sign in to comment.