-
-
Notifications
You must be signed in to change notification settings - Fork 609
/
OutputHelpers.scala
184 lines (162 loc) · 7.33 KB
/
OutputHelpers.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package slick.codegen
import java.io.File
import java.io.BufferedWriter
import java.io.FileWriter
/** Output-related code-generation utilities. */
trait OutputHelpers{
def code: String
/** The parent type of the generated main trait. This can be overridden in subclasses. */
def parentType: Option[String] = None
/**
* The generated code stored in a map that associates the scala filename with the generated code (this map contains one entry per table).
*/
def codePerTable: Map[String, String]
/**
* Foreign keys used for mapping a minimal set of dependencies between tables.
*/
def foreignKeysPerTable: Map[String, List[String]]
/**
* The generated code used to generate the container class.
*/
def codeForContainer: String
/** Indents all but the first line of the given string
* No indent is added to empty lines.
*/
def indent(code: String): String
/** Writes given content to a file.
* Ensures the file ends with a newline character.
* @group Output
*/
def writeStringToFile(content: String, folder: String, pkg: String, fileName: String): Unit = {
val folder2: String = folder + "/" + pkg.replace(".", "/") + "/"
new File(folder2).mkdirs()
val file = new File(folder2 + fileName)
if (!file.exists()) {
file.createNewFile()
}
file.setWritable(true)
val fw = new FileWriter(file.getAbsoluteFile());
val bw = new BufferedWriter(fw)
bw.write(content)
if (!content.endsWith("\n")) bw.write("\n")
bw.close()
file.setWritable(false)
}
/**
* Generates code and writes it to a file.
* Creates a folder structure for the given package inside the given srcFolder
* and places the new file inside or overrides the existing one.
* @group Output
* @param folder target folder, in which the package structure folders are placed
* @param profile Slick profile that is imported in the generated package (e.g. slick.jdbc.H2Profile)
* @param pkg Scala package the generated code is placed in (a subfolder structure will be created within srcFolder)
* @param container The name of a trait and an object the generated code will be placed in within the specified package.
* @param fileName Name of the output file, to which the code will be written
*/
def writeToFile(profile: String, folder:String, pkg: String, container:String="Tables", fileName: String="Tables.scala"): Unit = {
writeStringToFile(packageCode(profile, pkg, container, parentType), folder, pkg, fileName)
}
/**
* Generates code and writes it to multiple files.
* Creates a folder structure for the given package inside the given srcFolder
* and places the new files inside or overrides the existing one.
* @group Output
* @param folder target folder, in which the output files are placed
* @param profile Slick profile that is imported in the generated package (e.g. scala.slick.driver.H2Driver)
* @param pkg Scala package the generated code is placed in (a subfolder structure will be created within srcFolder)
* @param container The name of a trait and an object the generated code will be placed in within the specified package.
*/
def writeToMultipleFiles(profile: String, folder: String, pkg: String, container: String = "Tables"): Unit = {
// Write the container file (the file that contains the stand-alone object).
writeStringToFile(rootTraitCode(profile, pkg, container), folder, pkg, container + "Root.scala")
writeStringToFile(packageContainerCode(profile, pkg, container), folder, pkg, container + ".scala")
// Write one file for each table.
codePerTable.foreach {
case (tableName, tableCode) => writeStringToFile(packageTableCode(tableName, tableCode, pkg, container), folder, pkg, handleQuotedNamed(tableName)+".scala")
}
}
private def handleQuotedNamed(tableName: String) = {
if (tableName.endsWith("`")) s"${tableName.init}Table`" else s"${tableName}Table"
}
def rootTraitCode(profile: String, pkg: String, container: String = "Tables"): String = {
s"""
package ${pkg}
// AUTO-GENERATED Slick data model
trait ${container}Root {
val profile: slick.jdbc.JdbcProfile
}
"""
}
/**
* Generates code providing the data model as trait and object in a Scala package
* @group Basic customization overrides
* @param profile Slick profile that is imported in the generated package (e.g. slick.jdbc.H2Profile)
* @param pkg Scala package the generated code is placed in
* @param container The name of a trait and an object the generated code will be placed in within the specified package.
*/
def packageCode(profile: String, pkg: String, container: String, parentType: Option[String]) : String = {
s"""
package ${pkg}
// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object ${container} extends ${container} {
val profile: slick.jdbc.JdbcProfile = $profile
}
/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */
trait ${container}${parentType.map(t => s" extends $t").getOrElse("")} {
val profile: slick.jdbc.JdbcProfile
import profile.api._
${indent(code)}
}
""".trim()
}
/**
* Generates code providing the stand-alone slick data model for immediate use.
* @group Basic customization overrides
* @param profile Slick profile that is imported in the generated package (e.g. scala.slick.driver.H2Driver)
* @param pkg Scala package the generated code is placed in
* @param container The name of a trait and an object the generated code will be placed in within the specified package.
*/
def packageContainerCode(profile: String, pkg: String, container: String = "Tables"): String = {
val tableTraits = parentType.toSeq ++ codePerTable.keys.map(tableName => s"${handleQuotedNamed(tableName) }")
val allTraits = s"${container}Root" :: tableTraits.toList
val mixinCode = allTraits.mkString("extends ", " with ", "")
s"""
package ${pkg}
// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object ${container} extends ${container} {
val profile: slick.jdbc.JdbcProfile = $profile
}
/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.)
Each generated XXXXTable trait is mixed in this trait hence allowing access to all the TableQuery lazy vals.
*/
trait ${container} ${mixinCode} {
val profile: slick.jdbc.JdbcProfile
import profile.api._
${indent(codeForContainer)}
}
""".trim()
}
/**
* Generates code for the given table. The tableName and tableCode parameters should come from the #codePerTable map.
* @group Basic customization overrides
* @param tableName : the name of the table
* @param tableCode : the generated code for the table.
* @param pkg Scala package the generated code is placed in
* @param container The name of the container
*/
def packageTableCode(tableName: String, tableCode: String, pkg: String, container: String): String = {
val foreignKeys = foreignKeysPerTable.getOrElse(tableName, Nil).map(handleQuotedNamed)
val selfTraits = s"${container}Root" :: foreignKeys
s"""
package ${pkg}
// AUTO-GENERATED Slick data model for table ${tableName}
trait ${handleQuotedNamed(tableName) } {
self:${selfTraits.mkString(" with ")} =>
import profile.api._
${indent(tableCode)}
}
""".trim()
}
}