├── .gitignore
├── resources
├── fileTemplates
│ ├── Template build.gradle.ft
│ ├── Template Application.java.ft
│ ├── Template AucApiClass.java.ft
│ ├── Template Manifest.xml.ft
│ ├── Template AucApiImplClass.java.ft
│ └── Template MainActivity.java.ft
└── META-INF
│ ├── pluginIcon.svg
│ └── plugin.xml
├── screenshot
├── main.png
├── preview.png
└── settings.png
├── test
├── AucTemplateTest.kt
└── FileTreeDslTest.kt
├── src
└── com
│ └── dengzii
│ └── plugin
│ └── template
│ ├── ui
│ ├── PreviewPanel.form
│ ├── EditToolbar.kt
│ ├── CreateFileDialog.form
│ ├── EditableTable.kt
│ ├── CreateFileDialog.java
│ ├── CreateModuleDialog.java
│ ├── ConfigurePanel.form
│ ├── PreviewPanel.java
│ ├── CreateModuleDialog.form
│ └── ConfigurePanel.java
│ ├── utils
│ ├── PopMenuUtils.kt
│ ├── Logger.kt
│ └── PluginKit.kt
│ ├── CreateModuleAction.kt
│ ├── template
│ ├── FileTemplateFactory.kt
│ ├── Template.kt
│ └── AucTemplate.kt
│ ├── TemplateConfigurable.kt
│ ├── model
│ ├── Module.kt
│ ├── FileTreeDsl.kt
│ └── FileTreeNode.kt
│ ├── Config.kt
│ └── FileWriteCommand.kt
├── README-ZH.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /.out
3 | /*.iml
--------------------------------------------------------------------------------
/resources/fileTemplates/Template build.gradle.ft:
--------------------------------------------------------------------------------
1 | dependencies {
2 |
3 | }
--------------------------------------------------------------------------------
/screenshot/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/main.png
--------------------------------------------------------------------------------
/screenshot/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/preview.png
--------------------------------------------------------------------------------
/screenshot/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bayshier/GenerateModuleFromTemplate/master/screenshot/settings.png
--------------------------------------------------------------------------------
/resources/fileTemplates/Template Application.java.ft:
--------------------------------------------------------------------------------
1 | package ${PACKAGE_NAME};
2 |
3 | #parse("File Header.java")
4 | public class ${APPLICATION_NAME}App extends BaseApplication{
5 |
6 | public void onCreate(){
7 |
8 | }
9 | }
--------------------------------------------------------------------------------
/resources/fileTemplates/Template AucApiClass.java.ft:
--------------------------------------------------------------------------------
1 | package ${PACKAGE_NAME};
2 |
3 | import com.blankj.utilcode.util.ApiUtils;
4 |
5 | #parse("File Header.java")
6 | public abstract class ${FEATURE_NAME}Api extends ApiUtils.BaseApi {
7 |
8 | }
--------------------------------------------------------------------------------
/resources/fileTemplates/Template Manifest.xml.ft:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/resources/fileTemplates/Template AucApiImplClass.java.ft:
--------------------------------------------------------------------------------
1 | package ${PACKAGE_NAME};
2 |
3 | import ${PACKAGE_NAME}.export.${FEATURE_NAME}Api;
4 | import com.blankj.utilcode.util.ApiUtils;
5 |
6 | #parse("File Header.java")
7 | @ApiUtils.Api
8 | public class Feature1ApiImpl extends ${FEATURE_NAME}Api {
9 |
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/resources/fileTemplates/Template MainActivity.java.ft:
--------------------------------------------------------------------------------
1 | package ${PACKAGE_NAME};
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 | import androidx.annotation.Nullable;
5 | import android.os.Bundle;
6 |
7 | #parse("File Header.java")
8 | public class MainActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(@Nullable Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 |
14 | }
15 | }
--------------------------------------------------------------------------------
/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/AucTemplateTest.kt:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import com.dengzii.plugin.template.template.AucTemplate
4 | import org.junit.Test
5 |
6 | class AucTemplateTest {
7 |
8 | @Test
9 | fun aucAppModuleTest() {
10 | val app = AucTemplate.APP
11 | app {
12 | placeholder("PACKAGE_NAME", "com.dengzii.plugin")
13 | }
14 | println(app.placeholders)
15 | println(app.getAllPlaceholderInTree())
16 |
17 | app.build()
18 | println(app)
19 | println(app.getTreeGraph())
20 | }
21 |
22 | @Test
23 | fun aucModuleTemplateTest() {
24 | val module = AucTemplate.MODULE {
25 | placeholder("FEATURE_NAME","plugin")
26 | placeholder("PACKAGE_NAME","com.dengzi")
27 | }
28 | module.build()
29 | println(module.getTreeGraph())
30 | }
31 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/PreviewPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/utils/PopMenuUtils.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.utils
2 |
3 | import com.intellij.openapi.ui.JBMenuItem
4 | import com.intellij.openapi.ui.JBPopupMenu
5 |
6 | /**
7 | *
8 | * author : dengzi
9 | * e-mail : denua@foxmail.com
10 | * github : https://github.com/dengzii
11 | * time : 2020/1/16
12 | * desc :
13 | *
14 | */
15 | object PopMenuUtils {
16 |
17 | fun create(menu: Map): JBPopupMenu {
18 |
19 | val popMenu = JBPopupMenu()
20 | menu.forEach { (k, v) ->
21 | if (k.isBlank()) {
22 | popMenu.addSeparator()
23 | return@forEach
24 | }
25 | val item = JBMenuItem(k)
26 | item.addActionListener {
27 | v?.onClick()
28 | }
29 | popMenu.add(item)
30 | }
31 | return popMenu
32 | }
33 |
34 | interface PopMenuListener {
35 | fun onClick()
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/CreateModuleAction.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template
2 |
3 | import com.dengzii.plugin.template.ui.CreateModuleDialog
4 | import com.dengzii.plugin.template.utils.Logger
5 | import com.dengzii.plugin.template.utils.PluginKit
6 | import com.intellij.openapi.actionSystem.AnAction
7 | import com.intellij.openapi.actionSystem.AnActionEvent
8 | import com.intellij.openapi.project.Project
9 |
10 | /**
11 | *
12 | * author : dengzi
13 | * e-mail : denua@foxmail.com
14 | * github : https://github.com/dengzii
15 | * time : 2019/12/31
16 | * desc :
17 |
*
18 | */
19 | class CreateModuleAction : AnAction() {
20 |
21 | companion object {
22 | var project: Project? = null
23 | }
24 |
25 | override fun actionPerformed(e: AnActionEvent) {
26 | val kit = PluginKit.get(e)
27 | if (!kit.isProjectValid()) {
28 | Logger.d(CreateModuleAction::class.java.simpleName, "Project is not valid.")
29 | return
30 | }
31 | project = kit.project
32 | CreateModuleDialog.createAndShow(kit.project) {
33 | FileWriteCommand.startAction(kit, it)
34 | }
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/utils/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.utils
2 |
3 | import com.jetbrains.rd.util.printlnError
4 | import java.text.SimpleDateFormat
5 |
6 | /**
7 | *
8 | * author : dengzi
9 | * e-mail : denua@foxmail.com
10 | * github : https://github.com/MrDenua
11 | * time : 2019/12/31
12 | * desc :
13 | *
14 | */
15 | object Logger {
16 |
17 | var enable: Boolean = true
18 | var timeFormatter = SimpleDateFormat("MM-dd hh:mm:ss")
19 |
20 | fun e(tag: String, log: String) {
21 | log("e", tag, log)
22 | }
23 |
24 | fun e(tag: String, e: Throwable) {
25 | log("e", tag, e.toString())
26 | e.printStackTrace()
27 | }
28 |
29 | fun i(tag: String, log: String) {
30 | log("i", tag, log)
31 | }
32 |
33 | fun d(tag: String, log: String) {
34 | log("d", tag, log)
35 | }
36 |
37 | fun w(tag: String, log: String) {
38 | log("w", tag, log)
39 | }
40 |
41 | private fun log(level: String, tag: String, log: String) {
42 | if (!enable) {
43 | return
44 | }
45 |
46 | val logStr = "${getTime()} ${level.toUpperCase()}/$tag: $log"
47 | if (level == "e") {
48 | printlnError(logStr)
49 | return
50 | }
51 | println(logStr)
52 | }
53 |
54 | private fun getTime(): String {
55 | return timeFormatter.format(System.currentTimeMillis())
56 | }
57 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/template/FileTemplateFactory.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.template
2 |
3 | import com.intellij.icons.AllIcons
4 | import com.intellij.ide.fileTemplates.FileTemplateDescriptor
5 | import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptor
6 | import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptorFactory
7 | import com.intellij.openapi.fileTypes.StdFileTypes
8 | import javax.swing.Icon
9 |
10 |
11 | /**
12 | *
13 | * author : dengzi
14 | * e-mail : denua@foxmail.com
15 | * github : https://github.com/MrDenua
16 | * time : 2020/1/2
17 | * desc :
18 | *
19 | */
20 | class FileTemplateFactory : FileTemplateGroupDescriptorFactory {
21 |
22 | override fun getFileTemplatesDescriptor(): FileTemplateGroupDescriptor {
23 |
24 | val descriptor = FileTemplateGroupDescriptor("Module Template Plugin Descriptor", AllIcons.Nodes.Plugin)
25 |
26 | descriptor.addTemplate(getDescriptor("MainActivity", StdFileTypes.JAVA.icon))
27 | descriptor.addTemplate(getDescriptor("Manifest.xml", StdFileTypes.XML.icon))
28 | descriptor.addTemplate(getDescriptor("Application.java", StdFileTypes.JAVA.icon))
29 | descriptor.addTemplate(getDescriptor("build.gradle", StdFileTypes.PLAIN_TEXT.icon))
30 |
31 | return descriptor
32 | }
33 |
34 | private fun getDescriptor(templateName: String, icon: Icon?): FileTemplateDescriptor {
35 | return FileTemplateDescriptor(templateName, icon)
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/TemplateConfigurable.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template
2 |
3 | import com.dengzii.plugin.template.ui.ConfigurePanel
4 | import com.intellij.openapi.options.SearchableConfigurable
5 | import org.jetbrains.annotations.Nls
6 | import javax.swing.JComponent
7 |
8 | /**
9 | *
10 | * author : dengzi
11 | * e-mail : denua@foxmail.com
12 | * github : https://github.com/MrDenua
13 | * time : 2020/1/16
14 | * desc :
15 | *
16 | */
17 | class TemplateConfigurable() : SearchableConfigurable {
18 |
19 | private lateinit var panelConfig: ConfigurePanel
20 | private var onApplyListener: OnApplyListener? = null
21 |
22 | constructor(onApplyListener: OnApplyListener) : this() {
23 | this.onApplyListener = onApplyListener
24 | }
25 |
26 | override fun apply() {
27 | panelConfig.cacheConfig()
28 | panelConfig.saveConfig()
29 | onApplyListener?.onApply()
30 | }
31 |
32 | override fun getId(): String {
33 | return "preferences.ModuleTemplateConfig"
34 | }
35 |
36 | override fun createComponent(): JComponent? {
37 | panelConfig = ConfigurePanel()
38 | return panelConfig
39 | }
40 |
41 | override fun isModified(): Boolean {
42 | return true
43 | }
44 |
45 | @Nls(capitalization = Nls.Capitalization.Title)
46 | override fun getDisplayName(): String? {
47 | return "Module Template Settings"
48 | }
49 |
50 | interface OnApplyListener {
51 | fun onApply()
52 | }
53 | }
--------------------------------------------------------------------------------
/README-ZH.md:
--------------------------------------------------------------------------------
1 | # Generate Module From Template
2 |
3 | [ ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template)
4 | [ ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template)
5 |
6 | [README - EN](https://github.com/dengzii/GenerateModuleFromTemplate/blob/master/README.md)
7 |
8 | 使用这个用于 IntelliJ IDEs 的目录模板插件, 帮助你从模板生成任何目录结构
9 |
10 | ### 功能
11 | 1. 自定义目录结构
12 | 2. 目录, 文件名, 文件模板支持配置占位符
13 | 3. 支持文件模板配置
14 | 4. 与小伙伴分享你的模板
15 |
16 | ### 使用
17 | 1. 在设置中配置插件的模板: File > Settings > Tools > Module Template Settings
18 | 2. 在 Structure 中配置目录树, 右键编辑树结构.
19 | 3. 目录树可以使用占位符, 占位符是这样的 -> ${YOUR_PLACEHOLDER_HERE}.
20 | 4. File Template 中可以配置指定文件的模板, 文件名中可以使用占位符, 会自动替换成你创建时配置的.
21 | 5. Placeholder 中列出了你目录树中所有的占位符, 你可以给他们设置默认值.
22 | 6. 插件中使用的模板是在 IDE 本身 Editor>File And Code Templates 中的模板.
23 | 7. 如果插件更新升级了, 则之前配置保存的模板可能会存在不兼容问题.
24 | 8. 你的 star, 是我更新的动力.
25 |
26 | ### 更新日志
27 | - 1.4.0: feature: Support export and import template to file, adjust action button position.
28 | - 1.3.1: fix: AucFrame module template bugs.
29 | - 1.3: fix: Placeholder don't work when call FileTreeNode.include.
30 | - 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template.
31 | - 1.1: feature: support create module template, placeholder, file template
32 | - 1.1: feature: support create module template, placeholder, file template 1.0: basically feature, generate module directories from template
33 | - 1.0: basically feature, generate module directories from template
34 |
35 | ### 截图
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/model/Module.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.model
2 |
3 | import com.dengzii.plugin.template.template.AucTemplate
4 | import com.dengzii.plugin.template.template.Template
5 | import com.dengzii.plugin.template.utils.Logger
6 |
7 | class Module(
8 | var template: FileTreeNode,
9 | var language: String,
10 | var templateName: String
11 | ) {
12 |
13 | companion object {
14 |
15 | fun create(template: FileTreeNode, templateName: String): Module {
16 | Logger.i(Module::class.java.simpleName, "create module. templateName=$templateName")
17 | return Module(template, "java", templateName)
18 | }
19 |
20 | fun getAndroidApplication(): Module {
21 | return create(Template.ANDROID_APP.clone(), "AndroidApp")
22 | }
23 |
24 | fun getEmpty(): Module {
25 | return create(Template.EMPTY.clone(), "EmptyModule")
26 | }
27 |
28 | fun getAucModule(): Module {
29 | return create(AucTemplate.MODULE.clone(), "Auc Module")
30 | }
31 |
32 | fun getAucApp(): Module {
33 | return create(AucTemplate.APP.clone(), "Auc App")
34 | }
35 |
36 | fun getAucPkg(): Module {
37 | return create(AucTemplate.PKG.clone(), "Auc Pkg")
38 | }
39 |
40 | fun getAucExport(): Module {
41 | return create(AucTemplate.EXPORT.clone(), "Auc Export")
42 | }
43 |
44 | fun getAndroidMvp(): Module {
45 | return create(Template.ANDROID_MVP, "Android MVP")
46 | }
47 |
48 | fun getLangList() = Language.values().map { it.name.toLowerCase() }.toTypedArray()
49 | }
50 |
51 | enum class Language {
52 | JAVA;
53 | }
54 |
55 | fun initTemplate(node: FileTreeNode = template) {
56 | node.children.forEach {
57 | it.parent = node
58 | if (it.isDir) {
59 | initTemplate(it)
60 | }
61 | }
62 | }
63 |
64 | fun clone(): Module {
65 | return create(template.clone(), templateName)
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/EditToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui
2 |
3 | import com.intellij.icons.AllIcons
4 | import java.awt.Dimension
5 | import java.awt.FlowLayout
6 | import java.awt.event.MouseAdapter
7 | import javax.swing.Icon
8 | import javax.swing.JButton
9 | import javax.swing.JPanel
10 |
11 | /**
12 | *
13 | * author : dengzi
14 | * e-mail : denua@foxmail.com
15 | * github : https://github.com/MrDenua
16 | * time : 2020/1/14
17 | * desc :
18 | *
19 | */
20 | class EditToolbar : JPanel() {
21 |
22 | private val btAdd = JButton("Add")
23 | private val btRemove = JButton("Remove")
24 | private val btCopy = JButton("Copy")
25 | private val btShare = JButton("Export")
26 |
27 | init {
28 | initLayout()
29 | initButton()
30 | }
31 |
32 | fun onAdd(listener: MouseAdapter) {
33 | btAdd.addMouseListener(listener)
34 | }
35 |
36 | fun onAdd(listener: () -> Unit) {
37 | btAdd.addActionListener {
38 | listener()
39 | }
40 | }
41 |
42 | fun onRemove(listener: () -> Unit) {
43 | btRemove.addActionListener {
44 | listener()
45 | }
46 | }
47 |
48 | fun onCopy(listener: () -> Unit) {
49 | btCopy.addActionListener {
50 | listener()
51 | }
52 | }
53 |
54 | fun onExport(listener: () -> Unit) {
55 | btShare.addActionListener {
56 | listener()
57 | }
58 | }
59 |
60 | private fun initLayout() {
61 |
62 | val flowLayout = FlowLayout()
63 | flowLayout.vgap = 0
64 | flowLayout.hgap = 5
65 | flowLayout.alignment = FlowLayout.LEFT
66 | layout = flowLayout
67 | }
68 |
69 | private fun initButton() {
70 | setIconButton(btAdd, AllIcons.General.Add)
71 | setIconButton(btRemove, AllIcons.General.Remove)
72 | setIconButton(btCopy, AllIcons.General.CopyHovered)
73 | setIconButton(btShare, AllIcons.Actions.Download)
74 | }
75 |
76 | private fun setIconButton(button: JButton, icon: Icon) {
77 | add(button)
78 | button.toolTipText = button.text
79 | button.icon = icon
80 | button.text = ""
81 | button.preferredSize = Dimension(25, 25)
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/template/Template.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.template
2 |
3 | import com.dengzii.plugin.template.model.FileTreeDsl
4 |
5 | /**
6 | *
7 | * author : dengzi
8 | * e-mail : denua@foxmail.com
9 | * github : https://github.com/dengzii
10 | * time : 2020/1/14
11 | * desc :
12 | *
13 | */
14 | object Template {
15 |
16 | val ANDROID_RES = FileTreeDsl {
17 | dir("res") {
18 | dir("drawable") { }
19 | dir("layout") { }
20 | dir("values") { }
21 | }
22 | }
23 | val ANDROID_TEST = FileTreeDsl {
24 | dir("AndroidTest") {
25 |
26 | }
27 | }
28 |
29 | val JUNIT_TEST = FileTreeDsl {
30 | dir("test") {
31 |
32 | }
33 | }
34 |
35 | val EMPTY = FileTreeDsl {
36 | file("src")
37 | }
38 |
39 | val ANDROID_MVP = FileTreeDsl {
40 | placeholder("MVP_NAME", "Example")
41 |
42 | // fileTemplate("", "")
43 |
44 | file("\${MVP_NAME}Contract.java")
45 | file("\${MVP_NAME}View.java")
46 | file("\${MVP_NAME}Presenter.java")
47 | file("\${MVP_NAME}Model.java")
48 | }
49 |
50 | val ANDROID_APP = FileTreeDsl {
51 | placeholder("MODULE_NAME", "app")
52 | placeholder("PACKAGE_NAME", "com.example")
53 |
54 | fileTemplate("MainActivity.java", "Template MainActivity.java")
55 | fileTemplate("AndroidManifest.xml", "Template Manifest.xml")
56 | fileTemplate("build.gradle", "Template build.gradle")
57 |
58 | dir("\${MODULE_NAME}") {
59 | dir("src") {
60 | include(ANDROID_TEST)
61 | dir("main") {
62 | dir("java") {
63 | dir("\${PACKAGE_NAME}") {
64 | dir("\${MODULE_NAME}") {
65 | file("MainActivity.java")
66 | }
67 | }
68 | }
69 | include(ANDROID_RES)
70 | file("AndroidManifest.xml")
71 | }
72 | include(JUNIT_TEST)
73 | }
74 | file(".gitignore")
75 | file("build.gradle")
76 | file("ProGuard.pro")
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/Config.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template
2 |
3 | import com.dengzii.plugin.template.model.Module
4 | import com.dengzii.plugin.template.utils.Logger
5 | import com.google.gson.GsonBuilder
6 | import com.intellij.ide.util.PropertiesComponent
7 |
8 | /**
9 | *
10 | * author : dengzi
11 | * e-mail : denua@foxmail.com
12 | * github : https://github.com/dengzii
13 | * time : 2020/1/3
14 | * desc :
15 | *
16 | */
17 |
18 | object Config {
19 | private val TAG = Config::class.java.simpleName
20 |
21 | private const val KEY_TEMPLATES = "KEY_TEMPLATES"
22 | private const val KEY_INIT = "KEY_INIT"
23 |
24 | val GSON by lazy { GsonBuilder().setLenient().create() }
25 | private val STORE by lazy { PropertiesComponent.getInstance() }
26 |
27 | private fun clear() = STORE.unsetValue(KEY_TEMPLATES)
28 |
29 | fun loadModuleTemplates(): MutableList {
30 | val result = mutableListOf()
31 | val arr = STORE.getValues(KEY_TEMPLATES)
32 |
33 | if (STORE.getBoolean(KEY_INIT)) {
34 | Logger.i(TAG, "INIT... load AucFrame template")
35 | result.add(Module.getAucModule())
36 | result.add(Module.getAucApp())
37 | result.add(Module.getAucExport())
38 | result.add(Module.getAucPkg())
39 | }
40 | if (arr.isNullOrEmpty()) {
41 | return result
42 | }
43 | Logger.i(TAG, "loadModuleTemplates")
44 | arr.filter { !it.isNullOrBlank() }.forEach {
45 | try {
46 | val module = GSON.fromJson(it, Module::class.java)
47 | module.initTemplate()
48 | result.add(module)
49 | } catch (e: Exception) {
50 | clear()
51 | Logger.e(TAG, e)
52 | return result
53 | }
54 | }
55 |
56 | return result
57 | }
58 |
59 | fun saveModuleTemplates(templates: List) {
60 | if (templates.isEmpty()) {
61 | clear()
62 | return
63 | }
64 | val t = mutableListOf()
65 | templates.forEach {
66 | t.add(GSON.toJson(it))
67 | Logger.d(TAG, "saveModuleTemplates ${t[t.lastIndex]}")
68 | }
69 | STORE.setValue(KEY_INIT, false, false)
70 | STORE.setValues(KEY_TEMPLATES, t.toTypedArray())
71 | }
72 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Generate Module From Template
2 |
3 | [ ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template)
4 | [ ](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template)
5 |
6 | [中文 - README](https://github.com/dengzii/GenerateModuleFromTemplate/blob/master/README-ZH.md)
7 |
8 | ### Create a directory structure from a highly customizable template
9 |
10 | Using this plugin, help you create directories and files from customizable template
11 |
12 | ### Feature
13 | 1. custom directory structure.
14 | 2. support placeholders, and replace it when you create module.
15 | 3. create file from template.
16 | 4. support file template variables.
17 | 5. share your template with partner.
18 |
19 | ### Usage
20 | 1. Configure template in plugin settings: File > Settings > Tools > Module Template Settings
21 | 2. Create directories from the 'Structure' tab, click the right mouse button to operate the file tree.
22 | 3. FileTree can use placeholders, the placeholder should like this -> ${YOUR_PLACEHOLDER_HERE}.
23 | 4. The 'File Template' tab lists which template the specified file uses, you can also use placeholders for FileName field.
24 | 5. The 'Placeholder' tab's table defines placeholders for replacing filenames and file templates
25 |
26 | ### Changelog
27 | - 1.4.0: feature: Support export and import template to file, adjust action button position.
28 | - 1.3.1: fix: AucFrame module template bugs.
29 | - 1.3: fix: Placeholder don't work when call FileTreeNode.include.
30 | - 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template.
31 | - 1.1: feature: support create module template, placeholder, file template
32 | - 1.1: feature: support create module template, placeholder, file template 1.0: basically feature, generate module directories from template
33 | - 1.0: basically feature, generate module directories from template
34 |
35 | ### Screenshot
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/model/FileTreeDsl.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.model
2 |
3 | import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
4 |
5 | class FileTreeDsl() : FileTreeNode() {
6 |
7 | constructor(block: FileTreeDsl.() -> Unit) : this() {
8 | invoke(block)
9 | }
10 |
11 | operator fun invoke(block: FileTreeDsl.() -> Unit): FileTreeDsl {
12 | this.block()
13 | return this
14 | }
15 |
16 | operator fun FileTreeNode.invoke(block: FileTreeNode.() -> Unit) {
17 | block(this)
18 | }
19 |
20 | /**
21 | * create directory nodes from the path
22 | * if contains '/' or '.' in path
23 | *
24 | * @param path The dir path
25 | * @param block The child node domain
26 | */
27 | fun FileTreeNode.dir(path: String, block: FileTreeNode.() -> Unit = {}) {
28 | if (!isDir) {
29 | this(block)
30 | return
31 | }
32 | val newNode = FileTreeNode(this, path, true)
33 | if (addChild(newNode)) {
34 | newNode.invoke(block)
35 | }
36 | }
37 |
38 | fun FileTreeNode.file(name: String) {
39 | if (!isDir) return
40 | name.getPlaceholder().ifNotEmpty {
41 | allPlaceholder.addAll(this)
42 | }
43 | addChild(FileTreeNode(this, name, false))
44 | }
45 |
46 | fun FileTreeNode.placeholder(name: String, value: String) {
47 | if (this.placeholders == null) {
48 | this.placeholders = mutableMapOf()
49 | }
50 | placeholders!![name] = value
51 | }
52 |
53 | /**
54 | * merge all children of another node to this.
55 | * all placeholders and file templates of target node
56 | * will be copied to it's each children
57 | */
58 | fun FileTreeNode.include(other: FileTreeNode, override: Boolean = false) {
59 | if (!isDir) return
60 | other.children.forEach {
61 | val includeChild = it.clone()
62 | if (other.placeholders != null) {
63 | if (includeChild.placeholders == null) {
64 | includeChild.placeholders = mutableMapOf()
65 | }
66 | other.placeholders?.forEach { k, v ->
67 | if (getFileTemplateInherit()?.containsKey(k) != true) {
68 | includeChild.placeholders?.put(k, v)
69 | }
70 | }
71 | }
72 | if (other.fileTemplates != null) {
73 | if (includeChild.fileTemplates == null) {
74 | includeChild.fileTemplates = mutableMapOf()
75 | }
76 | other.fileTemplates?.forEach { k, v ->
77 | if (fileTemplates?.containsKey(k) != true) {
78 | includeChild.fileTemplates?.put(k, v)
79 | }
80 | }
81 | }
82 | addChild(includeChild, override)
83 | }
84 | }
85 |
86 | fun FileTreeNode.fileTemplate(fileName: String, template: String) {
87 | if (this.fileTemplates == null) {
88 | this.fileTemplates = mutableMapOf()
89 | }
90 | fileTemplates!![fileName] = template
91 | }
92 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/CreateFileDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
62 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/FileWriteCommand.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template
2 |
3 | import com.dengzii.plugin.template.model.FileTreeNode
4 | import com.dengzii.plugin.template.model.Module
5 | import com.dengzii.plugin.template.utils.Logger
6 | import com.dengzii.plugin.template.utils.PluginKit
7 | import com.intellij.openapi.command.UndoConfirmationPolicy
8 | import com.intellij.openapi.command.WriteCommandAction
9 | import com.intellij.openapi.vfs.VirtualFile
10 | import com.intellij.util.ThrowableRunnable
11 |
12 | /**
13 | *
14 | * author : dengzi
15 | * e-mail : denua@foxmail.com
16 | * github : https://github.com/dengzii
17 | * time : 2019/12/31
18 | * desc :
19 | *
20 | */
21 | class FileWriteCommand(private var kit: PluginKit, private var module: Module) : ThrowableRunnable {
22 |
23 | companion object {
24 | private val TAG = FileWriteCommand::class.java.simpleName
25 | fun startAction(kit: PluginKit, module: Module) {
26 | WriteCommandAction.writeCommandAction(kit.project)
27 | .withGlobalUndo()
28 | .withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
29 | .run(FileWriteCommand(kit, module))
30 | }
31 | }
32 |
33 | override fun run() {
34 |
35 | val current = kit.getVirtualFile() ?: return
36 | if (!current.isDirectory) {
37 | Logger.i(TAG, "Current target is not directory.")
38 | return
39 | }
40 | val fileTreeNode = module.template
41 | Logger.d(TAG, "Placeholders : " + fileTreeNode.getPlaceholderInherit().toString())
42 | Logger.d(TAG, "FileTemplates : " + fileTreeNode.getFileTemplateInherit().toString())
43 | fileTreeNode.build()
44 | fileTreeNode.children.forEach {
45 | createFileTree(it, current)
46 | }
47 | }
48 |
49 | private fun createFileTree(treeNode: FileTreeNode, currentDirectory: VirtualFile) {
50 | Logger.d(TAG, "create node $treeNode")
51 | if (treeNode.isDir) {
52 | val dir = kit.createDir(treeNode.getRealName(), currentDirectory)
53 | if (dir == null) {
54 | Logger.e(TAG, "create directory failure: ${treeNode.getRealName()}")
55 | return
56 | }
57 | treeNode.children.forEach {
58 | createFileTree(it, dir)
59 | Logger.d(TAG, "create dir ${it.getPath()}")
60 | }
61 | } else {
62 | val template = treeNode.getTemplateFile()
63 | if (template != null) {
64 | val result = kit.createFileFromTemplate(
65 | treeNode.getRealName(),
66 | template,
67 | treeNode.getPlaceholderInherit().orEmpty(),
68 | currentDirectory)
69 | if (result == null) {
70 | Logger.e(TAG, "create file from template failed, file: ${treeNode.getRealName()} template:$template")
71 | } else {
72 | Logger.d(TAG, "create file from template ${treeNode.getRealName()}")
73 | }
74 | } else {
75 | kit.createFile(treeNode.getRealName(), currentDirectory)
76 | Logger.d(TAG, "create file ${treeNode.getPath()}")
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.dengzii.plugin.template
3 | Generate Module From Template
4 | 1.4.0
5 | dengzi
6 |
7 | Create a directory structure from a highly customizable template
10 |
11 | Using this plugin, help you create directories and files from customizable template
12 |
13 | Feature
14 | 1. custom directory structure
15 | 2. support placeholders, and replace it when you create module.
16 | 3. create file from template
17 | 4. support file template variables
18 |
19 | Usage
20 | 1. Configure template in plugin settings: File > Settings > Tools > Module Template Settings
21 | 2. Create directories from the 'Structure' tab, click the right mouse button to operate the file tree.
22 | 3. FileTree can use placeholders, the placeholder should like this -> ${YOUR_PLACEHOLDER_HERE}.
23 | 4. The 'File Template' tab lists which template the specified file uses, you can also use placeholders for FileName field.
24 | 5. The 'Placeholder' tab's table defines placeholders for replacing filenames and file templates
25 |
26 |
27 | Contribute
28 | GitHub
29 | Any question please create issue
30 |
31 |
32 | Screenshot
33 |
34 |
35 |
36 |
37 |
38 | ]]>
39 |
40 |
42 | 1.3.1: fix: AucFrame module template bugs
43 | 1.3: fix: Placeholder don't work when call FileTreeNode.include
44 | 1.2: feature: all IntelliJ platform IDEs support, file template selection support when edit module template.
45 | 1.1: feature: support create module template, placeholder, file template
46 | 1.0: basically feature, generate module directories from template
47 | ]]>
48 |
49 |
50 |
51 |
52 | com.intellij.modules.platform
53 | com.intellij.modules.lang
54 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/test/FileTreeDslTest.kt:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import com.dengzii.plugin.template.model.FileTreeDsl
4 | import org.junit.Test
5 |
6 | class FileTreeDslTest {
7 |
8 | @Test
9 | fun createSimpleFileTreeTest() {
10 | val tree = FileTreeDsl {
11 | file("file")
12 | dir("dir1") {
13 | file("file2")
14 | dir("dir2") {
15 | dir("dir3") {
16 | dir("dir4")
17 | }
18 | dir("dir5") {
19 | dir("dir6")
20 | dir("dir9")
21 | }
22 | }
23 | file("file3")
24 | }
25 | dir("dir7") {
26 | dir("dir8")
27 | }
28 | }
29 | println(tree.getTreeGraph())
30 | }
31 |
32 | @Test
33 | fun createPackageDirTest() {
34 |
35 | val tree = FileTreeDsl {
36 | dir("src") {
37 | dir("com/example/app") {
38 | file("Main.java")
39 | }
40 | dir("com/example/app1")
41 | }
42 | file("README.md")
43 | }
44 | println(tree.getTreeGraph())
45 | }
46 |
47 |
48 | @Test
49 | fun fileNamePlaceholderTest() {
50 | val tree = FileTreeDsl {
51 | placeholder("FILE_1", "first_file")
52 | placeholder("FILE_2", "second_file")
53 | file("\${FILE_1}.java")
54 | dir("com/example") {
55 | println(name)
56 | file("\${FILE_2}.java")
57 | }
58 | }
59 | println(tree.getTreeGraph())
60 | }
61 |
62 | @Test
63 | fun expandDirectoriesTest() {
64 | val tree = FileTreeDsl {
65 | dir("src") {
66 | dir("com.dengzii.plugin") {
67 | dir("model")
68 | dir("template")
69 | }
70 | dir("test")
71 | }
72 | }
73 | tree.build()
74 | println(tree.getTreeGraph())
75 |
76 | val tree2 = FileTreeDsl {
77 | dir("src") {
78 | dir("com.dengzii.plugin") {
79 | dir("dir1/dir2/") {
80 | dir("model")
81 | dir("template")
82 | }
83 | dir("model")
84 | dir("template")
85 | }
86 | dir("test")
87 | }
88 | }
89 | tree2.build()
90 | println(tree2.getTreeGraph())
91 | }
92 |
93 | @Test
94 | fun expandDirectoriesInPlaceholderTest() {
95 |
96 | val tree = FileTreeDsl {
97 | placeholder("PACKAGE_NAME", "com.dengzii.plugin")
98 | dir("src") {
99 | dir("\${PACKAGE_NAME}") {
100 | dir("model")
101 | dir("template")
102 | }
103 | dir("test")
104 | }
105 | }
106 | tree.build()
107 | println(tree.getTreeGraph())
108 | }
109 |
110 | @Test
111 | fun getAllPlaceholderInTreeNodeNameTest() {
112 | val tree = FileTreeDsl {
113 | placeholder("PACKAGE_NAME", "com.dengzii.plugin")
114 | dir("src") {
115 | dir("\${PACKAGE_NAME}") {
116 | dir("model")
117 | dir("template"){
118 | file("\${TEST}")
119 | file("\${TEST2}")
120 | }
121 | }
122 | dir("test")
123 | }
124 | }
125 | println(tree.getAllPlaceholderInTree())
126 | }
127 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/EditableTable.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui
2 |
3 | import com.intellij.ui.components.JBScrollPane
4 | import com.intellij.ui.table.JBTable
5 | import java.awt.BorderLayout
6 | import java.awt.event.FocusEvent
7 | import java.awt.event.FocusListener
8 | import javax.swing.JPanel
9 | import javax.swing.table.DefaultTableModel
10 |
11 | /**
12 | *
13 | * author : dengzi
14 | * e-mail : denua@foxmail.com
15 | * github : https://github.com/MrDenua
16 | * time : 2020/1/14
17 | * desc :
18 | *
19 | */
20 | class EditableTable(header: Array, colEditable: Array = emptyArray()) : JPanel() {
21 |
22 | private val scrollPanel = JBScrollPane()
23 | private val editToolbar = EditToolbar()
24 | private val table = JBTable()
25 | private var tableModel = TableModel(header, colEditable)
26 |
27 | init {
28 | layout = BorderLayout()
29 |
30 | add(editToolbar, BorderLayout.NORTH)
31 | scrollPanel.setViewportView(table)
32 | add(scrollPanel, BorderLayout.CENTER)
33 | table.fillsViewportHeight = true
34 | table.showHorizontalLines = true
35 | table.showVerticalLines = true
36 | table.model = tableModel
37 | table.putClientProperty("terminateEditOnFocusLost", true)
38 | initListener()
39 | }
40 |
41 | fun setToolBarVisible(visible: Boolean) {
42 | editToolbar.isVisible = visible
43 | }
44 |
45 | fun setData(data: MutableList>) {
46 | tableModel.setData(data)
47 | tableModel.fireTableDataChanged()
48 | table.updateUI()
49 | }
50 |
51 | fun setPairData(data: Map?) {
52 | val dataList = mutableListOf>()
53 | data?.forEach { (t, u) ->
54 | dataList.add(mutableListOf(t, u))
55 | }
56 | tableModel.setData(dataList)
57 | tableModel.fireTableDataChanged()
58 | table.updateUI()
59 | }
60 |
61 | fun getPairResult(): MutableMap {
62 | val result = mutableMapOf()
63 | for (i in 0 until table.rowCount) {
64 | val key = table.getValueAt(i, 0).toString()
65 | val value = table.getValueAt(i, 1).toString()
66 | if (key.isBlank() || value.isBlank()) {
67 | continue
68 | }
69 | result[key] = value
70 | }
71 | return result
72 | }
73 |
74 | private fun initListener() {
75 |
76 | editToolbar.onAdd {
77 | tableModel.add()
78 | table.updateUI()
79 | }
80 | editToolbar.onCopy {
81 | if (table.selectedRow == -1) {
82 | return@onCopy
83 | }
84 | tableModel.copy(table.selectedRow)
85 | table.updateUI()
86 | }
87 | editToolbar.onRemove {
88 | if (table.selectedRow == -1) {
89 | return@onRemove
90 | }
91 | tableModel.remove(table.selectedRow)
92 | table.updateUI()
93 | }
94 | }
95 |
96 | internal class TableModel(private val header: Array, private val colEditable: Array) : DefaultTableModel() {
97 |
98 | fun setData(fileTemp: MutableList>?) {
99 | while (rowCount > 0) {
100 | removeRow(0)
101 | }
102 | if (fileTemp == null) {
103 | return
104 | }
105 | fileTemp.forEach {
106 | addRow(it.toTypedArray())
107 | }
108 | }
109 |
110 | fun add() {
111 | addRow(Array(header.size) { "" })
112 | fireTableDataChanged()
113 | }
114 |
115 | fun remove(row: Int) {
116 | removeRow(row)
117 | fireTableDataChanged()
118 | }
119 |
120 | fun copy(row: Int) {
121 | val r = mutableListOf()
122 | for (c in 0 until columnCount) {
123 | r.add(getValueAt(row, c).toString())
124 | }
125 | addRow(r.toTypedArray())
126 | fireTableDataChanged()
127 | }
128 |
129 | override fun isCellEditable(row: Int, column: Int): Boolean {
130 | return if (colEditable.isEmpty()) super.isCellEditable(row, column) else colEditable[column]
131 | }
132 |
133 | override fun getColumnClass(columnIndex: Int): Class<*> {
134 | return String::class.java
135 | }
136 |
137 | override fun getColumnCount(): Int {
138 | return header.size
139 | }
140 |
141 | override fun getColumnName(column: Int): String {
142 | return if (header.isEmpty()) super.getColumnName(column) else header[column]
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/template/AucTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.template
2 |
3 | import com.dengzii.plugin.template.model.FileTreeDsl
4 |
5 | /**
6 | *
7 | * author : dengzi
8 | * e-mail : denua@foxmail.com
9 | * github : https://github.com/dengzii
10 | * time : 2019/1/1
11 | * desc :
12 |
*/
13 | object AucTemplate {
14 |
15 | private val aucFileTemplates: () -> MutableMap = {
16 | mutableMapOf(
17 | Pair("AndroidManifest.xml", "Template Manifest.xml"),
18 | Pair("build.gradle", "Template build.gradle"),
19 | Pair("MainActivity.java", "Template MainActivity.java"),
20 | Pair("\${FEATURE_NAME}App.java", "Template Application.java"),
21 | Pair("\${FEATURE_NAME}Api.java", "Template AucApiClass.java"),
22 | Pair("\${FEATURE_NAME}ApiImpl.java", "Template AucApiImplClass.java")
23 | )
24 | }
25 |
26 | private val aucPlaceholders: () -> MutableMap = {
27 | mutableMapOf(
28 | Pair("PACKAGE_NAME", "com.example"),
29 | Pair("FEATURE_NAME", "Feature1")
30 | )
31 | }
32 |
33 | val APP = FileTreeDsl {
34 | addFileTemplates(aucFileTemplates())
35 | addPlaceholders(aucPlaceholders())
36 | placeholder("MODULE_NAME", "app")
37 |
38 | dir("app") {
39 | dir("src") {
40 | dir("main") {
41 | dir("java") {
42 | dir("\${PACKAGE_NAME}") {
43 | dir("\${FEATURE_NAME}") {
44 | dir("app") {
45 | file("MainActivity.java")
46 | file("\${FEATURE_NAME}App.java")
47 | }
48 | }
49 | }
50 | }
51 | include(Template.ANDROID_RES)
52 | file("AndroidManifest.xml")
53 | }
54 | }
55 | file(".gitignore")
56 | file("build.gradle")
57 | file("proguard-rules.pro")
58 | }
59 | }
60 |
61 | val PKG = FileTreeDsl {
62 | addFileTemplates(aucFileTemplates())
63 | addPlaceholders(aucPlaceholders())
64 | placeholder("MODULE_NAME", "pkg")
65 | dir("pkg") {
66 | dir("src") {
67 | dir("main") {
68 | dir("java") {
69 | dir("\${PACKAGE_NAME}") {
70 | dir("\${FEATURE_NAME}") {
71 | dir("pkg") {
72 | file("\${FEATURE_NAME}ApiImpl.java")
73 | }
74 | }
75 | }
76 | }
77 | include(Template.ANDROID_RES)
78 | file("AndroidManifest.xml")
79 | }
80 | }
81 | file(".gitignore")
82 | file("build.gradle")
83 | file("proguard-rules.pro")
84 | }
85 | }
86 |
87 | val EXPORT = FileTreeDsl {
88 | addFileTemplates(aucFileTemplates())
89 | addPlaceholders(aucPlaceholders())
90 | placeholder("MODULE_NAME", "export")
91 | dir("export") {
92 | dir("src") {
93 | dir("main") {
94 | dir("java") {
95 | dir("\${PACKAGE_NAME}") {
96 | dir("\${FEATURE_NAME}") {
97 | dir("export") {
98 | file("\${FEATURE_NAME}Api.java")
99 | }
100 | }
101 | }
102 | }
103 | include(Template.ANDROID_RES)
104 | file("AndroidManifest.xml")
105 | }
106 | }
107 | file(".gitignore")
108 | file("build.gradle")
109 | file("proguard-rules.pro")
110 | }
111 | }
112 |
113 | val MODULE = FileTreeDsl {
114 | addFileTemplates(aucFileTemplates())
115 | addPlaceholders(aucPlaceholders())
116 | dir("\${FEATURE_NAME}") {
117 |
118 | include(APP {
119 | placeholders?.remove("\${FEATURE_NAME}")
120 | placeholders?.remove("\${PACKAGE_NAME}")
121 | })
122 | include(PKG {
123 | placeholders?.remove("\${FEATURE_NAME}")
124 | placeholders?.remove("\${PACKAGE_NAME}")
125 | })
126 | include(EXPORT {
127 | placeholders?.remove("\${FEATURE_NAME}")
128 | placeholders?.remove("\${PACKAGE_NAME}")
129 | })
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/CreateFileDialog.java:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui;
2 |
3 | import com.dengzii.plugin.template.model.FileTreeNode;
4 | import com.dengzii.plugin.template.utils.PluginKit;
5 | import com.intellij.ide.fileTemplates.FileTemplate;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 | import java.awt.event.KeyAdapter;
10 | import java.awt.event.KeyEvent;
11 |
12 | public class CreateFileDialog extends JDialog {
13 | private static final String NONE = "None";
14 |
15 | private JPanel contentPane;
16 | private JComboBox cbTemplate;
17 | private JTextField tfName;
18 | private JLabel lbTemplate;
19 | private JLabel lbName;
20 | private JButton btConfirm;
21 |
22 | private CreateFileCallback createFileCallback;
23 | private boolean isDir;
24 | private FileTreeNode parent;
25 | private FileTreeNode current;
26 |
27 | public static void showForRefactor(FileTreeNode node, CreateFileCallback createFileCallback) {
28 | CreateFileDialog createFileDialog = new CreateFileDialog();
29 | createFileDialog.createFileCallback = createFileCallback;
30 | createFileDialog.isDir = node.isDir();
31 | createFileDialog.parent = node.getParent();
32 | createFileDialog.current = node;
33 | createFileDialog.initDialog();
34 | }
35 |
36 | public static void showForCreate(FileTreeNode parent, boolean isDir, CreateFileCallback createFileCallback) {
37 | CreateFileDialog createFileDialog = new CreateFileDialog();
38 | createFileDialog.createFileCallback = createFileCallback;
39 | createFileDialog.isDir = isDir;
40 | createFileDialog.parent = parent;
41 | createFileDialog.initDialog();
42 | }
43 |
44 | private CreateFileDialog() {
45 | setContentPane(contentPane);
46 | setModal(true);
47 | setDefaultCloseOperation(DISPOSE_ON_CLOSE);
48 | }
49 |
50 | private void initFileTemplateList() {
51 | FileTemplate[] fileTemplates = PluginKit.Companion.getAllFileTemplate();
52 | String[] items = new String[fileTemplates.length + 1];
53 | items[0] = NONE;
54 | for (int i = 0; i < fileTemplates.length; i++) {
55 | items[i + 1] = fileTemplates[i].getName();
56 | }
57 | cbTemplate.setModel(new DefaultComboBoxModel<>(items));
58 | if (isRefactor() && current.getTemplateFile() != null
59 | && PluginKit.Companion.getFileTemplate(current.getTemplateFile()) != null) {
60 | cbTemplate.setSelectedItem(current.getTemplateFile());
61 | }
62 | }
63 |
64 | private void onConfirm() {
65 | if (tfName.getText().trim().isEmpty()) {
66 | return;
67 | }
68 | if (isRefactor()) {
69 | current.setName(tfName.getText());
70 | // setup template
71 | if (!isDir && getSelectedTemplate() != null && !getSelectedTemplate().equals(NONE)) {
72 | current.setTemplate(getSelectedTemplate());
73 | }
74 | } else {
75 | current = new FileTreeNode(parent, tfName.getText(), isDir);
76 | }
77 |
78 | if (!isRefactor() && parent != null && parent.hasChild(tfName.getText(), isDir)) {
79 | lbName.setText(lbName.getText() + " (already exist.)");
80 | return;
81 | }
82 |
83 | createFileCallback.callback(current);
84 | dispose();
85 | }
86 |
87 | private void initDialog() {
88 |
89 | if (isDir) {
90 | lbTemplate.setVisible(false);
91 | cbTemplate.setVisible(false);
92 | } else {
93 | initFileTemplateList();
94 | }
95 | pack();
96 |
97 | Dimension screen = getToolkit().getScreenSize();
98 | int w = getWidth();
99 | int h = getHeight();
100 | int x = screen.width / 2 - w / 2;
101 | int y = screen.height / 2 - h / 2 - 100;
102 | setLocation(x, y);
103 | setPreferredSize(new Dimension(w, h));
104 |
105 | setTitle((isRefactor() ? "Refactor " : "New ") + (isDir ? "Directory" : "File"));
106 | if (isRefactor()) {
107 | tfName.setText(current.getRealName());
108 | }
109 | tfName.addKeyListener(new KeyAdapter() {
110 | @Override
111 | public void keyReleased(KeyEvent e) {
112 | super.keyReleased(e);
113 | if (e.getKeyCode() == KeyEvent.VK_ENTER) {
114 | onConfirm();
115 | }
116 | }
117 | });
118 | btConfirm.addActionListener(e -> onConfirm());
119 | setVisible(true);
120 | tfName.requestFocus();
121 | }
122 |
123 | private String getSelectedTemplate() {
124 | if (isDir) {
125 | return null;
126 | }
127 | return cbTemplate.getSelectedItem().toString();
128 | }
129 |
130 | private boolean isRefactor() {
131 | return current != null;
132 | }
133 |
134 | public interface CreateFileCallback {
135 | void callback(FileTreeNode fileTreeNode);
136 | }
137 |
138 | public static void main(String[] args) {
139 | CreateFileDialog.showForCreate(null, true, fileTreeNode -> {
140 |
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/utils/PluginKit.kt:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.utils
2 |
3 | import com.intellij.ide.fileTemplates.FileTemplate
4 | import com.intellij.ide.fileTemplates.FileTemplateManager
5 | import com.intellij.ide.fileTemplates.FileTemplateUtil
6 | import com.intellij.ide.fileTemplates.impl.FileTemplateManagerImpl
7 | import com.intellij.openapi.actionSystem.AnActionEvent
8 | import com.intellij.openapi.actionSystem.PlatformDataKeys
9 | import com.intellij.openapi.module.Module
10 | import com.intellij.openapi.module.ModuleManager
11 | import com.intellij.openapi.project.Project
12 | import com.intellij.openapi.vfs.VirtualFile
13 | import com.intellij.openapi.vfs.VirtualFileManager
14 | import com.intellij.psi.PsiDirectory
15 | import com.intellij.psi.PsiElement
16 | import com.intellij.psi.PsiFile
17 | import com.intellij.psi.PsiManager
18 | import java.util.*
19 |
20 | /**
21 | *
22 | * author : dengzi
23 | * e-mail : denua@foxmail.com
24 | * github : https://github.com/MrDenua
25 | * time : 2019/12/31
26 | * desc :
27 | *
28 | */
29 | class PluginKit private constructor(e: AnActionEvent) {
30 |
31 | private val event = e
32 | lateinit var project: Project
33 |
34 | init {
35 | if (e.project != null) {
36 | project = e.project!!
37 | }
38 | }
39 |
40 | fun isProjectValid(): Boolean {
41 | return project.isOpen && project.isInitialized
42 | }
43 |
44 | fun getModules(): Array {
45 | return ModuleManager.getInstance(project).modules
46 | }
47 |
48 | fun getCurrentPsiFile(): PsiFile? {
49 | return event.getData(PlatformDataKeys.PSI_FILE)
50 | }
51 |
52 | fun getCurrentPsiDirectory(): PsiDirectory? {
53 | if (getCurrentPsiFile() is PsiDirectory) {
54 | return getCurrentPsiFile() as PsiDirectory
55 | }
56 | return null
57 | }
58 |
59 | fun getPsiDirectoryByPath(path: String): PsiDirectory? {
60 | val vf = VirtualFileManager.getInstance().findFileByUrl("") ?: return null
61 | return PsiManager.getInstance(project).findDirectory(vf)
62 | }
63 |
64 | fun getPsiDirectoryByVirtualFile(vf: VirtualFile): PsiDirectory? {
65 | if (!vf.isDirectory) {
66 | Logger.e(TAG, "${vf.path} is not a directory.")
67 | return null
68 | }
69 | return PsiManager.getInstance(project).findDirectory(vf)
70 | }
71 |
72 | fun getVirtualFile(): VirtualFile? {
73 | return event.getData(PlatformDataKeys.VIRTUAL_FILE)
74 | }
75 |
76 | fun createDir(name: String, vf: VirtualFile? = getVirtualFile()): VirtualFile? {
77 | if (!checkCreateFile(name, vf)) {
78 | return null
79 | }
80 | return vf!!.createChildDirectory(null, name)
81 | }
82 |
83 | fun createFile(name: String, vf: VirtualFile? = getVirtualFile()) {
84 | if (!checkCreateFile(name, vf)) {
85 | return
86 | }
87 | vf!!.createChildData(null, name)
88 | }
89 |
90 | fun createFileFromTemplate(fileName: String,
91 | templateName: String,
92 | propertiesMap: Map,
93 | directory: VirtualFile): PsiElement? {
94 |
95 | val fileTemplateManager = FileTemplateManager.getInstance(project)
96 | val properties = Properties(fileTemplateManager.defaultProperties)
97 | propertiesMap.forEach { (t, u) ->
98 | properties.setProperty(t.strip(), u)
99 | }
100 | val template = fileTemplateManager.getTemplate(templateName) ?: return null
101 | val psiDirectory = getPsiDirectoryByVirtualFile(directory) ?: return null
102 | return try {
103 | FileTemplateUtil.createFromTemplate(template, fileName, properties, psiDirectory)
104 | } catch (e: Throwable) {
105 | Logger.e(TAG, e)
106 | null
107 | }
108 | }
109 |
110 | fun addTemplate(name: String, template: String) {
111 | FileTemplateManager.getInstance(project).addTemplate(name, template)
112 | }
113 |
114 | private fun String.strip(): String {
115 | var result = this
116 | if (startsWith("\${") && endsWith("}")) {
117 | result = result.substring(2, length - 1)
118 | }
119 | return result
120 | }
121 |
122 | private fun checkCreateFile(name: String, vf: VirtualFile?): Boolean {
123 | if (vf == null || !vf.isDirectory) {
124 | Logger.e(TAG, "target is null or is not a directory.")
125 | return false
126 | }
127 | if (vf.findChild(name)?.exists() == true) {
128 | Logger.e(TAG, "directory already exists.")
129 | return false
130 | }
131 | return true
132 | }
133 |
134 | companion object {
135 |
136 | private val TAG = PluginKit::class.java.simpleName;
137 |
138 | fun get(e: AnActionEvent): PluginKit {
139 | return PluginKit(e)
140 | }
141 |
142 | fun getAllFileTemplate(): Array = FileTemplateManagerImpl.getDefaultInstance().allTemplates
143 |
144 | fun getFileTemplate(name: String): FileTemplate? = FileTemplateManagerImpl.getDefaultInstance().getTemplate(name)
145 | }
146 | }
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/CreateModuleDialog.java:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui;
2 |
3 | import com.dengzii.plugin.template.Config;
4 | import com.dengzii.plugin.template.TemplateConfigurable;
5 | import com.dengzii.plugin.template.model.Module;
6 | import com.dengzii.plugin.template.utils.Logger;
7 | import com.intellij.icons.AllIcons;
8 | import com.intellij.openapi.options.ShowSettingsUtil;
9 | import com.intellij.openapi.project.Project;
10 | import org.bouncycastle.math.raw.Mod;
11 |
12 | import javax.swing.*;
13 | import java.awt.*;
14 | import java.awt.event.ActionEvent;
15 | import java.awt.event.KeyEvent;
16 | import java.util.ArrayList;
17 | import java.util.Collections;
18 | import java.util.HashMap;
19 | import java.util.List;
20 |
21 | public class CreateModuleDialog extends JDialog {
22 |
23 | private static final String TAG = CreateModuleDialog.class.getSimpleName();
24 |
25 | private JPanel rootPanel;
26 | private JLabel labelTitle;
27 | private JComboBox cbModuleTemplate;
28 | private JPanel mainPanel;
29 | private JPanel contentPanel;
30 |
31 | private JButton btConfigure;
32 | private JButton btCancel;
33 | private JButton btPrevious;
34 | private JButton btFinish;
35 | private JPanel panelPlaceholder;
36 |
37 | private EditableTable tablePlaceholder;
38 | private OnFinishListener onFinishListener;
39 |
40 | private java.util.List moduleTemplates = Collections.emptyList();
41 | private Module selectedModule;
42 |
43 | private HashMap panels = new HashMap<>();
44 | private List titles = new ArrayList<>();
45 |
46 | private PreviewPanel previewPanel;
47 |
48 | private int currentPanelIndex;
49 | private Project project;
50 |
51 | private CreateModuleDialog(Project project, OnFinishListener onFinishListener) {
52 | setContentPane(rootPanel);
53 | setModal(true);
54 | getRootPane().setDefaultButton(btFinish);
55 | this.project = project;
56 | this.onFinishListener = onFinishListener;
57 | }
58 |
59 | public static void createAndShow(Project project, OnFinishListener onFinishListener) {
60 |
61 | CreateModuleDialog dialog = new CreateModuleDialog(project, onFinishListener);
62 | dialog.initDialog();
63 | dialog.initData();
64 | dialog.pack();
65 | dialog.setVisible(true);
66 | }
67 |
68 | public static void main(String[] args) {
69 | createAndShow(null, config -> {
70 |
71 | });
72 | }
73 |
74 | private void onNextClick(ActionEvent e) {
75 | selectedModule.getTemplate().setPlaceholders(tablePlaceholder.getPairResult());
76 | previewPanel.setModuleConfigPreview(selectedModule);
77 |
78 | if (currentPanelIndex == panels.size() - 1) {
79 | onFinishListener.onFinish(selectedModule);
80 | dispose();
81 | return;
82 | }
83 | currentPanelIndex++;
84 | setPanel();
85 | setButton();
86 | }
87 |
88 | private void onPreviousClick(ActionEvent e) {
89 | if (currentPanelIndex == 0) {
90 | return;
91 | }
92 | currentPanelIndex--;
93 | setPanel();
94 | setButton();
95 | }
96 |
97 | private void onConfClick(ActionEvent e) {
98 | ShowSettingsUtil.getInstance().editConfigurable(project, new TemplateConfigurable(this::initData));
99 |
100 | }
101 |
102 | private void setButton() {
103 | btPrevious.setEnabled(true);
104 | btFinish.setText("Next");
105 | if (currentPanelIndex == panels.size() - 1) {
106 | btFinish.setText("Finish");
107 | } else if (currentPanelIndex == 0) {
108 | btPrevious.setEnabled(false);
109 | }
110 | }
111 |
112 | private void setPanel() {
113 | String title = titles.get(currentPanelIndex);
114 | labelTitle.setText(title);
115 | contentPanel.removeAll();
116 | contentPanel.add(panels.get(title));
117 | contentPanel.doLayout();
118 | contentPanel.updateUI();
119 | }
120 |
121 | private void onModuleConfigChange() {
122 | Logger.INSTANCE.i(TAG, "onModuleConfigChange");
123 | selectedModule = moduleTemplates.get(cbModuleTemplate.getSelectedIndex());
124 | previewPanel.setModuleConfigPreview(selectedModule);
125 | tablePlaceholder.setPairData(selectedModule.getTemplate().getPlaceholderInherit());
126 | }
127 |
128 | private void initDialog() {
129 |
130 | Dimension screen = getToolkit().getScreenSize();
131 | int w = screen.width / 3 + 160;
132 | int h = 600;
133 | int x = screen.width / 2 - w / 2;
134 | int y = screen.height / 2 - h / 2;
135 | setLocation(x, y);
136 | rootPanel.setPreferredSize(new Dimension(w, h));
137 |
138 | setTitle("Create Module");
139 | setDefaultCloseOperation(DISPOSE_ON_CLOSE);
140 | rootPanel.registerKeyboardAction(e -> dispose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
141 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
142 |
143 | tablePlaceholder = new EditableTable(new String[]{"Key", "Value"}, new Boolean[]{false, true});
144 | tablePlaceholder.setToolBarVisible(false);
145 | panelPlaceholder.add(tablePlaceholder, BorderLayout.CENTER);
146 |
147 | btConfigure.setIcon(AllIcons.General.GearPlain);
148 | previewPanel = new PreviewPanel();
149 | titles.add("Create Module From Template");
150 | titles.add("Preview Module");
151 |
152 | panels.put(titles.get(0), mainPanel);
153 | panels.put(titles.get(1), previewPanel);
154 |
155 | btFinish.addActionListener(this::onNextClick);
156 | btPrevious.addActionListener(this::onPreviousClick);
157 | btCancel.addActionListener(e -> dispose());
158 | btConfigure.addActionListener(this::onConfClick);
159 |
160 | cbModuleTemplate.addItemListener(e -> {
161 | onModuleConfigChange();
162 | });
163 | }
164 |
165 | private void initData() {
166 | Logger.INSTANCE.i(TAG, "initData");
167 | moduleTemplates = Config.INSTANCE.loadModuleTemplates();
168 | List temp = new ArrayList<>();
169 | moduleTemplates.forEach(i -> temp.add(i.getTemplateName()));
170 | cbModuleTemplate.setModel(new DefaultComboBoxModel<>(temp.toArray()));
171 | if (cbModuleTemplate.getModel().getSize() > 0) {
172 | cbModuleTemplate.setSelectedIndex(0);
173 | onModuleConfigChange();
174 | }
175 | }
176 |
177 | public interface OnFinishListener {
178 | void onFinish(Module config);
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/ConfigurePanel.form:
--------------------------------------------------------------------------------
1 |
2 |
169 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/PreviewPanel.java:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui;
2 |
3 | import com.dengzii.plugin.template.model.FileTreeNode;
4 | import com.dengzii.plugin.template.model.Module;
5 | import com.dengzii.plugin.template.utils.Logger;
6 | import com.intellij.icons.AllIcons;
7 | import com.intellij.openapi.ui.JBMenuItem;
8 | import com.intellij.openapi.ui.JBPopupMenu;
9 | import com.intellij.packageDependencies.ui.TreeModel;
10 | import com.intellij.ui.ColoredTreeCellRenderer;
11 | import com.intellij.ui.treeStructure.Tree;
12 | import com.intellij.util.IconUtil;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | import javax.swing.*;
16 | import javax.swing.tree.DefaultMutableTreeNode;
17 | import javax.swing.tree.TreeCellRenderer;
18 | import javax.swing.tree.TreeNode;
19 | import javax.swing.tree.TreePath;
20 | import java.awt.*;
21 | import java.awt.event.ActionListener;
22 | import java.awt.event.MouseAdapter;
23 | import java.awt.event.MouseEvent;
24 | import java.util.Enumeration;
25 | import java.util.HashMap;
26 | import java.util.Map;
27 |
28 | /**
29 | *
30 | * author : dengzi
31 | * e-mail : denua@foxmail.com
32 | * github : https://github.com/dengzii
33 | * time : 2020/1/3
34 | * desc :
35 | *
36 | */
37 | public class PreviewPanel extends JPanel {
38 |
39 | private static final String TAG = PreviewPanel.class.getSimpleName();
40 |
41 | private JPanel contentPanel;
42 | private Tree fileTree;
43 |
44 | private Map fileIconMap = new HashMap<>();
45 | private boolean replacePlaceholder = true;
46 |
47 | // render tree node icon, title
48 | private TreeCellRenderer treeCellRenderer = new ColoredTreeCellRenderer() {
49 | @Override
50 | public void customizeCellRenderer(@NotNull JTree jTree, Object value, boolean b, boolean b1, boolean b2, int i, boolean b3) {
51 | if (value instanceof DefaultMutableTreeNode) {
52 | Object object = ((DefaultMutableTreeNode) value).getUserObject();
53 | if (object instanceof FileTreeNode) {
54 | FileTreeNode node = (FileTreeNode) object;
55 | setIcon(IconUtil.getAddClassIcon());
56 | this.append(replacePlaceholder ? node.getRealName() : node.getName());
57 | if (node.isDir()) {
58 | setIcon(AllIcons.Nodes.Package);
59 | } else {
60 | String suffix = "";
61 | if (node.getName().contains(".")) {
62 | suffix = node.getName().substring(node.getName().lastIndexOf("."));
63 | }
64 | setIcon(fileIconMap.getOrDefault(suffix, AllIcons.FileTypes.Text));
65 | }
66 | } else {
67 | setIcon(AllIcons.FileTypes.Unknown);
68 | }
69 | }
70 | }
71 | };
72 |
73 | PreviewPanel() {
74 | setLayout(new BorderLayout());
75 | add(contentPanel);
76 | initPanel();
77 | }
78 |
79 | public void setReplacePlaceholder(boolean replace) {
80 | if (replace != replacePlaceholder) {
81 | replacePlaceholder = replace;
82 | fileTree.updateUI();
83 | }
84 | }
85 |
86 | public void setModuleConfigPreview(Module module) {
87 | Module clone = module.clone();
88 | clone.getTemplate().build();
89 | setModuleConfig(clone);
90 | }
91 |
92 | public void setModuleConfig(Module module) {
93 | Logger.INSTANCE.i("PreviewPanel", "setModuleConfig");
94 |
95 | FileTreeNode node = module.getTemplate();
96 | fileTree.setModel(getTreeModel(node));
97 | fileTree.doLayout();
98 | fileTree.updateUI();
99 | expandAll(fileTree, new TreePath(fileTree.getModel().getRoot()), true);
100 | }
101 |
102 | /**
103 | * init Tree icon, node title, mouse listener
104 | */
105 | private void initPanel() {
106 | fileIconMap.put(".java", AllIcons.Nodes.Class);
107 | fileIconMap.put(".kt", AllIcons.Nodes.Class);
108 | fileIconMap.put(".xml", AllIcons.FileTypes.Xml);
109 | fileIconMap.put(".gradle", AllIcons.FileTypes.Config);
110 | fileIconMap.put(".gitignore", AllIcons.FileTypes.Config);
111 | fileIconMap.put(".pro", AllIcons.FileTypes.Config);
112 |
113 | fileTree.addMouseListener(getTreeMouseListener());
114 |
115 | fileTree.setEditable(true);
116 | fileTree.setCellRenderer(treeCellRenderer);
117 | }
118 |
119 | /**
120 | * the JTree mouse listener use for edit tree
121 | *
122 | * @return the mouse adapter
123 | */
124 | private MouseAdapter getTreeMouseListener() {
125 |
126 | return new MouseAdapter() {
127 | @Override
128 | public void mouseClicked(MouseEvent e) {
129 | super.mouseClicked(e);
130 | if (e.getButton() != MouseEvent.BUTTON3) {
131 | return;
132 | }
133 | int row = fileTree.getRowForLocation(e.getX(), e.getY());
134 | if (row != -1) {
135 | DefaultMutableTreeNode treeNode = ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent());
136 | Object[] nodes = treeNode.getUserObjectPath();
137 | // has selected node and the node is FileTreeNode
138 | if (nodes.length == 0 || !(nodes[0] instanceof FileTreeNode)) {
139 | return;
140 | }
141 | FileTreeNode selectedNode = ((FileTreeNode) nodes[nodes.length - 1]);
142 | showEditMenu(e.getComponent(), selectedNode, e.getX(), e.getY());
143 | Logger.INSTANCE.d(TAG, selectedNode.toString());
144 | }
145 | }
146 | };
147 | }
148 |
149 | private void showEditMenu(Component anchor, FileTreeNode current, int x, int y) {
150 |
151 | JBPopupMenu jbPopupMenu = new JBPopupMenu();
152 | if (current.isDir()) {
153 | jbPopupMenu.add(getMenuItem("New Directory", e -> {
154 | CreateFileDialog.showForCreate(current, true, fileTreeNode -> addTreeNode(current, fileTreeNode));
155 | }));
156 | jbPopupMenu.add(getMenuItem("New File", e -> {
157 | CreateFileDialog.showForCreate(current, false, fileTreeNode -> addTreeNode(current, fileTreeNode));
158 | }));
159 | }
160 | if (!current.isRoot()) {
161 | jbPopupMenu.add(getMenuItem("Rename", e -> {
162 | CreateFileDialog.showForRefactor(current, fileTreeNode -> {
163 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).setUserObject(fileTreeNode);
164 | fileTree.updateUI();
165 | });
166 | }));
167 | jbPopupMenu.add(getMenuItem("Delete", e -> {
168 | if (current.removeFromParent()) {
169 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).removeFromParent();
170 | fileTree.updateUI();
171 | }
172 | }));
173 | }
174 |
175 | jbPopupMenu.show(anchor, x, y);
176 | }
177 |
178 | private void addTreeNode(FileTreeNode parent, FileTreeNode node) {
179 |
180 | parent.getChildren().add(node);
181 | ((DefaultMutableTreeNode) fileTree.getLastSelectedPathComponent()).add(new DefaultMutableTreeNode(node));
182 | fileTree.updateUI();
183 | }
184 |
185 | @NotNull
186 | private TreeModel getTreeModel(FileTreeNode fileTreeNode) {
187 | return new TreeModel(getTree(fileTreeNode));
188 | }
189 |
190 | // convert FileTreeNode to JTree TreeNode
191 | @NotNull
192 | private DefaultMutableTreeNode getTree(FileTreeNode treeNode) {
193 | DefaultMutableTreeNode result = new DefaultMutableTreeNode(treeNode, true);
194 |
195 | if (treeNode.isDir()) {
196 | treeNode.getChildren().forEach(i -> result.add(getTree(i)));
197 | }
198 | return result;
199 | }
200 |
201 | // expand all nodes
202 | private void expandAll(JTree tree, @NotNull TreePath parent, boolean expand) {
203 | TreeNode node = (TreeNode) parent.getLastPathComponent();
204 | if (node.getChildCount() >= 0) {
205 | for (Enumeration e = node.children(); e.hasMoreElements(); ) {
206 | TreeNode n = (TreeNode) e.nextElement();
207 | TreePath path = parent.pathByAddingChild(n);
208 | expandAll(tree, path, expand);
209 | }
210 | }
211 | if (expand) {
212 | tree.expandPath(parent);
213 | } else {
214 | tree.collapsePath(parent);
215 | }
216 | }
217 |
218 | private JBMenuItem getMenuItem(String title, ActionListener listener) {
219 | JBMenuItem jbMenuItem = new JBMenuItem(title);
220 | jbMenuItem.addActionListener(listener);
221 | return jbMenuItem;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/CreateModuleDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
185 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/ui/ConfigurePanel.java:
--------------------------------------------------------------------------------
1 | package com.dengzii.plugin.template.ui;
2 |
3 | import com.dengzii.plugin.template.Config;
4 | import com.dengzii.plugin.template.CreateModuleAction;
5 | import com.dengzii.plugin.template.model.Module;
6 | import com.dengzii.plugin.template.utils.PopMenuUtils;
7 | import com.intellij.openapi.fileChooser.FileChooser;
8 | import com.intellij.openapi.fileChooser.FileChooserDescriptor;
9 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
10 | import com.intellij.openapi.vfs.VirtualFile;
11 | import com.intellij.ui.DocumentAdapter;
12 | import com.intellij.ui.components.JBList;
13 | import com.intellij.ui.components.JBTabbedPane;
14 | import org.jetbrains.annotations.NotNull;
15 |
16 | import javax.swing.*;
17 | import javax.swing.event.DocumentEvent;
18 | import java.awt.*;
19 | import java.awt.event.MouseAdapter;
20 | import java.awt.event.MouseEvent;
21 | import java.io.*;
22 | import java.util.HashMap;
23 | import java.util.List;
24 | import java.util.Map;
25 |
26 | /**
27 | *
28 | * author : dengzi
29 | * e-mail : dengzii@foxmail.com
30 | * github : https://github.com/dengzii
31 | * time : 2020/1/8
32 | * desc :
33 | *
34 | */
35 | public class ConfigurePanel extends JPanel {
36 |
37 | private JPanel contentPane;
38 |
39 | private JTextField tfName;
40 | private JBList listTemplate;
41 | private JPanel panelStructure;
42 | private JPanel panelPlaceholder;
43 | private JPanel panelFileTemp;
44 | private EditToolbar actionbar;
45 | private JCheckBox cbPlaceholder;
46 | private JBTabbedPane tabbedPane;
47 |
48 | private List configs;
49 | private DefaultListModel templateListModel;
50 |
51 | private Module currentConfig;
52 |
53 | private ConfigurePanel panelConfig;
54 | private PreviewPanel panelPreview;
55 |
56 | private EditableTable tablePlaceholder;
57 | private EditableTable tableFileTemp;
58 |
59 | public ConfigurePanel() {
60 |
61 | initComponent();
62 | loadConfig();
63 | initData();
64 | }
65 |
66 | public void cacheConfig() {
67 | if (currentConfig == null) return;
68 | currentConfig.getTemplate().setFileTemplates(tableFileTemp.getPairResult());
69 | currentConfig.getTemplate().setPlaceholders(tablePlaceholder.getPairResult());
70 | }
71 |
72 | public void saveConfig() {
73 | Config.INSTANCE.saveModuleTemplates(configs);
74 | }
75 |
76 | private void initComponent() {
77 | setLayout(new BorderLayout());
78 | add(contentPane);
79 |
80 | panelPreview = new PreviewPanel();
81 | tablePlaceholder = new EditableTable(new String[]{"Placeholder", "Default Value"}, new Boolean[]{true, true});
82 | tableFileTemp = new EditableTable(new String[]{"FileName", "Template"}, new Boolean[]{true, true});
83 | panelStructure.add(panelPreview);
84 | panelPlaceholder.add(tablePlaceholder, BorderLayout.CENTER);
85 | panelFileTemp.add(tableFileTemp, BorderLayout.CENTER);
86 | }
87 |
88 |
89 | private void initData() {
90 |
91 | actionbar.onAdd(new MouseAdapter() {
92 | @Override
93 | public void mouseClicked(MouseEvent e) {
94 | onAddConfig(e);
95 | }
96 | });
97 | actionbar.onRemove(() -> {
98 | onRemoveConfig();
99 | return null;
100 | });
101 | actionbar.onCopy(() -> {
102 | onCopyConfig();
103 | return null;
104 | });
105 | actionbar.onExport(() -> {
106 | onExportTemplate();
107 | return null;
108 | });
109 |
110 | cbPlaceholder.addChangeListener(e -> {
111 | panelPreview.setReplacePlaceholder(cbPlaceholder.isSelected());
112 | });
113 | tabbedPane.addChangeListener(e -> onChangeTab());
114 | listTemplate.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
115 | listTemplate.addListSelectionListener(e -> {
116 | if (noSelectedConfig()) return;
117 | onConfigSelect(getSelectedConfigIndex());
118 | });
119 | listTemplate.addMouseListener(new MouseAdapter() {
120 | @Override
121 | public void mouseClicked(MouseEvent e) {
122 | super.mouseClicked(e);
123 | if (e.getButton() != MouseEvent.BUTTON3) {
124 | return;
125 | }
126 | }
127 | });
128 | //noinspection unchecked
129 | listTemplate.setModel(templateListModel);
130 | tfName.getDocument().addDocumentListener(new DocumentAdapter() {
131 | @Override
132 | protected void textChanged(@NotNull DocumentEvent documentEvent) {
133 | if (currentConfig != null)
134 | currentConfig.setTemplateName(tfName.getText());
135 | }
136 | });
137 | if (templateListModel.size() > 1) {
138 | listTemplate.setSelectedIndex(0);
139 | }
140 | }
141 |
142 | private void onChangeTab() {
143 | if (0 == tabbedPane.getSelectedIndex()) {
144 | currentConfig.getTemplate().setPlaceholders(tablePlaceholder.getPairResult());
145 | }
146 | if (2 == tabbedPane.getSelectedIndex()) {
147 | Map mergedPlaceholder = currentConfig.getTemplate().getAllPlaceholdersMap();
148 | List allPlaceholders = currentConfig.getTemplate().getAllPlaceholderInTree();
149 | allPlaceholders.forEach(s -> {
150 | if (!mergedPlaceholder.containsKey(s)) {
151 | mergedPlaceholder.put(s, "");
152 | }
153 | });
154 | tablePlaceholder.setPairData(mergedPlaceholder);
155 | }
156 | panelPreview.setModuleConfig(currentConfig);
157 | }
158 |
159 | private void onAddConfig(MouseEvent e) {
160 |
161 | Map items = new HashMap<>();
162 | items.put("Empty Template", () -> addModuleTemplate(Module.Companion.getEmpty()));
163 | items.put("Android Application", () -> addModuleTemplate(Module.Companion.getAndroidApplication()));
164 | items.put("Auc Module", () -> addModuleTemplate(Module.Companion.getAucModule()));
165 | items.put("Auc app", () -> addModuleTemplate(Module.Companion.getAucApp()));
166 | items.put("Auc Pkg", () -> addModuleTemplate(Module.Companion.getAucPkg()));
167 | items.put("Auc Export", () -> addModuleTemplate(Module.Companion.getAucExport()));
168 | items.put("Android Mvp", () -> addModuleTemplate(Module.Companion.getAndroidMvp()));
169 | items.put("=> Import From File", this::onImportTemplate);
170 | PopMenuUtils.INSTANCE.create(items).show(actionbar, e.getX(), e.getY());
171 | }
172 |
173 | private void addModuleTemplate(Module module) {
174 | configs.add(module);
175 | currentConfig = module;
176 | templateListModel.addElement(module.getTemplateName());
177 | listTemplate.doLayout();
178 | listTemplate.setSelectedIndex(configs.indexOf(module));
179 | }
180 |
181 | private void onRemoveConfig() {
182 | if (noSelectedConfig()) {
183 | return;
184 | }
185 | int selectedIndex = getSelectedConfigIndex();
186 | configs.remove(selectedIndex);
187 | templateListModel.remove(selectedIndex);
188 | listTemplate.doLayout();
189 | }
190 |
191 | private void onCopyConfig() {
192 | if (noSelectedConfig()) {
193 | return;
194 | }
195 | Module newConfig = currentConfig.clone();
196 | configs.add(newConfig);
197 | templateListModel.addElement(newConfig.getTemplateName());
198 | listTemplate.doLayout();
199 | listTemplate.setSelectedIndex(configs.indexOf(newConfig));
200 | }
201 |
202 | private void loadConfig() {
203 | configs = Config.INSTANCE.loadModuleTemplates();
204 | templateListModel = new DefaultListModel<>();
205 | configs.forEach(module -> templateListModel.addElement(module.getTemplateName()));
206 | if (templateListModel.size() > 0) {
207 | listTemplate.setSelectedIndex(0);
208 | }
209 | }
210 |
211 | private void onExportTemplate() {
212 |
213 | FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
214 | descriptor.setTitle("Save Template to File");
215 | VirtualFile vf = FileChooser.chooseFile(descriptor,
216 | CreateModuleAction.Companion.getProject(),
217 | null);
218 | if (vf != null && vf.isWritable()) {
219 | String config = Config.INSTANCE.getGSON().toJson(currentConfig);
220 | File file = new File(vf.getPath(), currentConfig.getTemplateName() + ".json");
221 | OutputStreamWriter outputStream = null;
222 | try {
223 | outputStream = new OutputStreamWriter(new FileOutputStream(file));
224 | outputStream.write(config);
225 | } catch (IOException e) {
226 | e.printStackTrace();
227 | } finally {
228 | if (outputStream != null) {
229 | try {
230 | outputStream.flush();
231 | outputStream.close();
232 | } catch (IOException e) {
233 | e.printStackTrace();
234 | }
235 | }
236 | }
237 | }
238 | }
239 |
240 | private void onImportTemplate() {
241 |
242 | FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor("json");
243 | descriptor.setTitle("Import Template From File");
244 | VirtualFile vf = FileChooser.chooseFile(descriptor,
245 | CreateModuleAction.Companion.getProject(),
246 | null);
247 | if (vf != null && vf.exists()) {
248 | File file = new File(vf.getPath());
249 | BufferedInputStream inputStream = null;
250 | try {
251 | inputStream = new BufferedInputStream(new FileInputStream(file));
252 | byte[] bytes = new byte[1024];
253 | int len = 0;
254 | StringBuilder stringBuilder = new StringBuilder();
255 | while ((len = inputStream.read(bytes)) > 0) {
256 | stringBuilder.append(new String(bytes, 0, len));
257 | }
258 | Module template = Config.INSTANCE.getGSON().fromJson(stringBuilder.toString(), Module.class);
259 | template.initTemplate(template.getTemplate());
260 | addModuleTemplate(template);
261 | } catch (IOException e) {
262 | e.printStackTrace();
263 | } finally {
264 | if (inputStream != null) {
265 | try {
266 | inputStream.close();
267 | } catch (IOException e) {
268 | e.printStackTrace();
269 | }
270 | }
271 | }
272 | }
273 | }
274 |
275 | private void onConfigSelect(int index) {
276 | if (currentConfig == configs.get(index)) {
277 | return;
278 | }
279 | if (currentConfig != null) {
280 | cacheConfig();
281 | }
282 | currentConfig = configs.get(index);
283 | tfName.setText(currentConfig.getTemplateName());
284 |
285 | // update tree, file template and placeholder table
286 | panelPreview.setModuleConfig(currentConfig);
287 | tableFileTemp.setPairData(currentConfig.getTemplate().getFileTemplates());
288 | tablePlaceholder.setPairData(currentConfig.getTemplate().getPlaceholders());
289 | }
290 |
291 | private int getSelectedConfigIndex() {
292 | return listTemplate.getSelectedIndex();
293 | }
294 |
295 | private boolean noSelectedConfig() {
296 | return getSelectedConfigIndex() == -1;
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/com/dengzii/plugin/template/model/FileTreeNode.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package com.dengzii.plugin.template.model
4 |
5 | import com.dengzii.plugin.template.utils.Logger
6 | import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
7 | import java.io.File
8 | import java.util.*
9 | import java.util.regex.Pattern
10 |
11 | /**
12 | *
13 | * author : dengzi
14 | * e-mail : denua@foxmail.com
15 | * github : https://github.com/dengzii
16 | * time : 2019/1/1
17 | * desc :
18 |
*/
19 | open class FileTreeNode() {
20 |
21 | var name: String = ""
22 |
23 | var isDir = true
24 | var children
25 | set(value) {
26 | realChildren = value
27 | realChildren.forEach { labeledChildren[it.getLabel()] = it }
28 | }
29 | get() = realChildren
30 |
31 | var placeholders: MutableMap? = null
32 | // all placeholder in tree node name
33 | val allPlaceholder = mutableListOf()
34 |
35 | // template for node, higher priority than fileTemplates
36 | private var template: String? = null
37 |
38 | // template of filename
39 | var fileTemplates: MutableMap? = null
40 |
41 | private var realChildren = mutableSetOf()
42 |
43 | // the label composed by 'name' and 'isDir'.
44 | @Transient
45 | private val labeledChildren = mutableMapOf()
46 | @Transient
47 | var parent: FileTreeNode? = null
48 |
49 | companion object {
50 |
51 | var packageNameToDirs = true
52 | private val TAG = FileTreeNode::class.java.simpleName
53 | private val sPathSplitPattern = Pattern.compile("[./]")
54 | private val sPlaceholderPattern = Pattern.compile("\\$\\{([A-Za-z0-9_\\-]+)}")
55 |
56 | fun root(path: String): FileTreeNode {
57 | val root = File(path)
58 | if (!root.isDirectory) {
59 | throw RuntimeException("The root must be a directory.")
60 | }
61 | return with(FileTreeNode(null, path, true)) {
62 | this.parent = this
63 | this
64 | }
65 | }
66 | }
67 |
68 | constructor(parent: FileTreeNode?, name: String, isDir: Boolean) : this() {
69 | this.name = name
70 | this.parent = parent
71 | this.isDir = isDir
72 | name.getPlaceholder().ifNotEmpty {
73 | allPlaceholder.addAll(this)
74 | }
75 | }
76 |
77 | fun removeFromParent(): Boolean {
78 | if (parent != null) {
79 | parent!!.labeledChildren.remove(getLabel())
80 | parent!!.realChildren.remove(this)
81 | parent = null
82 | return true
83 | }
84 | return false
85 | }
86 |
87 | /**
88 | * add child to this node
89 | *
90 | * @param child The child need be add
91 | * @param override Weather override the node with the same name and type
92 | */
93 | fun addChild(child: FileTreeNode, override: Boolean = false): Boolean {
94 | if (hasChild(child.getLabel())) {
95 | if (override) {
96 | Logger.d(TAG, "node has already exists $child")
97 | return false
98 | } else {
99 | Logger.w(TAG, "THE SAME CHILE ALREADY EXISTS IN $name: ${child.name}")
100 | }
101 | }
102 | child.parent = this
103 | realChildren.add(child)
104 | labeledChildren[child.getLabel()] = child
105 | return true
106 | }
107 |
108 | fun hasChild(name: String, isDir: Boolean): Boolean {
109 | realChildren.forEach {
110 | if (it.name == name && isDir == it.isDir) {
111 | return true
112 | }
113 | }
114 | return false
115 | }
116 |
117 | fun hasChild(label: String): Boolean {
118 | return labeledChildren.containsKey(label)
119 | }
120 |
121 | fun getChild(name: String, isDir: Boolean): FileTreeNode? {
122 | return labeledChildren["${name}_$isDir"]
123 | }
124 |
125 | /**
126 | * get the real name replace with placeholder
127 | */
128 | fun getRealName(): String {
129 | return getRealName(this.name)
130 | }
131 |
132 | private fun getRealName(fileName: String = this.name): String {
133 | return if (isDir) {
134 | fileName.replacePlaceholder(getPlaceholderInherit(), false).toLowerCase()
135 | } else {
136 | fileName.replacePlaceholder(getPlaceholderInherit(), true)
137 | }
138 | }
139 |
140 | fun getFileTemplateInherit(): MutableMap? {
141 | return fileTemplates ?: parent?.getFileTemplateInherit()
142 | }
143 |
144 | fun getPlaceholderInherit(): MutableMap? {
145 | return placeholders ?: parent?.getPlaceholderInherit()
146 | }
147 |
148 | fun getTemplateFile(): String? {
149 | return template ?: getFileTemplateInherit()?.get(name)
150 | }
151 |
152 | fun setTemplate(name: String) {
153 | template = name
154 | }
155 |
156 | fun addPlaceholders(placeholders: Map) {
157 | if (this.placeholders == null) {
158 | this.placeholders = mutableMapOf()
159 | }
160 | this.placeholders!!.putAll(placeholders)
161 | }
162 |
163 |
164 | fun addFileTemplates(placeholders: Map) {
165 | if (this.fileTemplates == null) {
166 | this.fileTemplates = mutableMapOf()
167 | }
168 | fileTemplates!!.putAll(placeholders)
169 | }
170 |
171 | /**
172 | * set current node as root.
173 | * the file of specified path must be a directory and exist.
174 | * the root's 'name' is the root path of entire file tree.
175 | *
176 | * @param path: The root directory path of whole file tree
177 | */
178 | fun setRoot(path: String): FileTreeNode {
179 | if (!File(path).isDirectory) {
180 | throw RuntimeException("The root must be a directory.")
181 | }
182 | parent = this
183 | name = path
184 | return this
185 | }
186 |
187 | /**
188 | * traversal and create file tree, must be called from the root node.
189 | * the existing files will be skipped
190 | */
191 | fun create() {
192 | if (!isRoot()) {
193 | throw RuntimeException("Must create structure from root node.")
194 | }
195 | createChild()
196 | }
197 |
198 | /**
199 | * traverse and call back each node of the entire file tree.
200 | */
201 | fun traversal(block: (it: FileTreeNode, depth: Int) -> Unit, depth: Int = 0) {
202 | if (!isDir) return
203 |
204 | realChildren.forEach {
205 | block(it, depth)
206 | it.traversal(block, depth + 1)
207 | }
208 | }
209 |
210 | /**
211 | * create directories tree from a list
212 | * the larger the index, the deeper the directory
213 | *
214 | * @param dirs The dirs list to create tree
215 | * @param parent The parent of current node
216 | */
217 | fun createDirs(dirs: MutableList, parent: FileTreeNode): FileTreeNode {
218 | if (dirs.isEmpty()) {
219 | return parent
220 | }
221 | // the first dir
222 | val first = dirs.first()
223 | dirs.removeAt(0)
224 | val find = parent.getChild(first, true)
225 | if (find != null) {
226 | return createDirs(dirs, find)
227 | }
228 | val newNode = FileTreeNode(parent, first, true)
229 | parent.addChild(newNode)
230 | // create child dir
231 | return createDirs(dirs, newNode)
232 | }
233 |
234 | /**
235 | * get path of current node.
236 | * if the current node is the root node, it will return absolute path,
237 | * otherwise return relative path.
238 | *
239 | * @return The intact path of current node
240 | */
241 | fun getPath(): String {
242 | if (isRoot() || parent == null || parent!!.getRealName() == "") {
243 | return getRealName()
244 | }
245 | return parent!!.getPath() + "/" + getRealName()
246 | }
247 |
248 | fun isRoot(): Boolean {
249 | return this == parent || parent == null
250 | }
251 |
252 | /**
253 | * build file tree
254 | * replace placeholder in name
255 | * if this is directory and name is a path or package name
256 | * the name will expand to several dirs
257 | */
258 | fun build() {
259 |
260 | if (!isDir) {
261 | return
262 | }
263 | name = getRealName()
264 | if (!isRoot()) {
265 | val dir = if (packageNameToDirs) {
266 | name.split(sPathSplitPattern)
267 | } else {
268 | name.split("/")
269 | }.filter {
270 | it.isNotBlank()
271 | }.toMutableList()
272 |
273 | expandDirs(dir)
274 | }
275 | realChildren.forEach {
276 | it.build()
277 | }
278 | }
279 |
280 | /**
281 | * expand the dir list
282 | * all the children of this will move the new dir
283 | */
284 | private fun expandDirs(dirs: MutableList) {
285 | if (dirs.isEmpty() || dirs.size == 1) {
286 | return
287 | }
288 | this.name = dirs.first()
289 | dirs.removeAt(0)
290 |
291 | var preNode: FileTreeNode = this
292 | var newChild: FileTreeNode
293 |
294 | val oldChildren = realChildren.toMutableList()
295 | oldChildren.forEach {
296 | it.removeFromParent()
297 | }
298 | dirs.forEach {
299 | newChild = FileTreeNode(preNode, it, true)
300 | preNode.addChild(newChild)
301 | preNode = newChild
302 | }
303 | oldChildren.forEach {
304 | preNode.addChild(it)
305 | }
306 | }
307 |
308 | fun getAllPlaceholderInTree(): List {
309 | val result = mutableSetOf()
310 | traversal({ fileTreeNode: FileTreeNode, _: Int ->
311 | result.addAll(fileTreeNode.allPlaceholder)
312 | })
313 | return result.toList()
314 | }
315 |
316 | fun getAllPlaceholdersMap(): Map {
317 | val result = mutableMapOf()
318 | result.putAll(placeholders.orEmpty())
319 | traversal({ fileTreeNode: FileTreeNode, _: Int ->
320 | if (fileTreeNode.placeholders != null) {
321 | result.putAll(fileTreeNode.placeholders!!)
322 | }
323 | })
324 | return result
325 | }
326 |
327 | /**
328 | * get the tree graph of node inherit
329 | *
330 | * @return The tree graph of node
331 | */
332 | fun getTreeGraph(): String {
333 | return getNodeGraph().toString()
334 | }
335 |
336 | /**
337 | * clone current node, the following fields will be copied:
338 | * name, isDir, fileTemplates, placeHolderMap, children
339 | *
340 | * the parent will not be cloned
341 | */
342 | fun clone(): FileTreeNode {
343 | val clone = FileTreeNode(null, name, isDir)
344 | clone.fileTemplates = fileTemplates?.toMutableMap()
345 | clone.placeholders = placeholders?.toMutableMap()
346 | realChildren.forEach {
347 | clone.addChild(it.clone())
348 | }
349 | return clone
350 | }
351 |
352 | private fun removeChild(label: String): FileTreeNode? {
353 | if (hasChild(label)) {
354 | realChildren.remove(labeledChildren[label])
355 | return labeledChildren.remove(label)
356 | }
357 | return null
358 | }
359 |
360 | private fun getNodeGraph(head: Stack = Stack(), str: StringBuilder = StringBuilder()): StringBuilder {
361 |
362 | head.forEach {
363 | str.append(it)
364 | }
365 | str.append(when (this) {
366 | parent?.realChildren?.last() -> "└─"
367 | parent?.realChildren?.first() -> "├─"
368 | else -> {
369 | when {
370 | parent != null -> "├─"
371 | else -> ""
372 | }
373 | }
374 | })
375 | str.append(getRealName())
376 | if (isDir) {
377 | // str.append("\tplaceholder: ").append(placeholders)
378 | }
379 | str.append("\n")
380 |
381 | if (!realChildren.isNullOrEmpty()) {
382 | head.push(when {
383 | parent == null -> ""
384 | parent?.realChildren?.last() != this -> "│\t"
385 | else -> "\t"
386 | })
387 | realChildren.forEach {
388 | str.append(it.getNodeGraph(head))
389 | }
390 | head.pop()
391 | }
392 | return str
393 | }
394 |
395 | private fun createChild() {
396 | realChildren.forEach {
397 | val file = File(it.getPath())
398 | if (file.exists()) {
399 | Logger.d(TAG, "${file.absolutePath} already exists.")
400 | } else {
401 | Logger.d(TAG, "create ${file.absolutePath}")
402 | if (it.isDir) {
403 | file.mkdir()
404 | } else {
405 | file.createNewFile()
406 | }
407 | }
408 | if (it.isDir) {
409 | it.createChild()
410 | }
411 | }
412 | }
413 |
414 | private fun getLabel(): String {
415 | return "${name}_$isDir"
416 | }
417 |
418 | override fun toString(): String {
419 | return "FileTreeNode(path='${getPath()}' isDir=$isDir, fileTemplate=${getTemplateFile()}, children=${children.size})"
420 | }
421 |
422 | protected fun String.getPlaceholder(): List {
423 | val result = mutableListOf()
424 | val nameMatcher = sPlaceholderPattern.matcher(this)
425 | while (nameMatcher.find()) {
426 | result.add(nameMatcher.group(1))
427 | }
428 | return result
429 | }
430 |
431 | private fun String.replacePlaceholder(placeholders: Map?, capitalize: Boolean = false): String {
432 | var after = this
433 | if (placeholders.isNullOrEmpty()) {
434 | return this
435 | }
436 | placeholders.forEach { (k, v) ->
437 | val replaced = if (capitalize) {
438 | v.toLowerCase().capitalize()
439 | } else {
440 | v
441 | }
442 | after = after.replace("\${$k}", replaced)
443 | }
444 | return after
445 | }
446 | }
--------------------------------------------------------------------------------