Skip to content

Commit

Permalink
Add single_file and preamble.
Browse files Browse the repository at this point in the history
Fixes #67
  • Loading branch information
thesamet committed Apr 12, 2016
1 parent 97569d3 commit f6fa0f9
Show file tree
Hide file tree
Showing 8 changed files with 889 additions and 38 deletions.
408 changes: 397 additions & 11 deletions compiler-plugin/src/main/java/com/trueaccord/scalapb/Scalapb.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,6 @@ trait DescriptorPimps {

def baseName(fileName: String) =
fileName.split("/").last.replaceAll(raw"[.]proto$$|[.]protodevel", "")

}

object Helper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import scala.collection.JavaConversions._

case class GeneratorParams(javaConversions: Boolean = false, flatPackage: Boolean = false, grpc: Boolean = false)

// Exceptions that are caught and passed upstreams as errors.
case class GeneratorException(message: String) extends Exception(message)

class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
def printEnum(e: EnumDescriptor, printer: FunctionalPrinter): FunctionalPrinter = {
val name = e.nameSymbol
Expand Down Expand Up @@ -887,6 +890,9 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
}

def scalaFileHeader(file: FileDescriptor): FunctionalPrinter = {
if (file.scalaOptions.getPreambleList.nonEmpty && !file.scalaOptions.getSingleFile) {
throw new GeneratorException(s"${file.getName}: single_file must be true when a preamble is provided.")
}
new FunctionalPrinter().addM(
s"""// Generated by the Scala Plugin for the Protocol Buffer Compiler.
|// Do not edit!
Expand All @@ -900,6 +906,8 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
case (i, printer) => printer.add(s"import $i")
}
.add("")
.seq(file.scalaOptions.getPreambleList)
.when(file.scalaOptions.getPreambleList.nonEmpty)(_.add(""))
}

def generateFileDescriptor(file: FileDescriptor)(fp: FunctionalPrinter): FunctionalPrinter = {
Expand Down Expand Up @@ -960,8 +968,24 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
}
}

def generateScalaFilesForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val serviceFiles = if(params.grpc) {
def generateSingleScalaFileForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val code =
scalaFileHeader(file)
.print(file.getEnumTypes)(printEnum)
.print(file.getMessageTypes)(printMessage)
.add(s"object ${file.fileDescriptorObjectName} {")
.indent
.call(generateFileDescriptor(file))
.outdent
.add("}").result()
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaPackageName.replace('.', '/') + "/" + file.fileDescriptorObjectName + ".scala")
b.setContent(code)
generateServiceFiles(file) :+ b.build
}

def generateServiceFiles(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
if(params.grpc) {
file.getServices.map { service =>
val p = new GrpcServicePrinter(service, params)
val code = p.printService(FunctionalPrinter()).result()
Expand All @@ -971,6 +995,10 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
b.build
}
} else Nil
}

def generateMultipleScalaFilesForFileDescriptor(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
val serviceFiles = generateServiceFiles(file)

val enumFiles = for {
enum <- file.getEnumTypes
Expand Down Expand Up @@ -1026,18 +1054,27 @@ object ProtobufGenerator {
val b = CodeGeneratorResponse.newBuilder
parseParameters(request.getParameter) match {
case Right(params) =>
val generator = new ProtobufGenerator(params)
val filesByName: Map[String, FileDescriptor] =
request.getProtoFileList.foldLeft[Map[String, FileDescriptor]](Map.empty) {
case (acc, fp) =>
val deps = fp.getDependencyList.map(acc)
acc + (fp.getName -> FileDescriptor.buildFrom(fp, deps.toArray))
try {
val generator = new ProtobufGenerator(params)
import generator.FileDescriptorPimp
val filesByName: Map[String, FileDescriptor] =
request.getProtoFileList.foldLeft[Map[String, FileDescriptor]](Map.empty) {
case (acc, fp) =>
val deps = fp.getDependencyList.map(acc)
acc + (fp.getName -> FileDescriptor.buildFrom(fp, deps.toArray))
}
request.getFileToGenerateList.foreach {
name =>
val file = filesByName(name)
val responseFiles =
if (file.scalaOptions.getSingleFile)
generator.generateSingleScalaFileForFileDescriptor(file)
else generator.generateMultipleScalaFilesForFileDescriptor(file)
b.addAllFile(responseFiles)
}
request.getFileToGenerateList.foreach {
name =>
val file = filesByName(name)
val responseFiles = generator.generateScalaFilesForFileDescriptor(file)
b.addAllFile(responseFiles)
} catch {
case e: GeneratorException =>
b.setError(e.message)
}
case Left(error) =>
b.setError(error)
Expand Down
33 changes: 33 additions & 0 deletions e2e/src/main/protobuf/sealed_trait.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
syntax = "proto2";

package com.trueaccord.proto.e2e;

import "scalapb/scalapb.proto";

// Demonstrates a sealed trait and two messages extending it.

option (scalapb.options) = {
// All classes that extend a sealed trait need to be in the same Scala
// file, so we set single_file to true.
single_file: true

// Generate the base trait.
preamble: [
"sealed trait MyBaseTrait {",
" def getFoo: Int",
" def getBar: String",
"}"
];
};

message Type1 {
option (scalapb.message).extends = "MyBaseTrait";
optional int32 foo = 1;
optional string bar = 2;
}

message Type2 {
option (scalapb.message).extends = "MyBaseTrait";
optional int32 foo = 1;
optional string bar = 2;
}
4 changes: 2 additions & 2 deletions proptest/src/test/scala/GeneratedCodeSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ class GeneratedCodeSpec extends PropSpec with GeneratorDrivenPropertyChecks with
javaParse(scalaAscii) should be(javaProto)
javaParse(scalaUnicodeAscii) should be(javaProto)

val jsonRep = com.trueaccord.scalapb.json.JsonFormat.toJson(scalaProto)
com.trueaccord.scalapb.json.JsonFormat.fromJson(jsonRep)(companion.asInstanceOf[GeneratedMessageCompanion[T] forSome {type T <: GeneratedMessage with ScalaPBMessage[T] }]) should be(scalaProto)
val jsonRep = com.trueaccord.scalapb.json.JsonFormat.toJsonString(scalaProto)
com.trueaccord.scalapb.json.JsonFormat.fromJsonString(jsonRep)(companion.asInstanceOf[GeneratedMessageCompanion[T] forSome {type T <: GeneratedMessage with ScalaPBMessage[T] }]) should be(scalaProto)

} catch {
case e: Exception =>
Expand Down
2 changes: 2 additions & 0 deletions proptest/src/test/scala/GraphGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,14 @@ object GraphGen {
def genScalaOptions(state: State): Gen[(Option[ScalaPbOptions], State)] = for {
(scalaPackageName, state) <- GenUtils.listWithStatefulGen(state, minSize = 1, maxSize = 4)(_.generateName)
flatPackage <- Gen.oneOf(true, false)
singleFile <- Gen.oneOf(true, false)
} yield {
val b = ScalaPbOptions.newBuilder
if (scalaPackageName.nonEmpty) {
b.setPackageName(scalaPackageName.mkString("."))
}
b.setFlatPackage(flatPackage)
.setSingleFile(singleFile)
(Some(b.build), state)
}

Expand Down
8 changes: 8 additions & 0 deletions protobuf/scalapb/scalapb.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ message ScalaPbOptions {
// Adds the following imports at the top of the file (this is meant
// to provide implicit TypeMappers)
repeated string import = 3;

// If true, all messages and enums (but not services) will be written
// to a single Scala file.
optional bool single_file = 5;

// Text to add to the generated scala file. This can be used only
// when single_file is true.
repeated string preamble = 4;
}

extend google.protobuf.FileOptions {
Expand Down
Loading

0 comments on commit f6fa0f9

Please sign in to comment.