Created
December 12, 2010 18:39
-
-
Save kxbmap/738232 to your computer and use it in GitHub Desktop.
sbt-android-plugin(https://github.com/jberkel/android-plugin)のcreate_projectを改造してみた
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
exec scala -deprecation `cygpath -m $0` "$@" | |
!# | |
object CreateProject { | |
import scala.util.control.Exception._ | |
def usage() { | |
println("create_project <name> <pkg> [--scala-version <version>] " + | |
"[--android] " + | |
"[--api-level <level>] [--platform <name>] [--activity <name>] " + | |
"[--idea] " + | |
"[--project-jdk-name <name>] [--project-output-path <path>] " + | |
"[--java-language-level <level>] [--exclude-sbt-project-definition-module] " + | |
"[--exclude-libmanaged-folders] [--compile-with-idea] " + | |
"[--gitignore]") | |
exit(1) | |
} | |
// sbt build.properties settings | |
val sbtVersion = "0.7.4" | |
val projectVersion = "0.1" | |
val defScalaVersion = "2.7.7" | |
val projectInitialize = false | |
// android | |
val androidPlatformPrefix = "android-" | |
val defaultApiLevel = 7 | |
case class ProjectConfig( | |
name: String, | |
target: String, | |
pkg: String, | |
scalaVersion: String = """\d\.\d\.\d""".r.findFirstIn( | |
scala.util.Properties.versionString) getOrElse defScalaVersion) | |
case class AndroidConfig( | |
apiLevel: Int = defaultApiLevel, | |
platform: String = androidPlatformPrefix + defaultApiLevel, | |
activity: String = "MainActivity") | |
case class IdeaConfig( | |
projectJdkName:String = "1.6", | |
projectOutputPath:Option[String] = None, | |
javaLanguageLevel: String = "JDK_1_6", | |
includeSbtProjectDefinitionModule: Boolean = true, | |
excludeLibManagedFolders: Boolean = false, | |
compileWithIdea: Boolean = false) | |
case class GitConfig(gitIgnore: Boolean = true) | |
case class Config( | |
project: ProjectConfig, | |
android: Option[AndroidConfig] = None, | |
idea: Option[IdeaConfig] = None, | |
git: Option[GitConfig] = None) | |
def config(i: Iterator[String]) = { | |
@annotation.tailrec | |
def cfg(i: Iterator[String], c: Config = null): Option[Config] = | |
if (c == null) { | |
if (!i.hasNext) None else { | |
val name = i.next() | |
if ((name matches """[a-zA-Z]\w+""" tap { b => if(!b) println("invalid project name: "+name) }) && i.hasNext){ | |
val pkg = i.next() | |
if (pkg matches """[a-z]+\.([a-z]+\.?)+[a-z]$""") cfg(i, Config(ProjectConfig(name, name, pkg))) | |
else None tap { _ => println("invalid package name, need 2 package components (e.g. com.bar)") } | |
} else None | |
} | |
} else { | |
if (!i.hasNext) Some(c) else { | |
def setAndroid(c: Config)(f: AndroidConfig => AndroidConfig):Config = | |
c.copy(android = Some(f(c.android getOrElse AndroidConfig()))) | |
def setIdea(c: Config)(f: IdeaConfig => IdeaConfig): Config = | |
c.copy(idea = Some(f(c.idea getOrElse IdeaConfig()))) | |
def setGit(c: Config)(f: GitConfig => GitConfig): Config = | |
c.copy(git = Some(f(c.git getOrElse GitConfig()))) | |
i.next() match { | |
case "--scala-version" => | |
if (!i.hasNext) None else cfg(i, c.copy(project = c.project.copy(scalaVersion = i.next()))) | |
case "--android" => | |
cfg(i, setAndroid(c){ ac => ac }) | |
case "--api-level" => | |
if (!i.hasNext) None else { | |
catching(classOf[NumberFormatException]) opt i.next().toInt match { | |
case Some(level) => cfg(i, setAndroid(c) { | |
_.copy(apiLevel = level, platform = androidPlatformPrefix + level) }) | |
case None => None | |
} | |
} | |
case "--platform" => | |
if (!i.hasNext) None else cfg(i, setAndroid(c) { _.copy(platform = i.next()) }) | |
case "--activity" => | |
if (!i.hasNext) None else cfg(i, setAndroid(c) { _.copy(activity = i.next()) }) | |
case "--idea" => | |
cfg(i, setIdea(c){ ic => ic }) | |
case "--project-jdk-name" => | |
if (!i.hasNext) None else cfg(i, setIdea(c) { _.copy(projectJdkName = i.next()) }) | |
case "--project-output-path" => | |
if (!i.hasNext) None else { | |
val path = i.next() | |
// TODO check path | |
cfg(i, setIdea(c) { _.copy(projectOutputPath = Some(path)) }) | |
} | |
case "--java-language-level" => | |
if (!i.hasNext) None else cfg(i, setIdea(c) { _.copy(javaLanguageLevel = i.next()) }) | |
case "--exclude-sbt-project-definition-module" => | |
cfg(i, setIdea(c) { _.copy(includeSbtProjectDefinitionModule = false) }) | |
case "--exclude-libmanaged-folders" => | |
cfg(i, setIdea(c) { _.copy(excludeLibManagedFolders = true) }) | |
case "--compile-with-idea" => | |
cfg(i, setIdea(c) { _.copy(compileWithIdea = true) }) | |
case "--gitignore" => | |
cfg(i, setGit(c) { _.copy(gitIgnore = true) }) | |
case _ => None | |
} | |
} | |
} | |
cfg(i) | |
} | |
def generate(config: Config) { | |
import scala.collection.mutable.ListBuffer | |
import config.{project => pc, android => aco, idea => ico, git => gco} | |
val withAndroid = aco match { | |
case Some(_) => true | |
case None => false | |
} | |
val withIdea = ico match { | |
case Some(_) => true | |
case None => false | |
} | |
val buildProperties = """ | |
|project.name=%s | |
|sbt.version=%s | |
|project.version=%s | |
|def.scala.version=%s | |
|build.scala.versions=%s | |
|project.initialize=%b | |
|""".stripMargin.trim format (pc.name, sbtVersion, projectVersion, defScalaVersion, | |
pc.scalaVersion, projectInitialize) // tap { println("---------"); println } | |
val pluginDef = (""" | |
|import sbt._ | |
| | |
|class Plugins(info: ProjectInfo) extends PluginDefinition(info) {""" + | |
(if (withAndroid) """ | |
| val android = "org.scala-tools.sbt" % "sbt-android-plugin" % "0.5.0" | |
|""" else "") + | |
(if (withIdea) """ | |
| val sbtIdeaRepo = "sbt-idea-repo" at "http://mpeltonen.github.com/maven/" | |
| val sbtIdea = "com.github.mpeltonen" % "sbt-idea-plugin" % "0.1.0" | |
|""" else "") + "}").stripMargin.trim // tap { println("---------"); println } | |
val parentMix = { | |
val buf = new ListBuffer[String] | |
buf += "ParentProject(info)" | |
if (withIdea) buf += "IdeaProject" | |
buf.toList | |
} mkString ("extends ", " with ", "") | |
val mainMix = { | |
val buf = new ListBuffer[String] | |
buf += (if (withAndroid) "AndroidProject(info)" else "DefaultProject(info)") | |
if (withAndroid) { | |
buf += "Defaults" | |
buf += "MarketPublish" | |
buf += "TypedResources" | |
} | |
if (withIdea) buf += "IdeaProject" | |
buf.toList | |
} mkString ("extends ", " with ", "") | |
val testMix = { | |
val buf = new ListBuffer[String] | |
buf += (if (withAndroid) "AndroidTestProject(info)" else "DefaultProject(info)") | |
if (withAndroid) buf += "Defaults" | |
if (withIdea) buf += "IdeaProject" | |
buf.toList | |
} mkString ("extends ", " with ", "") | |
val projectDef = (""" | |
|import sbt._ | |
|""" + (if (withAndroid) (""" | |
|trait Defaults extends AndroidProject { | |
| def androidPlatformName = "%s" | |
| def androidPlatformToolsPath = androidSdkPath / "platform-tools" | |
| override def adbPath = androidPlatformToolsPath / adbName | |
|} | |
|""" format aco.get.platform) else "") + (""" | |
|class %s(info: ProjectInfo) %s { | |
| override def shouldCheckOutputDirectories = false | |
| override def updateAction = task { None } | |
| | |
| lazy val main = project(".", "%s", new MainProject(_)) | |
| lazy val tests = project("tests", "tests", new TestProject(_), main) | |
|""" format (pc.name.capitalize, parentMix, pc.name)) + (""" | |
| class MainProject(info: ProjectInfo) %s {""" format mainMix) + (if (withAndroid) (""" | |
| val keyalias = "change-me" | |
|""") else "") + (""" | |
| val scalatest = "org.scalatest" % "scalatest" % "1.0" % "test" | |
| } | |
|""") + (""" | |
| class TestProject(info: ProjectInfo) %s | |
|} | |
|""" format testMix)).stripMargin.trim // tap { println("---------"); println } | |
val specDef = """ | |
|import %s | |
|import org.scalatest.matchers.ShouldMatchers | |
|import org.scalatest.Spec | |
| | |
|class Specs extends Spec with ShouldMatchers { | |
| describe("a spec") { | |
| it("should do something") { | |
| } | |
| } | |
|}""".stripMargin.trim format pc.pkg // tap { println("---------"); println } | |
class AndroidDef(ac: AndroidConfig) { | |
val manifestXml = | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package={pc.pkg} | |
android:versionCode="1" | |
android:versionName="0.1"> | |
<uses-sdk android:minSdkVersion={ac.apiLevel.toString}/> | |
<application android:label="@string/app_name" android:icon="@drawable/app_icon"> | |
<activity android:name={"."+ac.activity} android:label="@string/app_name"> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN"/> | |
<category android:name="android.intent.category.LAUNCHER"/> | |
</intent-filter> | |
</activity> | |
</application> | |
</manifest> | |
val testManifestXml = | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
package={pc.pkg + ".tests"}> | |
<uses-sdk android:minSdkVersion={ac.apiLevel.toString}/> | |
<application> | |
<uses-library android:name="android.test.runner"/> | |
</application> | |
<instrumentation android:name="android.test.InstrumentationTestRunner" | |
android:targetPackage={pc.pkg} | |
android:label="Tests"/> | |
</manifest> | |
val activityDef = (""" | |
|package %s | |
| | |
|import _root_.android.app.Activity | |
|import _root_.android.os.Bundle | |
|import _root_.android.widget.TextView | |
| | |
|class %s extends Activity """ + (if (withAndroid) "with TypedActivity" else "") + """{ | |
| override def onCreate(savedInstanceState: Bundle) { | |
| super.onCreate(savedInstanceState) | |
| setContentView(new TextView(this) { | |
| setText("hello, world") | |
| }) | |
| } | |
|}""").stripMargin.trim format (pc.pkg, ac.activity) // tap { println("---------"); println } | |
val testDef = """ | |
|package %s.tests | |
| | |
|import junit.framework.Assert._ | |
|import _root_.android.test.AndroidTestCase | |
| | |
|class UnitTests extends AndroidTestCase { | |
| def testPackageIsCorrect { | |
| assertEquals("%s", getContext.getPackageName) | |
| } | |
|}""".stripMargin.trim format (pc.pkg, pc.pkg) // tap { println("---------"); println } | |
} | |
val androidDef = for (ac <- aco) yield new AndroidDef(ac) | |
class IdeaDef(ic: IdeaConfig) { | |
val ideaPropertiesDef = ((""" | |
|project.jdk.name=%s""" format ic.projectJdkName) + | |
(ic.projectOutputPath match { | |
case Some(outputPath) => """ | |
|project.output.path=%s""" format outputPath | |
case None => "" | |
}) + """ | |
|java.language.level=%s | |
|include.sbt.project.definition.module=%b | |
|exclude.libmanaged.folders=%b | |
|compile.with.idea=%b | |
|""" format ( | |
ic.javaLanguageLevel, | |
ic.includeSbtProjectDefinitionModule, | |
ic.excludeLibManagedFolders, | |
ic.compileWithIdea) | |
).stripMargin.trim // tap { println("---------"); println } | |
} | |
val ideaDef = for (ic <- ico) yield new IdeaDef(ic) | |
class GitDef(gc: GitConfig) { | |
val gitIgnoreDef = (""" | |
|/project/boot/ | |
|target/ | |
|lib_managed/ | |
|src_managed/ | |
|test-output/""" + (if (withIdea) """ | |
|/.idea/ | |
|*.iml | |
|""" else """" | |
|""")).stripMargin.trim // tap { println("---------"); println } | |
} | |
val gitDef = for (gc <- gco) yield new GitDef(gc) | |
class Generator { | |
import java.io.File | |
import scala.xml.Node | |
val Project = new File(pc.target, "project/build/" + pc.name.capitalize + ".scala") | |
val Plugins = new File(pc.target, "project/plugins/Plugins.scala") | |
val BuildProperties = new File(pc.target, "project/build.properties") | |
val Spec = new File(pc.target, "src/test/scala/Specs.scala") | |
// android | |
val Resources = List("drawable", "layout", "values", "xml") | |
val Manifest = new File(pc.target, "src/main/AndroidManifest.xml") | |
val TestManifest = new File(pc.target, "tests/src/main/AndroidManifest.xml") | |
val Strings = new File(pc.target, "src/main/res/values/strings.xml") | |
val Test = new File(pc.target, "tests/src/main/scala/UnitTests.scala") | |
val Activity = new File(pc.target, "src/main/scala/Activity.scala") | |
val AppIcon = new File(pc.target, "src/main/res/drawable/app_icon.png") | |
// idea | |
val IdeaProperties = new File(pc.target, "project/idea.properties") | |
// git | |
val GitIgnore = new File(pc.target, ".gitignore") | |
val dirs2create = { | |
val dirs = List( | |
"src" :: "main" :: "scala" :: Nil, | |
"src" :: "test" :: "scala" :: Nil, | |
"project" :: "plugins" :: Nil, | |
"project" :: "build" :: Nil | |
) | |
if (!withAndroid) dirs | |
else dirs ++ List( | |
"src" :: "main" :: "java" :: Nil, | |
"src" :: "main" :: "assets" :: Nil | |
) ++ Resources.map("src" :: "main" :: "res" :: _ :: Nil) | |
} // tap { println("-- dirs2create ----"); _ foreach println } | |
val testdirs2create = { | |
val dirs = List( | |
"tests" :: "src" :: "main" :: "scala" :: Nil | |
) | |
if (!withAndroid) dirs | |
else dirs ++ List( | |
"tests" :: "src" :: "main" :: "res" :: Nil, | |
"tests" :: "src" :: "main" :: "assets" :: Nil | |
) | |
} // tap { println("-- testdirs2create ----"); _ foreach println } | |
def resources(m: Map[String,String]) = { | |
val strings = for ((name,value) <- m) yield { <string name={name}>{value}</string> } | |
<resources>{strings}</resources> | |
} | |
def fail(s: String) = throw new RuntimeException(s) | |
def pretty(n: Node) = new scala.xml.PrettyPrinter(100, 4).format(n) | |
def writeFile(f: File, s: String) { | |
val w = new java.io.FileWriter(f) | |
try { | |
w.write(s) | |
} finally { | |
w.close() | |
} | |
} | |
def createDirs() { | |
if (new File(pc.target).exists) fail("target directory " + pc.target + " already exists") | |
implicit def stringSeq2File(s: Seq[String]):File = s.foldLeft[File](new File(".")) { (f,p) => new File(f, p) } | |
def mkdir(f: File) = if (!f.mkdirs()) fail("could not create directory "+f.getPath) | |
(dirs2create ++ testdirs2create) foreach { d=> mkdir(pc.target :: d) } | |
} | |
def createIcon() { createIcon(AppIcon, 48, 48) } | |
def createIcon(file: File, width: Int, height:Int) { | |
val bi = new java.awt.image.BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB) | |
val g = bi.createGraphics() | |
val gradient = new java.awt.GradientPaint(0, 0, java.awt.Color.red, width, height, java.awt.Color.blue) | |
g.setPaint(gradient) | |
g.fill(new java.awt.geom.RoundRectangle2D.Double(0, 0, width, height, 10, 10)) | |
//g.setPaint(java.awt.Color.black) | |
//g.drawString(config.project, 10, height / 2) | |
javax.imageio.ImageIO.write(bi, "PNG", file) | |
} | |
def createManifests(manifestXml: Node, testManifestXml:Node) { | |
writeFile(Manifest, pretty(manifestXml)) | |
writeFile(TestManifest, pretty(testManifestXml)) | |
} | |
def createProject(pluginDef:String, projectDef:String, buildProperties:String) { | |
writeFile(Plugins, pluginDef) | |
writeFile(Project, projectDef) | |
writeFile(BuildProperties, buildProperties) | |
} | |
def createActivity(activityDef: String) { writeFile(Activity, activityDef) } | |
def createResources() { writeFile(Strings, pretty(resources(Map("app_name" -> pc.name.capitalize)))) } | |
def createSpecs(specDef: String) { writeFile(Spec, specDef) } | |
def createTests(testDef: String) { writeFile(Test, testDef) } | |
def createIdeaProperties(ideaProperties: String) { writeFile(IdeaProperties, ideaProperties) } | |
def createGitIgnore(gitIgnoreDef: String) { writeFile(GitIgnore, gitIgnoreDef) } | |
} | |
val gen = new Generator | |
gen.createDirs() | |
gen.createProject(pluginDef, projectDef, buildProperties) | |
gen.createSpecs(specDef) | |
for (ad <- androidDef) { | |
gen.createManifests(ad.manifestXml, ad.testManifestXml) | |
gen.createActivity(ad.activityDef) | |
gen.createTests(ad.testDef) | |
gen.createIcon() | |
gen.createResources() | |
} | |
for (id <- ideaDef) { | |
gen.createIdeaProperties(id.ideaPropertiesDef) | |
} | |
for (gd <- gitDef) { | |
gen.createGitIgnore(gd.gitIgnoreDef) | |
} | |
} | |
implicit def any2Tap[A](toTap: A): { def tap(f: (A) => Unit): A } = new { | |
def tap(f: (A) => Unit): A = { | |
f(toTap) | |
toTap | |
} | |
} | |
def run(args: Array[String]) { | |
config(args.iterator) /* tap { println } */ match { | |
case Some(c) => | |
catching(classOf[RuntimeException], classOf[java.io.IOException]) either generate(c) match { | |
case Left(e) => println(e.getMessage) | |
case _ => println("generated project in directory '"+c.project.target+"'") | |
} | |
case None => usage() | |
} | |
} | |
def main(args: Array[String]) { run(args) } | |
} | |
CreateProject.run(args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment