Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Rework plugin structure to replace pluggable with a xml file #91

Merged
merged 11 commits into from
Jul 21, 2019
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pluginTargetFolderNames := List("plugins", s"target/scala-$scalaMajorVersion/plu
apiProjectPath := "api"
guiProjectPath := "gui"

create := BuildUtility(streams.value.log).createPluginTask(pluginFolderNames.value)
create := PluginCreateWizard(streams.value.log).createPluginTask(pluginFolderNames.value)
fetch := BuildUtility(streams.value.log).fetchPluginsTask(pluginFolderNames.value, pluginBuildFileName.value,
pluginTargetFolderNames.value, apiProjectPath.value)
copy := BuildUtility(streams.value.log).copyPluginsTask(pluginFolderNames.value, pluginTargetFolderNames.value, scalaMajorVersion)
Expand Down
103 changes: 6 additions & 97 deletions project/BuildUtility.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,86 +131,6 @@ class BuildUtility(logger: ManagedLogger) {
logger info s"Successfully copied $successCounter / ${allJarFiles.length} plugins to target '${pluginTargetFolder.getPath}'!"
}

/**
* Creates a new plugin. Interactive command using the console.
*
* @param pluginFolderNames All folder names, containing plugin source code. Defined in build.sbt.
*/
def createPluginTask(pluginFolderNames: List[String]): Unit = {
withTaskInfo("CREATE PLUGIN") {

// Plugin folders have to be defined in the build.sbt file first
if (pluginFolderNames.isEmpty) {
println("Before creating a new plugin, please define at least one plugin source folder in the build.sbt file.")
logger warn "Aborting task without plugin creation."

} else {
println("Welcome to the \"create plugin\"-wizard. Please specify name, version and plugin source folder.")

// Plugin name
val name = BuildUtility.askForInput(
"Please specify the name of the plugin. Do only use characters allowed for directories and files of your OS.",
"Plugin name",
repeatIfEmpty = true
)

// Plugin version (default: 0.1)
var version = BuildUtility.askForInput(
"Please specify the version of the plugin. Just press enter for version \"0.1\".",
"Plugin version",
repeatIfEmpty = false
)
if (version == "") version = "0.1"

// Plugin folder name (must be defined in build.sbt)
var pluginFolderName = ""
while (!pluginFolderNames.contains(pluginFolderName)) {
pluginFolderName = BuildUtility.askForInput(
s"Please specify the plugin source directory. Available directories: ${pluginFolderNames.mkString("[", ", ", "]")}",
"Plugin source directory",
repeatIfEmpty = true
)
}

createPlugin(name, version, pluginFolderName)
}
}
}

private def withTaskInfo(taskName: String)(task: Unit): Unit = BuildUtility.withTaskInfo(taskName, logger)(task)

private def createPlugin(name: String, version: String, pluginFolderName: String): Unit = {
logger info s"Trying to create plugin $name (version $version) at plugin folder $pluginFolderName."

val pluginFolder = new File(pluginFolderName)
if (!pluginFolder.exists()) {
logger error "Plugin source folder does not exist. Aborting task without plugin creation."

} else {

val plugin = new Plugin(pluginFolderName, name)

if (!plugin.createPluginFolder()) {
logger error "Plugin does already exist. Aborting task without plugin creation."
} else {
logger info s"Created plugin '$name'"

if (plugin.createSrcFolder()) {
logger info "Successfully created source folder."
} else {
logger warn "Unable to create source folder."
}

if (plugin.createSbtFile(version)) {
logger info "Successfully created plugins sbt file."
} else {
logger warn "Unable to create plugins sbt file."
}

}
}
}

def guiTask(guiProjectPath: String, cacheDir: File): Unit = {
withTaskInfo("BUILD GUI") {
val guiDir = new File(guiProjectPath)
Expand All @@ -234,13 +154,13 @@ class BuildUtility(logger: ManagedLogger) {
/**
* Download the dependencies of the gui using npm.
*
* @param guiDir the directory of the gui.
* @param guiDir the directory of the gui.
* @param cacheDir a dir, where sbt can store files for caching in the "install" sub-dir.
* @return None, if a error occurs which will be displayed, otherwise the output directory with the built gui.
*/
private def installGuiDeps(guiDir: File, cacheDir: File): Option[File] = {
// Check buildGui for a explanation, it's almost the same.

val install = FileFunction.cached(new File(cacheDir, "install"), FilesInfo.hash)(_ => {

logger info "Installing GUI dependencies."
Expand All @@ -266,8 +186,8 @@ class BuildUtility(logger: ManagedLogger) {

/**
* Builds the gui using npm.
*
* @param guiDir the directory of the gui.
*
* @param guiDir the directory of the gui.
* @param cacheDir a dir, where sbt can store files for caching in the "build" sub-dir.
* @return None, if a error occurs which will be displayed, otherwise the output directory with the built gui.
*/
Expand Down Expand Up @@ -326,25 +246,14 @@ class BuildUtility(logger: ManagedLogger) {
Set(f)
}
}

private def withTaskInfo(taskName: String)(task: Unit): Unit = BuildUtility.withTaskInfo(taskName, logger)(task)
}

object BuildUtility {

def apply(logger: ManagedLogger): BuildUtility = new BuildUtility(logger)

private def askForInput(information: String, description: String, repeatIfEmpty: Boolean): String = {
println(information)
print(s"$description > ")

var input = scala.io.Source.fromInputStream(System.in).bufferedReader().readLine()
println("")

if (input == "" && repeatIfEmpty)
input = askForInput(information, description, repeatIfEmpty)

input
}

/**
* This method can be used to create better readable sbt console output by declaring start and stop of a custom task.
*
Expand Down
78 changes: 59 additions & 19 deletions project/Plugin.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import java.io.File

import sbt.io.IO

import scala.util.Try
import scala.xml.PrettyPrinter

/**
* A plugin represents a directory in a plugin source directory. Every plugin has its own build file and source folder.
*
Expand All @@ -26,22 +31,6 @@ class Plugin(val pluginSourceDirectoryName: String, val name: String) {
}
}

/**
* Creates the plugin src folder inside of a plugin folder
*
* @note Make sure to create the plugin folder first!
* @return true, if the process was successful
*/
def createSrcFolder(): Boolean = {
if (new File(s"$pluginDirectoryPath/src").mkdir() &&
new File(s"$pluginDirectoryPath/src/main").mkdir() &&
new File(s"$pluginDirectoryPath/src/main/scala").mkdir()) {
true
} else {
false
}
}

/**
* Creates a simple sbt file with name and version info into the plugin folder
*
Expand All @@ -52,7 +41,53 @@ class Plugin(val pluginSourceDirectoryName: String, val name: String) {
val sbtFile = new SbtFile(name, version)

// The name of the sbt file is the plugin name. This worked in first tests
sbtFile.save(s"$pluginDirectoryPath/$name.sbt")
sbtFile.save(s"$pluginDirectoryPath/$normalizedName.sbt")
}

/**
* Generates the plugin.xml file in the resources of the plugin.
*
* @param metadata the metadata for this plugin
* @param author author of this plugin, used by the framework to identify it
*/
def createPluginXMLFile(metadata: PluginMetadata, author: String, version: String, apiVersion: (Int, Int)): Boolean = {
val xml = <plugin>
<name>
{name}
</name>
<author>
{author}
</author>
<version>
{version}
</version>
<api>
<major>{apiVersion._1}</major>
<minor>{apiVersion._2}</minor>
</api>{metadata.toXML}
</plugin>

val trimmed = scala.xml.Utility.trim(xml)
val prettyXml = new PrettyPrinter(100, 2).format(trimmed)

Try(
IO.write(new File(s"$pluginDirectoryPath/src/main/resources/plugin.xml"), prettyXml)
).isSuccess
}

/**
* Generates the main class file implementing PluginImpl for the plugin developer in their choosen language.
*
* @param language the language in with the source file will be generated
* @return true, if everything was successful
*/
def createSourceFile(language: PluginLanguage.Value): Boolean = {
val content = PluginLanguage.getSourceFileContent(normalizedName, language)
val langName = language.toString.toLowerCase

Try(
IO.write(new File(s"$pluginDirectoryPath/src/main/$langName/${normalizedName}Plugin.$langName"), content.getBytes)
).isSuccess
}

/**
Expand Down Expand Up @@ -94,7 +129,9 @@ object Plugin {
*/
def getPlugins(pluginSourceFolderName: String): Seq[Plugin] = {
val pluginSourceFolder = new File(pluginSourceFolderName)
pluginSourceFolder.listFiles.filter(_.isDirectory).filter(d => d.getName != ".git" && d.getName != ".github")
pluginSourceFolder.listFiles
.filter(_.isDirectory)
.filter(d => containsPluginXMLFile(d))
.map(folder => new Plugin(pluginSourceFolderName, folder.getName))

}
Expand All @@ -110,8 +147,11 @@ object Plugin {
pluginSourceFolder.exists() && pluginSourceFolder.isDirectory
}

private def toPluginPathName(name: String) = name.replace(" ", "").toLowerCase
private def toPluginPathName(name: String) = name.replaceAll("[ -]", "").toLowerCase

private def containsPluginXMLFile(directory: File): Boolean = {
new File(s"$directory/src/main/resources/plugin.xml").exists()
}
}


Loading