forked from mpeltonen/sbt-idea
/
IdeaProjectDescriptor.scala
196 lines (168 loc) · 6.97 KB
/
IdeaProjectDescriptor.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
185
186
187
188
189
190
191
192
193
194
195
196
package org.sbtidea
/**
* Copyright (C) 2010, Mikko Peltonen, Ismael Juma, Jon-Anders Teigen, Jason Zaugg
* Licensed under the new BSD License.
* See the LICENSE file for details.
*/
import sbt._
import xml.transform.{RewriteRule, RuleTransformer}
import java.io.{FileOutputStream, File}
import java.nio.channels.Channels
import util.control.Exception._
import xml.{Text, Elem, XML, Node, Unparsed}
object OutputUtil {
def saveFile(dir: File, filename: String, node: xml.Node) { saveFile(new File(dir, filename), node) }
def saveFile(file: File, node: xml.Node) {
val prettyPrint = new scala.xml.PrettyPrinter(150, 2)
val fos = new FileOutputStream(file)
val w = Channels.newWriter(fos.getChannel(), XML.encoding)
ultimately(w.close())(
w.write(prettyPrint.format(node))
)
}
}
class IdeaProjectDescriptor(val projectInfo: IdeaProjectInfo, val env: IdeaProjectEnvironment, val log: Logger) {
def projectRelative(file: File) = IOUtils.relativePath(projectInfo.baseDir, file, "$PROJECT_DIR$/")
val vcsName = List("svn", "Git").foldLeft("") { (res, vcs) =>
if (new File(projectInfo.baseDir, "." + vcs.toLowerCase).exists) vcs else res
}
private def moduleEntry(pathPrefix: String, moduleName: String, groupName: Option[String]) =
<module fileurl={String.format("file://$PROJECT_DIR$%s/%s.iml", pathPrefix, moduleName)}
filepath={String.format("$PROJECT_DIR$%s/%s.iml", pathPrefix, moduleName)}
group={groupName map(xml.Text(_))} />
private def projectModuleManagerComponent: xml.Node =
<component name="ProjectModuleManager">
<modules>
{
if (env.includeSbtProjectDefinitionModule) {
for {
moduleInfo <- projectInfo.childProjects if new File(moduleInfo.baseDir, "project").exists
} yield {
moduleEntry("/" + env.modulePath, moduleInfo.name + "-build", None)
}
}
}
{
for {
moduleInfo <- projectInfo.childProjects
} yield {
moduleEntry("/" + env.modulePath, moduleInfo.name, moduleInfo.ideaGroup)
}
}
</modules>
</component>
private def project(inner: xml.Node*): xml.Node = <project version="4">{inner}</project>
private def libraryTableComponent(library: IdeaLibrary): xml.Node = {
def makeUrl(file: File) = {
val path = projectRelative(file)
val formatStr = if (path.endsWith(".jar")) "jar://%s!/" else "file://%s"
<root url={String.format(formatStr, path)}/>
}
<component name="libraryTable">
<library name={library.name}>
<CLASSES>
{ library.classes map makeUrl }
</CLASSES>
<JAVADOC>
{ library.javaDocs map makeUrl }
</JAVADOC>
<SOURCES>
{ library.sources map makeUrl }
</SOURCES>
</library>
</component>
}
private def projectRootManagerComponent: xml.Node =
<component name="ProjectRootManager" version="2" languageLevel={env.javaLanguageLevel} assert-keyword="true" jdk-15="true" project-jdk-name={env.projectJdkName} project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/target/idea_output" />
</component>
private def projectDetailsComponent: xml.Node =
<component name="ProjectDetails">
<option name="projectName" value={projectInfo.name} />
</component>
private def vcsComponent: xml.Node =
<component name="VcsDirectoryMappings">
<mapping directory="" vcs={vcsName} />
</component>
def save() {
import OutputUtil.saveFile
if (projectInfo.baseDir.exists) {
val configDir = new File(projectInfo.baseDir, ".idea")
def configFile(name: String) = new File(configDir, name)
configDir.mkdirs
Seq(
"modules.xml" -> Some(project(projectModuleManagerComponent)),
"misc.xml" -> miscXml(configDir).map(miscTransformer.transform).map(_.head)
) foreach {
case (fileName, Some(xmlNode)) => saveFile(configDir, fileName, xmlNode)
case _ =>
}
Seq(
"vcs.xml" -> Some(project(vcsComponent)),
"projectCodeStyle.xml" -> Some(defaultProjectCodeStyleXml),
"encodings.xml" -> Some(defaultEncodingsXml),
"scala_compiler.xml" -> (if (env.useProjectFsc) Some(scalaCompilerXml) else None),
"highlighting.xml" -> (if (env.enableTypeHighlighting) Some(highlightingXml) else None)
) foreach {
case (fileName, Some(xmlNode)) if (!configFile(fileName).exists) => saveFile(configDir, fileName, xmlNode)
case _ =>
}
val librariesDir = configFile("libraries")
librariesDir.mkdirs
for (ideaLib <- projectInfo.ideaLibs) {
// MUST all be _
val filename = ideaLib.name.replaceAll("[:\\.\\s-]", "_") + ".xml"
saveFile(librariesDir, filename, libraryTableComponent(ideaLib))
}
log.info("Created " + configDir)
} else log.error("Skipping .idea creation for " + projectInfo.baseDir + " since directory does not exist")
}
val scalaCompilerXml = project(
<component name="ScalacSettings">
<option name="COMPILER_LIBRARY_NAME" value={projectInfo.childProjects.headOption.
map(p => SbtIdeaModuleMapping.toIdeaLib(p.scalaInstance).name).getOrElse("")}/>
<option name="COMPILER_LIBRARY_LEVEL" value="Project"/>
</component>
)
val highlightingXml = project(
<component name="HighlightingAdvisor">
<option name="SUGGEST_TYPE_AWARE_HIGHLIGHTING" value="false"/>
<option name="TYPE_AWARE_HIGHLIGHTING_ENABLED" value="true"/>
</component>
)
val defaultProjectCodeStyleXml = project(
<component name="CodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="LINE_SEPARATOR" value={Unparsed(" ")}/>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true"/>
</component>
)
val defaultEncodingsXml = project(
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" defaultCharsetForPropertiesFiles="ISO-8859-1">
<file url="PROJECT" charset="UTF-8"/>
</component>
)
val defaultMiscXml = project(projectRootManagerComponent)
private def miscXml(configDir: File): Option[Node] = try {
Some(XML.loadFile(new File(configDir, "misc.xml")))
} catch {
case e: java.io.FileNotFoundException => Some(defaultMiscXml)
case e: org.xml.sax.SAXParseException => {
log.error("Existing .idea/misc.xml is not well-formed. Reset to default [y/n]?")
val key = System.console.reader.read
if (key == 121 /*y*/ || key == 89 /*Y*/ ) Some(defaultMiscXml) else None
}
}
private object miscTransformer extends RuleTransformer(
new RewriteRule () {
override def transform (n: Node): Seq[Node] = n match {
case e @ Elem(_, "component", _, _, _*) if e \ "@name" == Text("ProjectDetails") => projectDetailsComponent
case e @ Elem(_, "component", _, _, _*) if e \ "@name" == Text("ProjectRootManager") => projectRootManagerComponent
case _ => n
}
}
)
}