├── .github
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .run
├── Run IDE for UI Tests.run.xml
├── Run IDE with Plugin.run.xml
├── Run Plugin Tests.run.xml
├── Run Plugin Verification.run.xml
└── Run Qodana.run.xml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── assets
├── icon.svg
└── marketplace.png
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
├── spotless.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
├── kotlin
│ └── com
│ │ └── joetr
│ │ └── modulemaker
│ │ ├── Constants.kt
│ │ ├── MessageDialogWrapper.kt
│ │ ├── ModuleMakerAction.kt
│ │ ├── ModuleMakerDialogWrapper.kt
│ │ ├── MultiplatformSourceSets.kt
│ │ ├── Notifications.kt
│ │ ├── PreviewDialogWrapper.kt
│ │ ├── SettingsDialogWrapper.kt
│ │ ├── data
│ │ ├── File.kt
│ │ ├── ToProjectFile.kt
│ │ └── analytics
│ │ │ ├── ModuleCreationAnalytics.kt
│ │ │ └── ModuleCreationErrorAnalytics.kt
│ │ ├── file
│ │ └── FileWriter.kt
│ │ ├── persistence
│ │ ├── PreferenceService.kt
│ │ └── PreferenceServiceImpl.kt
│ │ ├── template
│ │ ├── AndroidModuleKtsTemplate.kt
│ │ ├── AndroidModuleTemplate.kt
│ │ ├── GitIgnoreTemplate.kt
│ │ ├── KotlinModuleKtsTemplate.kt
│ │ ├── KotlinModuleTemplate.kt
│ │ ├── ModuleReadMeTemplate.kt
│ │ ├── MultiplatformKtsTemplate.kt
│ │ ├── TemplateVariable.kt
│ │ └── TemplateWriter.kt
│ │ └── ui
│ │ ├── LabelledCheckbox.kt
│ │ ├── file
│ │ ├── FileTree.kt
│ │ └── FileTreeView.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Shape.kt
│ │ ├── Type.kt
│ │ ├── WidgetTheme.kt
│ │ └── intellij
│ │ ├── SwingColor.kt
│ │ └── ThemeChangeListener.kt
└── resources
│ └── META-INF
│ ├── plugin.xml
│ └── pluginIcon.svg
└── test
└── kotlin
└── com
└── joetr
└── modulemaker
├── AndroidModuleMakerTest.kt
├── EnhancedModuleMakerTest.kt
├── KotlinModuleMakerTest.kt
├── MultiplatformModuleMakerTest.kt
├── TestConstants.kt
├── TestUtilities.kt
└── settings
├── DefaultTemplateSettingsGradle.kt
├── NowInAndroidSettingsGradleKts.kt
└── TiviSettingsGradleKts.kt
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - uses: actions/setup-java@v3
18 | with:
19 | distribution: temurin
20 | java-version: 17
21 |
22 | - name: Setup Gradle
23 | uses: gradle/gradle-build-action@v2
24 |
25 | - name: Execute Gradle build
26 | run: ./gradlew build --stacktrace
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'release/[0-9]+.[0-9]+.[0-9]+'
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | build-and-release:
13 | runs-on: ubuntu-latest
14 | permissions:
15 | contents: write
16 | steps:
17 | - uses: actions/checkout@v3
18 | - uses: actions/setup-java@v3
19 | with:
20 | distribution: temurin
21 | java-version: 17
22 |
23 | - name: Setup Gradle
24 | uses: gradle/gradle-build-action@v2
25 |
26 | - name: Execute Gradle build
27 | run: ./gradlew buildPlugin --stacktrace
28 |
29 | - name: Upload and Release
30 | uses: fnkr/github-action-ghr@v1
31 | env:
32 | GHR_PATH: build/distributions/
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Google Services (e.g. APIs or Firebase)
30 | google-services.json
31 |
32 | # Android Profiling
33 | *.hprof
34 |
35 | .DS_Store
36 |
37 | .intellijPlatform
38 |
--------------------------------------------------------------------------------
/.run/Run IDE for UI Tests.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | true
18 | true
19 | false
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.run/Run IDE with Plugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.run/Run Plugin Tests.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.run/Run Plugin Verification.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.run/Run Qodana.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | true
22 | true
23 | false
24 |
25 |
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Module Maker Changelog
2 |
3 | ## [1.1.1]
4 | - Platform updates
5 |
6 | ## [1.1.0]
7 | - Support Multiplatform modules
8 |
9 | ## [1.0.26]
10 | - Platform updates
11 |
12 | ## [1.0.25]
13 | - Update window sizes
14 |
15 | ## [1.0.24]
16 | - Update Platform Version
17 |
18 | ## [1.0.23]
19 | - Add file preview
20 |
21 | ## [1.0.22]
22 | - Don't specify pluginUntilBuild. This plugin will work forever ;)
23 |
24 | ## [1.0.21]
25 | - Update supported platform versions
26 |
27 | ## [1.0.20]
28 | - Accept starting file location when starting Module Maker
29 |
30 | ## [1.0.19]
31 | - Dependency updates
32 |
33 | ## [1.0.18]
34 | - Attempt to fix crash on startup (again)
35 |
36 | ## [1.0.17]
37 | - Attempt to fix crash on startup
38 |
39 | ## [1.0.16]
40 | - Finished Compose UI migration (all UI is now in Compose!)
41 | - Added basic telemetry. There is no project or user specific data collected, just general module settings used when creating a module.
42 |
43 | ## [1.0.15]
44 | - Compose UI migration
45 |
46 | ## [1.0.14]
47 | - Support new intellij platform versions
48 | - Update to Kotlin 1.9
49 | - Better support for smaller screens
50 |
51 | ## [1.0.13]
52 | - Support for multi-app projects with multiple settings.gradle(.kts) files
53 | - Support a custom "include" keyword to be used to include modules in settings.gradle(.kts) files
54 |
55 | ## [1.0.12]
56 | - Performance improvement for large projects
57 |
58 | ## [1.0.11]
59 | - Added better support around multiple include keywords in settings.gradle.kts file
60 |
61 | ## [1.0.10]
62 | - Added support for custom module names for api / glue / impl modules
63 |
64 | ## [1.0.9]
65 | - Added support for file paths in settings.gradle.kts file
66 |
67 | ## [1.0.8]
68 | - Fixed issue when multiple projects were open
69 |
70 | ## [1.0.7]
71 | - Add option to add README.md when generating a module
72 | - Add option to add .gitignore to module creation
73 | - Added new section in settings to specify custom .gitignore template
74 | - Added settings under 'General' tab to set defaults for most module configurations
75 |
76 | ## [1.0.6]
77 | - Add import / export in settings
78 |
79 | ## [1.0.5]
80 | - Internal improvements
81 |
82 | ## [1.0.4]
83 | - Add support for variables in custom templates
84 |
85 | ## [1.0.3]
86 | - Auto sync project after module creation
87 |
88 | ## [1.0.2]
89 | - Added question mark next to 3 module checkbox
90 |
91 | ## [1.0.1]
92 | - Compatibility fixes
93 |
94 | ## [1.0.0]
95 | - Initial release
96 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at {{ email }}. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Transcriptase
2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
3 |
4 | - Reporting a bug
5 | - Discussing the current state of the code
6 | - Submitting a fix
7 | - Proposing new features
8 | - Becoming a maintainer
9 |
10 | ## We Develop with Github
11 | We use github to host code, to track issues and feature requests, as well as accept pull requests.
12 |
13 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests
14 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
15 |
16 | 1. Fork the repo and create your branch from `master`.
17 | 2. If you've added code that should be tested, add tests.
18 | 3. If you've changed APIs, update the documentation.
19 | 4. Ensure the test suite passes.
20 | 5. Make sure your code lints.
21 | 6. Issue that pull request!
22 |
23 | ## Any contributions you make will be under the MIT Software License
24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
25 |
26 | ## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues)
27 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
28 |
29 | ## Write bug reports with detail, background, and sample code
30 | [This is an example](http://stackoverflow.com/q/12488905/180626) of a bug report I wrote, and I think it's not a bad model. Here's [another example from Craig Hockenberry](http://www.openradar.me/11905408), an app developer whom I greatly respect.
31 |
32 | **Great Bug Reports** tend to have:
33 |
34 | - A quick summary and/or background
35 | - Steps to reproduce
36 | - Be specific!
37 | - Give sample code if you can. [My stackoverflow question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing
38 | - What you expected would happen
39 | - What actually happens
40 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
41 |
42 | People *love* thorough bug reports. I'm not even kidding.
43 |
44 | ## License
45 | By contributing, you agree that your contributions will be licensed under its MIT License.
46 |
47 | ## References
48 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 j-roskopf
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Module Maker
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | This is a plugin that allows one to create modules without having to copy / paste / modify existing modules.
18 |
19 | Creating both single modules and enhanced modules (representing the 3 module system outline [here](https://www.droidcon.com/2019/11/15/android-at-scale-square/))
20 |
21 | Additional features include:
22 |
23 | 1. Specifying gradle template for modules to align with your project specific defaults.
24 | 1. Allows for custom variables to be replaced with generated values
25 | 2. Aligning the gradle files to follow the module name
26 | 3. Generating both .gradle and .gradle.kts build files for a given module
27 |
28 |
29 | # Demo
30 |
31 | https://www.youtube.com/watch?v=ZtXCxBuiQNk
32 |
33 | ## Building
34 |
35 | Creating a release tag that follows `release/x.x.x` will create a Github release with the relevant artifacts.
36 |
37 | ## How To Use
38 |
39 | - From under the `Tools` menu
40 |
41 | Tools > Module Maker
42 |
43 | ## Installation
44 |
45 |
46 |
47 | - Using IDE built-in plugin system:
48 |
49 | Settings/Preferences > Plugins > Marketplace > Search for "Module Maker" >
50 | Install Plugin
51 |
52 | - Manually:
53 |
54 | Download the [latest release](https://github.com/j-roskopf/ModuleMakerPlugin/releases/latest) and install it manually using
55 | Settings/Preferences > Plugins > ⚙️ > Install plugin from disk...
56 |
--------------------------------------------------------------------------------
/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/marketplace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-roskopf/ModuleMakerPlugin/be7483caa297610fa4874b1931c28f9431edbe7d/assets/marketplace.png
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.changelog.Changelog
2 | import org.jetbrains.changelog.markdownToHTML
3 |
4 | fun properties(key: String) = providers.gradleProperty(key)
5 | fun environment(key: String) = providers.environmentVariable(key)
6 |
7 | plugins {
8 | id("java") // Java support
9 | alias(libs.plugins.kotlin) // Kotlin support
10 | alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin
11 | alias(libs.plugins.changelog) // Gradle Changelog Plugin
12 | kotlin("plugin.serialization") version libs.versions.kotlin.get()
13 | id("org.jetbrains.compose")
14 | alias(libs.plugins.spotless)
15 | alias(libs.plugins.compose)
16 | }
17 |
18 | group = properties("pluginGroup").get()
19 | version = properties("pluginVersion").get()
20 |
21 | buildscript {
22 | repositories {
23 | mavenCentral()
24 | google()
25 | maven { url = uri("https://plugins.gradle.org/m2/") }
26 | maven {
27 | url = uri("https://www.jetbrains.com/intellij-repository/releases")
28 | }
29 | }
30 | }
31 |
32 | // Configure project's dependencies
33 | repositories {
34 | mavenCentral()
35 | google()
36 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
37 | maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
38 | intellijPlatform {
39 | defaultRepositories()
40 | }
41 | }
42 |
43 | apply(
44 | from = "gradle/spotless.gradle"
45 | )
46 |
47 | dependencies {
48 | implementation(libs.freemarker)
49 | implementation(libs.serialization)
50 | implementation(compose.desktop.currentOs)
51 | implementation(compose.materialIconsExtended)
52 | implementation(libs.segment)
53 |
54 | val version = "0.8.18"
55 | val macTarget = "macos-arm64"
56 | val windowsTarget = "windows-x64"
57 | val linuxTarget = "linux-x64"
58 |
59 | implementation("org.jetbrains.skiko:skiko-awt-runtime-$macTarget:$version")
60 | implementation("org.jetbrains.skiko:skiko-awt-runtime-$windowsTarget:$version")
61 | implementation("org.jetbrains.skiko:skiko-awt-runtime-$linuxTarget:$version")
62 |
63 | testImplementation(libs.junit)
64 |
65 | // IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
66 | intellijPlatform {
67 | javaCompiler("243.26053.29") // https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1894
68 | create(properties("platformType").get(), properties("platformVersion").get())
69 |
70 | // Plugin Dependencies. Uses `platformBundledPlugins` property from the gradle.properties file for bundled IntelliJ Platform plugins.
71 | bundledPlugins(properties("platformBundledPlugins").map { it.split(',') })
72 |
73 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file for plugin from JetBrains Marketplace.
74 | plugins(properties("platformPlugins").map { it.split(',') })
75 |
76 | // instrumentationTools()
77 | pluginVerifier()
78 | zipSigner()
79 | }
80 | }
81 |
82 | kotlin {
83 | jvmToolchain(libs.versions.jdk.get().toInt())
84 | }
85 |
86 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
87 | changelog {
88 | groups.empty()
89 | repositoryUrl = properties("pluginRepositoryUrl")
90 | }
91 |
92 | tasks {
93 | wrapper {
94 | gradleVersion = properties("gradleVersion").get()
95 | }
96 |
97 | patchPluginXml {
98 | version = properties("pluginVersion").get()
99 | sinceBuild = properties("pluginSinceBuild").get()
100 | // untilBuild = properties("pluginUntilBuild").get()
101 |
102 | // Extract the section from README.md and provide for the plugin's manifest
103 | pluginDescription = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
104 | val start = ""
105 | val end = ""
106 |
107 | with(it.lines()) {
108 | if (!containsAll(listOf(start, end))) {
109 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
110 | }
111 | subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML)
112 | }
113 | }
114 |
115 | val changelog = project.changelog // local variable for configuration cache compatibility
116 | // Get the latest available change notes from the changelog file
117 | changeNotes = properties("pluginVersion").map { pluginVersion ->
118 | with(changelog) {
119 | renderItem(
120 | (getOrNull(pluginVersion) ?: getUnreleased())
121 | .withHeader(false)
122 | .withEmptySections(false),
123 | Changelog.OutputType.HTML
124 | )
125 | }
126 | }
127 | }
128 |
129 | signPlugin {
130 | certificateChain = environment("CERTIFICATE_CHAIN")
131 | privateKey = environment("PRIVATE_KEY")
132 | password = environment("PRIVATE_KEY_PASSWORD")
133 | }
134 | }
135 |
136 | intellijPlatformTesting {
137 | runIde {
138 | register("runIdeForUiTests") {
139 | task {
140 | jvmArgumentProviders += CommandLineArgumentProvider {
141 | listOf(
142 | "-Drobot-server.port=8082",
143 | "-Dide.mac.message.dialogs.as.sheets=false",
144 | "-Djb.privacy.policy.text=",
145 | "-Djb.consents.confirmation.enabled=false"
146 | )
147 | }
148 | }
149 |
150 | plugins {
151 | robotServerPlugin()
152 | }
153 | }
154 | }
155 | }
156 |
157 | // Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
158 | intellijPlatform {
159 | pluginConfiguration {
160 | version = properties("pluginVersion").get()
161 |
162 | // Extract the section from README.md and provide for the plugin's manifest
163 | description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
164 | val start = ""
165 | val end = ""
166 |
167 | with(it.lines()) {
168 | if (!containsAll(listOf(start, end))) {
169 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
170 | }
171 | subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML)
172 | }
173 | }
174 |
175 | val changelog = project.changelog // local variable for configuration cache compatibility
176 | // Get the latest available change notes from the changelog file
177 | changeNotes = properties("pluginVersion").map { pluginVersion ->
178 | with(changelog) {
179 | renderItem(
180 | (getOrNull(pluginVersion) ?: getUnreleased())
181 | .withHeader(false)
182 | .withEmptySections(false),
183 | Changelog.OutputType.HTML
184 | )
185 | }
186 | }
187 |
188 | ideaVersion {
189 | sinceBuild = properties("pluginSinceBuild").get()
190 | // untilBuild = properties("pluginUntilBuild").get()
191 | }
192 | }
193 |
194 | signing {
195 | certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
196 | privateKey = providers.environmentVariable("PRIVATE_KEY")
197 | password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
198 | }
199 |
200 | publishing {
201 | token = providers.environmentVariable("PUBLISH_TOKEN")
202 | // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
203 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
204 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
205 | channels = properties("pluginVersion")
206 | .map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
207 | }
208 |
209 | pluginVerification {
210 | ides {
211 | recommended()
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
2 |
3 | pluginGroup = com.joetr.modulemaker
4 | pluginName = ModuleMaker
5 | pluginRepositoryUrl = https://github.com/j-roskopf/ModuleMakerPlugin
6 | # SemVer format -> https://semver.org
7 | pluginVersion = 1.1.1
8 |
9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
10 | pluginSinceBuild = 222
11 | # pluginUntilBuild =
12 |
13 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
14 | platformType = AI
15 | # AS version and patch at the end
16 | platformVersion = 2024.3.1.1
17 |
18 | # Example: platformBundledPlugins = com.intellij.java
19 | platformBundledPlugins = com.intellij.java
20 |
21 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
22 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
23 | platformPlugins =
24 |
25 | # Gradle Releases -> https://github.com/gradle/gradle/releases
26 | # update gradle-wrapper.properties and run ./gradlew wrapper
27 | gradleVersion = 8.13
28 |
29 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
30 | kotlin.stdlib.default.dependency = false
31 |
32 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
33 | # TODO - Enable - seems to fail on CI
34 | org.gradle.configuration-cache = false
35 |
36 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
37 | org.gradle.caching = true
38 |
39 | # Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment
40 | systemProp.org.gradle.unsafe.kotlin.assignment = true
41 |
42 | compose.version=1.7.3
43 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | freemarker = "2.3.30"
3 | serialization = "1.5.1"
4 | jdk = "17"
5 | kotlin = "2.1.20"
6 | changelog = "2.0.0"
7 | gradleIntelliJPlugin = "2.4.0"
8 | spotless = "6.8.0"
9 | segment = "1.13.2"
10 | junit = "4.13.2"
11 |
12 | [libraries]
13 | freemarker = { group = "org.freemarker", name = "freemarker", version.ref = "freemarker" }
14 | serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
15 | segment = { group = "com.segment.analytics.kotlin", name = "core", version.ref = "segment" }
16 | junit = { group = "junit", name = "junit", version.ref = "junit" }
17 |
18 | [plugins]
19 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
20 | compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
21 | gradleIntelliJPlugin = { id = "org.jetbrains.intellij.platform", version.ref = "gradleIntelliJPlugin" }
22 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
23 | spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
24 |
--------------------------------------------------------------------------------
/gradle/spotless.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.diffplug.spotless"
2 |
3 | spotless {
4 | java {
5 | target '**/*.java'
6 | googleJavaFormat().aosp()
7 | removeUnusedImports()
8 | trimTrailingWhitespace()
9 | indentWithSpaces()
10 | endWithNewline()
11 | }
12 |
13 | kotlinGradle {
14 | ktlint("0.46.0")
15 | trimTrailingWhitespace()
16 | endWithNewline()
17 | }
18 |
19 | kotlin {
20 | target '**/*.kt'
21 | ktlint("0.46.0")
22 | trimTrailingWhitespace()
23 | indentWithSpaces()
24 | endWithNewline()
25 | }
26 |
27 | format 'misc', {
28 | target '**/*.gradle', '**/*.md', '**/.gitignore'
29 | indentWithSpaces()
30 | trimTrailingWhitespace()
31 | endWithNewline()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j-roskopf/ModuleMakerPlugin/be7483caa297610fa4874b1931c28f9431edbe7d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "Module Maker"
2 |
3 | pluginManagement {
4 | plugins {
5 | id("org.jetbrains.compose").version(extra["compose.version"] as String)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import freemarker.template.Configuration
4 | import freemarker.template.Version
5 |
6 | const val DEFAULT_PADDING = 4
7 | const val EXTRA_PADDING = 8
8 | const val SCROLLBAR_WIDTH = 40
9 |
10 | const val DEFAULT_EXIT_CODE = 2
11 |
12 | val FREEMARKER_VERSION: Version = Configuration.VERSION_2_3_30
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/MessageDialogWrapper.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.intellij.openapi.ui.DialogWrapper
4 | import org.jetbrains.annotations.Nullable
5 | import java.awt.BorderLayout
6 | import java.awt.Dimension
7 | import javax.swing.Action
8 | import javax.swing.JComponent
9 | import javax.swing.JPanel
10 | import javax.swing.JTextArea
11 |
12 | private const val WINDOW_WIDTH = 100
13 | private const val WINDOW_HEIGHT = 100
14 |
15 | class MessageDialogWrapper(private val message: String) : DialogWrapper(true) {
16 |
17 | init {
18 | init()
19 | }
20 |
21 | @Nullable
22 | override fun createCenterPanel(): JComponent {
23 | val dialogPanel = JPanel(BorderLayout())
24 | dialogPanel.preferredSize = Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)
25 |
26 | val label = JTextArea(message)
27 | label.isEditable = false
28 | dialogPanel.add(label, BorderLayout.CENTER)
29 |
30 | return dialogPanel
31 | }
32 |
33 | override fun createActions(): Array {
34 | return arrayOf(
35 | DialogWrapperExitAction(
36 | "Okay",
37 | 2
38 | )
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ModuleMakerAction.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.intellij.openapi.actionSystem.AnAction
4 | import com.intellij.openapi.actionSystem.AnActionEvent
5 | import com.intellij.openapi.actionSystem.CommonDataKeys
6 | import com.intellij.openapi.project.Project
7 | import com.intellij.openapi.vfs.VirtualFile
8 |
9 | class ModuleMakerAction : AnAction() {
10 | override fun actionPerformed(event: AnActionEvent) {
11 | val project: Project = event.project ?: return
12 |
13 | val startingLocation: VirtualFile? = event.getData(CommonDataKeys.VIRTUAL_FILE)
14 |
15 | // we only want to use a starting location if it's coming from a directory
16 | val shouldUseStartingLocation = startingLocation != null && startingLocation.isDirectory
17 |
18 | ModuleMakerDialogWrapper(
19 | project = project,
20 | startingLocation = if (shouldUseStartingLocation) startingLocation else null
21 | ).show()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/MultiplatformSourceSets.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyrightest (c) 2025 Joseph Roskopf
3 | */
4 |
5 | package com.joetr.modulemaker
6 |
7 | val kotlinMultiplatformSourceSets = listOf(
8 | // Main Source Sets
9 | "commonMain",
10 | "androidMain",
11 | "jvmMain",
12 | "nativeMain",
13 | "iosMain",
14 | "jsMain",
15 | "wasmJsMain",
16 | "iosArm64Main",
17 | "iosX64Main",
18 | "iosSimulatorArm64Main",
19 | "macosMain",
20 | "macosX64Main",
21 | "macosArm64Main",
22 | "linuxMain",
23 | "linuxX64Main",
24 | "linuxArm64Main",
25 | "mingwMain",
26 | "mingwX64Main",
27 | "tvosMain",
28 | "tvosArm64Main",
29 | "tvosX64Main",
30 | "tvosSimulatorArm64Main",
31 | "watchosMain",
32 | "watchosArm32Main",
33 | "watchosArm64Main",
34 | "watchosX64Main",
35 | "watchosSimulatorArm64Main"
36 | )
37 |
38 | val kotlinMultiplatformTestSourceSets = listOf(
39 | "commonTest",
40 | "androidTest",
41 | "jvmTest",
42 | "nativeTest",
43 | "iosTest",
44 | "jsTest",
45 | "wasmTest",
46 | "iosArm64Test",
47 | "iosX64Test",
48 | "iosSimulatorArm64Test",
49 | "macosTest",
50 | "macosX64Test",
51 | "macosArm64Test",
52 | "linuxTest",
53 | "linuxX64Test",
54 | "linuxArm64Test",
55 | "mingwTest",
56 | "mingwX64Test",
57 | "tvosTest",
58 | "tvosArm64Test",
59 | "tvosX64Test",
60 | "tvosSimulatorArm64Test",
61 | "watchosTest",
62 | "watchosArm32Test",
63 | "watchosArm64Test",
64 | "watchosX64Test",
65 | "watchosSimulatorArm64Test"
66 | )
67 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/Notifications.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.intellij.notification.Notification
4 | import com.intellij.notification.NotificationType
5 | import com.intellij.openapi.project.Project
6 |
7 | object Notifications {
8 | fun showExportError(project: Project) {
9 | val notification = Notification(
10 | "ModuleMaker",
11 | "Error",
12 | "An error occurred while exporting your settings",
13 | NotificationType.ERROR
14 | )
15 |
16 | notification.notify(project)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/PreviewDialogWrapper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyrightest (c) 2024 Joseph Roskopf
3 | */
4 |
5 | package com.joetr.modulemaker
6 |
7 | import androidx.compose.foundation.layout.height
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.material.Surface
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.awt.ComposePanel
15 | import androidx.compose.ui.unit.dp
16 | import com.intellij.openapi.ui.DialogWrapper
17 | import com.intellij.openapi.util.io.FileUtilRt
18 | import com.joetr.modulemaker.data.toProjectFile
19 | import com.joetr.modulemaker.ui.file.FileTree
20 | import com.joetr.modulemaker.ui.file.FileTreeView
21 | import com.joetr.modulemaker.ui.theme.WidgetTheme
22 | import java.io.File
23 | import javax.swing.Action
24 | import javax.swing.JComponent
25 |
26 | private const val WINDOW_WIDTH = 400
27 | private const val WINDOW_HEIGHT = 600
28 |
29 | class PreviewDialogWrapper(val filesToBeCreated: List, val root: String) : DialogWrapper(true) {
30 |
31 | private var tempRoot: File
32 |
33 | init {
34 | title = "Preview"
35 | init()
36 |
37 | tempRoot = FileUtilRt.createTempDirectory(root, null, true)
38 |
39 | createFileStructure(
40 | tempRoot,
41 | filesToBeCreated.map {
42 | val pathToRoot = tempRoot.absolutePath
43 | val split = it.absolutePath.split(root)
44 |
45 | // splice together the files to have a root of our temp folder
46 | File(pathToRoot, split.drop(1).joinToString(separator = ""))
47 | }
48 | )
49 | }
50 |
51 | override fun dispose() {
52 | super.dispose()
53 | tempRoot.parentFile.deleteRecursively()
54 | }
55 |
56 | override fun createCenterPanel(): JComponent {
57 | return ComposePanel().apply {
58 | setBounds(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
59 | setContent {
60 | WidgetTheme {
61 | Surface {
62 | FileTreeJPanel(
63 | modifier = Modifier.height(WINDOW_HEIGHT.dp).width(WINDOW_WIDTH.dp)
64 | )
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
71 | @Composable
72 | private fun FileTreeJPanel(
73 | modifier: Modifier = Modifier
74 | ) {
75 | val height = remember { mutableStateOf(WINDOW_HEIGHT) }
76 |
77 | FileTreeView(
78 | modifier = modifier,
79 | model = FileTree(root = tempRoot.toProjectFile()),
80 | height = height.value.dp,
81 | onClick = { }
82 | )
83 | }
84 |
85 | private fun List.root(): File {
86 | return this.minBy { file ->
87 | file.absolutePath.count { it.toString() == File.separator }
88 | }
89 | }
90 |
91 | private fun createFileStructure(root: File, structure: List) {
92 | root.mkdirs()
93 | structure.forEach {
94 | it.mkdirs()
95 | if (it.isDirectory.not() && it.extension.isEmpty().not()) {
96 | it.writeText("")
97 | }
98 | }
99 | }
100 |
101 | override fun createActions(): Array {
102 | return arrayOf(
103 | DialogWrapperExitAction(
104 | "Okay",
105 | 2
106 | )
107 | )
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/data/File.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.data
2 |
3 | interface File {
4 | val name: String
5 | val absolutePath: String
6 | val isDirectory: Boolean
7 | val children: List
8 | val hasChildren: Boolean
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/data/ToProjectFile.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.data
2 |
3 | fun java.io.File.toProjectFile(): File = object : File {
4 | override val name: String
5 | get() = this@toProjectFile.name
6 |
7 | override val absolutePath: String
8 | get() = this@toProjectFile.absolutePath
9 |
10 | override val isDirectory: Boolean
11 | get() = this@toProjectFile.isDirectory
12 |
13 | override val children: List
14 | get() = this@toProjectFile
15 | .listFiles { _, name -> !name.startsWith(".") }
16 | .orEmpty()
17 | .map { it.toProjectFile() }
18 |
19 | private val numberOfFiles
20 | get() = listFiles()?.size ?: 0
21 |
22 | override val hasChildren: Boolean
23 | get() = isDirectory && numberOfFiles > 0
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/data/analytics/ModuleCreationAnalytics.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.data.analytics
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModuleCreationAnalytics(
7 | val moduleType: String,
8 | val threeModule: Boolean,
9 | val addGitIgnore: Boolean,
10 | val addReadme: Boolean,
11 | val gradleNameToFollow: Boolean,
12 | val useKts: Boolean
13 | )
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/data/analytics/ModuleCreationErrorAnalytics.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.data.analytics
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModuleCreationErrorAnalytics(
7 | val message: String
8 | )
9 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/file/FileWriter.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.file
2 |
3 | import com.joetr.modulemaker.ANDROID
4 | import com.joetr.modulemaker.MULTIPLATFORM
5 | import com.joetr.modulemaker.persistence.PreferenceService
6 | import com.joetr.modulemaker.template.GitIgnoreTemplate
7 | import com.joetr.modulemaker.template.TemplateWriter
8 | import java.io.File
9 | import java.io.Writer
10 | import java.nio.file.Files
11 | import java.nio.file.Paths
12 | import kotlin.io.path.absolutePathString
13 |
14 | const val ANDROID_KEY = "android"
15 | const val KOTLIN_KEY = "kotlin"
16 | const val GLUE_KEY = "glue"
17 | const val API_KEY = "api"
18 | const val IMPL_KEY = "key"
19 |
20 | /**
21 | * This class is responsible for writing files into the project
22 | */
23 | class FileWriter(
24 | private val preferenceService: PreferenceService
25 | ) {
26 |
27 | private val templateWriter = TemplateWriter(
28 | preferenceService = preferenceService
29 | )
30 |
31 | fun createModule(
32 | settingsGradleFile: File,
33 | workingDirectory: File,
34 | modulePathAsString: String,
35 | moduleType: String,
36 | showErrorDialog: (String) -> Unit,
37 | showSuccessDialog: () -> Unit,
38 | enhancedModuleCreationStrategy: Boolean,
39 | useKtsBuildFile: Boolean,
40 | gradleFileFollowModule: Boolean,
41 | packageName: String,
42 | addReadme: Boolean,
43 | addGitIgnore: Boolean,
44 | rootPathString: String,
45 | previewMode: Boolean = false,
46 | platformType: String = ANDROID,
47 | sourceSets: List = emptyList()
48 | ): List {
49 | val filesCreated = mutableListOf()
50 |
51 | val fileReady = modulePathAsString.replace(":", "/")
52 |
53 | val path = Paths.get(workingDirectory.toURI())
54 | val modulePath = Paths.get(path.toString(), fileReady)
55 | val moduleFile = File(modulePath.absolutePathString())
56 |
57 | // get the actual module name, not the path. at this point, it will be something like :experiences:foo
58 | val moduleName = modulePathAsString.split(":").last()
59 |
60 | if (moduleName.isEmpty()) {
61 | // display alert
62 | showErrorDialog("Module name empty / not as expected (is it formatted as :module?)")
63 | return emptyList()
64 | }
65 |
66 | if (previewMode.not()) {
67 | // create if it doesn't exist
68 | moduleFile.mkdirs()
69 |
70 | // add to settings.gradle.kts
71 | addToSettingsAtCorrectLocation(
72 | rootPathAsString = rootPathString,
73 | modulePathAsString = modulePathAsString,
74 | settingsGradleFile = settingsGradleFile,
75 | enhancedModuleCreationStrategy = enhancedModuleCreationStrategy,
76 | showErrorDialog = showErrorDialog
77 | )
78 | }
79 |
80 | if (enhancedModuleCreationStrategy) {
81 | filesCreated += createEnhancedModuleStructure(
82 | moduleFile = moduleFile,
83 | moduleType = moduleType,
84 | useKtsBuildFile = useKtsBuildFile,
85 | gradleFileFollowModule = gradleFileFollowModule,
86 | packageName = packageName,
87 | addReadme = addReadme,
88 | addGitIgnore = addGitIgnore,
89 | previewMode = previewMode,
90 | platformType = platformType,
91 | sourceSets = sourceSets
92 | )
93 | } else {
94 | filesCreated += createDefaultModuleStructure(
95 | moduleFile = moduleFile,
96 | moduleName = moduleName,
97 | moduleType = moduleType,
98 | useKtsBuildFile = useKtsBuildFile,
99 | gradleFileFollowModule = gradleFileFollowModule,
100 | packageName = packageName,
101 | addReadme = addReadme,
102 | addGitIgnore = addGitIgnore,
103 | previewMode = previewMode,
104 | platformType = platformType,
105 | sourceSets = sourceSets
106 | )
107 | }
108 |
109 | if (previewMode.not()) {
110 | showSuccessDialog()
111 | }
112 |
113 | return filesCreated
114 | }
115 |
116 | private fun createEnhancedModuleStructure(
117 | moduleFile: File,
118 | moduleType: String,
119 | useKtsBuildFile: Boolean,
120 | gradleFileFollowModule: Boolean,
121 | packageName: String,
122 | addReadme: Boolean,
123 | addGitIgnore: Boolean,
124 | previewMode: Boolean,
125 | platformType: String,
126 | sourceSets: List
127 | ): List {
128 | val filesCreated = mutableListOf()
129 |
130 | // make the 3 module
131 | moduleFile.toPath().resolve(preferenceService.preferenceState.glueModuleName).toFile().apply {
132 | if (previewMode.not()) {
133 | mkdirs()
134 | }
135 | // create the gradle file
136 | filesCreated += templateWriter.createGradleFile(
137 | moduleFile = this,
138 | moduleName = moduleFile.path.split(File.separator).toList().last().plus("-")
139 | .plus(preferenceService.preferenceState.glueModuleName),
140 | moduleType = moduleType,
141 | useKtsBuildFile = useKtsBuildFile,
142 | defaultKey = GLUE_KEY,
143 | gradleFileFollowModule = gradleFileFollowModule,
144 | packageName = packageName.plus(".${preferenceService.preferenceState.glueModuleName}"),
145 | previewMode = previewMode,
146 | platformType = platformType
147 | )
148 |
149 | // create default packages
150 | filesCreated += createDefaultPackages(
151 | moduleFile = this,
152 | packageName = packageName.plus(".${preferenceService.preferenceState.glueModuleName}"),
153 | previewMode = previewMode,
154 | platformType = platformType,
155 | sourceSets = sourceSets
156 | )
157 |
158 | if (addGitIgnore) {
159 | filesCreated += createGitIgnore(
160 | moduleFile = this,
161 | previewMode = previewMode
162 | )
163 | }
164 | }
165 |
166 | moduleFile.toPath().resolve(preferenceService.preferenceState.implModuleName).toFile().apply {
167 | if (previewMode.not()) {
168 | mkdirs()
169 | }
170 | filesCreated += templateWriter.createGradleFile(
171 | moduleFile = this,
172 | moduleName = moduleFile.path.split(File.separator).toList().last().plus("-")
173 | .plus(preferenceService.preferenceState.implModuleName),
174 | moduleType = moduleType,
175 | useKtsBuildFile = useKtsBuildFile,
176 | defaultKey = IMPL_KEY,
177 | gradleFileFollowModule = gradleFileFollowModule,
178 | packageName = packageName.plus(".${preferenceService.preferenceState.implModuleName}"),
179 | previewMode = previewMode,
180 | platformType = platformType
181 | )
182 |
183 | // create default packages
184 | filesCreated += createDefaultPackages(
185 | moduleFile = this,
186 | packageName = packageName.plus(".${preferenceService.preferenceState.implModuleName}"),
187 | previewMode = previewMode,
188 | platformType = platformType,
189 | sourceSets = sourceSets
190 | )
191 |
192 | if (addGitIgnore) {
193 | filesCreated += createGitIgnore(
194 | moduleFile = this,
195 | previewMode = previewMode
196 | )
197 | }
198 | }
199 |
200 | moduleFile.toPath().resolve(preferenceService.preferenceState.apiModuleName).toFile().apply {
201 | if (previewMode.not()) {
202 | mkdirs()
203 | }
204 | filesCreated += templateWriter.createGradleFile(
205 | moduleFile = this,
206 | moduleName = moduleFile.path.split(File.separator).toList().last().plus("-")
207 | .plus(preferenceService.preferenceState.apiModuleName),
208 | moduleType = moduleType,
209 | useKtsBuildFile = useKtsBuildFile,
210 | defaultKey = API_KEY,
211 | gradleFileFollowModule = gradleFileFollowModule,
212 | packageName = packageName.plus(".${preferenceService.preferenceState.apiModuleName}"),
213 | previewMode = previewMode,
214 | platformType = platformType
215 | )
216 |
217 | if (addReadme) {
218 | // create readme file for the api module
219 | filesCreated += templateWriter.createReadmeFile(
220 | moduleFile = this,
221 | moduleName = preferenceService.preferenceState.apiModuleName,
222 | previewMode = previewMode
223 | )
224 | }
225 |
226 | // create default packages
227 | filesCreated += createDefaultPackages(
228 | moduleFile = this,
229 | packageName = packageName.plus(".${preferenceService.preferenceState.apiModuleName}"),
230 | previewMode = previewMode,
231 | platformType = platformType,
232 | sourceSets = sourceSets
233 | )
234 |
235 | if (addGitIgnore) {
236 | filesCreated += createGitIgnore(
237 | moduleFile = this,
238 | previewMode = previewMode
239 | )
240 | }
241 | }
242 |
243 | return filesCreated
244 | }
245 |
246 | private fun createDefaultModuleStructure(
247 | moduleFile: File,
248 | moduleName: String,
249 | moduleType: String,
250 | useKtsBuildFile: Boolean,
251 | gradleFileFollowModule: Boolean,
252 | packageName: String,
253 | addReadme: Boolean,
254 | addGitIgnore: Boolean,
255 | previewMode: Boolean,
256 | platformType: String,
257 | sourceSets: List
258 | ): List {
259 | val filesCreated = mutableListOf()
260 |
261 | // create gradle files
262 | filesCreated += templateWriter.createGradleFile(
263 | moduleFile = moduleFile,
264 | moduleName = moduleName,
265 | moduleType = moduleType,
266 | useKtsBuildFile = useKtsBuildFile,
267 | defaultKey = null,
268 | gradleFileFollowModule = gradleFileFollowModule,
269 | packageName = packageName,
270 | previewMode = previewMode,
271 | platformType = platformType
272 | )
273 |
274 | if (addReadme) {
275 | // create readme file
276 | filesCreated += templateWriter.createReadmeFile(
277 | moduleFile = moduleFile,
278 | moduleName = moduleName,
279 | previewMode = previewMode
280 | )
281 | }
282 |
283 | // create default packages
284 | filesCreated += createDefaultPackages(
285 | moduleFile = moduleFile,
286 | packageName = packageName,
287 | previewMode = previewMode,
288 | platformType = platformType,
289 | sourceSets = sourceSets
290 | )
291 |
292 | if (addGitIgnore) {
293 | filesCreated += createGitIgnore(
294 | moduleFile = moduleFile,
295 | previewMode = previewMode
296 | )
297 | }
298 |
299 | return filesCreated
300 | }
301 |
302 | private fun createGitIgnore(moduleFile: File, previewMode: Boolean): List {
303 | val gitignoreFile = Paths.get(moduleFile.absolutePath).toFile()
304 |
305 | val filePath = Paths.get(gitignoreFile.absolutePath, ".gitignore").toFile()
306 |
307 | if (previewMode.not()) {
308 | val writer: Writer = java.io.FileWriter(filePath)
309 |
310 | val customPreferences = preferenceService.preferenceState.gitignoreTemplate
311 | val dataToWrite = customPreferences.ifEmpty {
312 | GitIgnoreTemplate.data
313 | }
314 |
315 | writer.write(dataToWrite)
316 | writer.flush()
317 | writer.close()
318 | }
319 |
320 | return listOf(filePath)
321 | }
322 |
323 | /**
324 | * Creates the default package name
325 | *
326 | * Gives the module a src/main/kotlin folder with com. name
327 | */
328 | private fun createDefaultPackages(
329 | moduleFile: File,
330 | packageName: String,
331 | previewMode: Boolean,
332 | platformType: String,
333 | sourceSets: List
334 | ): List {
335 | fun makePath(srcPath: File): File {
336 | val packagePath = Paths.get(srcPath.path, packageName.split(".").joinToString(File.separator)).toFile()
337 | // create default package
338 | val stringBuilder = StringBuilder()
339 | val filePath = Paths.get(srcPath.absolutePath, stringBuilder.toString()).toFile()
340 | if (previewMode.not()) {
341 | packagePath.mkdirs()
342 | filePath.mkdirs()
343 | }
344 | return packagePath
345 | }
346 | // create src/main
347 | val packagePaths = if (platformType == ANDROID) {
348 | val srcPath = Paths.get(moduleFile.absolutePath, "src/main/kotlin").toFile()
349 | val packagePath = makePath(srcPath)
350 | listOf(packagePath)
351 | } else if (platformType == MULTIPLATFORM) {
352 | val paths = mutableListOf()
353 | sourceSets.forEach {
354 | val srcPath = Paths.get(moduleFile.absolutePath, "src/$it/kotlin").toFile()
355 | val packagePath = makePath(srcPath)
356 | paths.add(packagePath)
357 | }
358 | paths
359 | } else {
360 | throw IllegalArgumentException("Unknown platform type $platformType")
361 | }
362 |
363 | return packagePaths
364 | }
365 |
366 | /**
367 | * Inserts the entry into settings.gradle.kts at the correct spot to maintain alphabetical order
368 | *
369 | * This assumes the file was in alphabetical order to begin with
370 | */
371 | private fun addToSettingsAtCorrectLocation(
372 | settingsGradleFile: File,
373 | modulePathAsString: String,
374 | enhancedModuleCreationStrategy: Boolean,
375 | showErrorDialog: (String) -> Unit,
376 | rootPathAsString: String
377 | ) {
378 | val settingsFile = Files.readAllLines(Paths.get(settingsGradleFile.toURI()))
379 |
380 | // if the user has non-empty include keyword set, only check for that.
381 | val includeKeywords = if (preferenceService.preferenceState.includeProjectKeyword.isNotEmpty()) {
382 | listOf(preferenceService.preferenceState.includeProjectKeyword)
383 | } else {
384 | listOf(
385 | "includeProject",
386 | "includeBuild",
387 | "include"
388 | )
389 | }
390 |
391 | val twoParametersPattern = """\(".+", ".+"\)""".toRegex()
392 |
393 | val lastNonEmptyLineInSettingsGradleFile = settingsFile.last { settingsFileLine ->
394 | settingsFileLine.isNotEmpty() && includeKeywords.any {
395 | settingsFileLine.contains(it)
396 | }
397 | }
398 | val projectIncludeKeyword = includeKeywords.firstOrNull { includeKeyword ->
399 | lastNonEmptyLineInSettingsGradleFile.contains(includeKeyword)
400 | }
401 |
402 | if (projectIncludeKeyword == null) {
403 | showErrorDialog("Could not find any include statements in settings.gradle(.kts) file")
404 | return
405 | }
406 |
407 | val usesTwoParameters = settingsFile.any { line ->
408 | twoParametersPattern.containsMatchIn(line)
409 | }
410 |
411 | // get the last line numbers for an include statement
412 | val lastLineNumberOfFirstIncludeProjectStatement = settingsFile.indexOfLast {
413 | settingsFileContainsSpecialIncludeKeyword(it, projectIncludeKeyword)
414 | }
415 |
416 | // traverse backwards from there to find the first instance
417 | var tempIndexForSettingsFile = lastLineNumberOfFirstIncludeProjectStatement
418 | while (tempIndexForSettingsFile >= 0) {
419 | val currentLine = settingsFile[tempIndexForSettingsFile]
420 | if (currentLine.trim().isEmpty() || settingsFileContainsSpecialIncludeKeyword(
421 | currentLine,
422 | projectIncludeKeyword
423 | )
424 | ) {
425 | tempIndexForSettingsFile--
426 | } else {
427 | break
428 | }
429 | }
430 |
431 | // assume tempIndexForSettingsFile is the first line
432 | val firstLineNumberOfFirstIncludeProjectStatement = tempIndexForSettingsFile + 1
433 |
434 | if (firstLineNumberOfFirstIncludeProjectStatement <= 0) {
435 | showErrorDialog("Could not find any include statements in settings.gradle(.kts) file")
436 | return
437 | }
438 |
439 | // sub list them and create a new list so we aren't modifying the original
440 | val includeProjectStatements = settingsFile.subList(
441 | firstLineNumberOfFirstIncludeProjectStatement,
442 | lastLineNumberOfFirstIncludeProjectStatement + 1
443 | )
444 | .filter {
445 | it.isNotEmpty()
446 | }
447 | .toMutableList()
448 |
449 | val textToWrite = constructTextToWrite(
450 | enhancedModuleCreationStrategy = enhancedModuleCreationStrategy,
451 | usesTwoParameters = usesTwoParameters,
452 | projectIncludeKeyword = projectIncludeKeyword,
453 | modulePathAsString = modulePathAsString,
454 | rootPathAsString = rootPathAsString
455 | )
456 |
457 | // the spot we want to insert it is the first line we find that is after it alphabetically
458 | val insertionIndex = includeProjectStatements.indexOfFirst {
459 | it.isNotEmpty() && it.lowercase() >= textToWrite.lowercase()
460 | }
461 |
462 | if (insertionIndex < 0) {
463 | /**
464 | * IN a scenario where there is just a single include statement, we want to differentiate between the two scenarios:
465 | *
466 | * include(":app") for example and
467 | * include(
468 | * ":app",
469 | * ":module1",
470 | * ) etc
471 | *
472 | * in the former case, we want to add a 1 offset to insert the new module after the single module
473 | * in the latter case, we don't really support that, but we also don't want to add it after the include statement to break
474 | * the current include, so we insert it just before
475 | */
476 | val offsetAmount = if (includeProjectStatements.size == 1 && includeProjectStatements.first()
477 | .doesNotContainModule(projectIncludeKeyword)
478 | ) {
479 | 0
480 | } else {
481 | 1
482 | }
483 | // insert it at the end as nothing is past it
484 | settingsFile.add(lastLineNumberOfFirstIncludeProjectStatement + offsetAmount, textToWrite)
485 | } else {
486 | // insert it in our original list adding the original offset of the first line
487 | settingsFile.add(insertionIndex + firstLineNumberOfFirstIncludeProjectStatement, textToWrite)
488 | }
489 |
490 | Files.write(Paths.get(settingsGradleFile.toURI()), settingsFile)
491 | }
492 |
493 | private fun settingsFileContainsSpecialIncludeKeyword(
494 | stringToCheck: String,
495 | projectIncludeKeyword: String
496 | ): Boolean {
497 | return stringToCheck.contains("$projectIncludeKeyword(\"") ||
498 | stringToCheck.contains("$projectIncludeKeyword('") ||
499 | stringToCheck.contains("$projectIncludeKeyword(") ||
500 | stringToCheck.contains("$projectIncludeKeyword \"") ||
501 | stringToCheck.contains("$projectIncludeKeyword '")
502 | }
503 |
504 | private fun constructTextToWrite(
505 | enhancedModuleCreationStrategy: Boolean,
506 | usesTwoParameters: Boolean,
507 | projectIncludeKeyword: String,
508 | modulePathAsString: String,
509 | rootPathAsString: String
510 | ): String {
511 | fun buildText(path: String): String {
512 | val parametersString = if (usesTwoParameters) {
513 | val filePath = "$rootPathAsString${path.replace(":", File.separator)}".removePrefix("/")
514 | "\"$path\", \"$filePath\""
515 | } else {
516 | "\"$path\""
517 | }
518 | return "$projectIncludeKeyword($parametersString)"
519 | }
520 |
521 | return if (enhancedModuleCreationStrategy) {
522 | val paths = arrayOf(
523 | "$modulePathAsString:${preferenceService.preferenceState.apiModuleName}",
524 | "$modulePathAsString:${preferenceService.preferenceState.implModuleName}",
525 | "$modulePathAsString:${preferenceService.preferenceState.glueModuleName}"
526 | )
527 | paths.joinToString("\n") { buildText(it) }
528 | } else {
529 | buildText(modulePathAsString)
530 | }
531 | }
532 | }
533 |
534 | private fun String.doesNotContainModule(includeKeyword: String): Boolean {
535 | return this.replace(" ", "").replace("(", "") == includeKeyword
536 | }
537 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/persistence/PreferenceService.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.persistence
2 |
3 | interface PreferenceService {
4 | var preferenceState: PreferenceServiceImpl.Companion.State
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/persistence/PreferenceServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.persistence
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.components.PersistentStateComponent
5 | import com.intellij.openapi.components.State
6 | import com.intellij.openapi.components.Storage
7 | import com.intellij.util.xmlb.XmlSerializerUtil.copyBean
8 | import com.joetr.modulemaker.DEFAULT_ADD_GIT_IGNORE
9 | import com.joetr.modulemaker.DEFAULT_ADD_README
10 | import com.joetr.modulemaker.DEFAULT_API_MODULE_NAME
11 | import com.joetr.modulemaker.DEFAULT_BASE_PACKAGE_NAME
12 | import com.joetr.modulemaker.DEFAULT_GLUE_MODULE_NAME
13 | import com.joetr.modulemaker.DEFAULT_GRADLE_FILE_NAMED_AFTER_MODULE
14 | import com.joetr.modulemaker.DEFAULT_IMPL_MODULE_NAME
15 | import com.joetr.modulemaker.DEFAULT_INCLUDE_KEYWORD
16 | import com.joetr.modulemaker.DEFAULT_REFRESH_ON_MODULE_ADD
17 | import com.joetr.modulemaker.DEFAULT_THREE_MODULE_CREATION
18 | import com.joetr.modulemaker.DEFAULT_USE_KTS_FILE_EXTENSION
19 | import kotlinx.serialization.Serializable
20 | import org.jetbrains.annotations.Nullable
21 |
22 | @State(name = "PreferenceService", storages = [(Storage("module_maker_preferences.xml"))])
23 | class PreferenceServiceImpl : PersistentStateComponent, PreferenceService {
24 |
25 | private var state = State()
26 |
27 | override var preferenceState: State
28 | get() = this.state
29 | set(value) {
30 | this.state = value
31 | }
32 |
33 | @Nullable
34 | override fun getState(): State {
35 | return this.preferenceState
36 | }
37 |
38 | override fun loadState(from: State) {
39 | copyBean(from, this.preferenceState)
40 | }
41 |
42 | companion object {
43 |
44 | @Serializable
45 | data class State(
46 | var androidTemplate: String = "",
47 | var kotlinTemplate: String = "",
48 | var multiplatformTemplate: String = "",
49 | var apiTemplate: String = "",
50 | var apiModuleName: String = DEFAULT_API_MODULE_NAME,
51 | var glueTemplate: String = "",
52 | var glueModuleName: String = DEFAULT_GLUE_MODULE_NAME,
53 | var implTemplate: String = "",
54 | var implModuleName: String = DEFAULT_IMPL_MODULE_NAME,
55 | var gitignoreTemplate: String = "",
56 | var packageName: String = DEFAULT_BASE_PACKAGE_NAME,
57 | var includeProjectKeyword: String = DEFAULT_INCLUDE_KEYWORD,
58 | var refreshOnModuleAdd: Boolean = DEFAULT_REFRESH_ON_MODULE_ADD,
59 | var threeModuleCreationDefault: Boolean = DEFAULT_THREE_MODULE_CREATION,
60 | var useKtsFileExtension: Boolean = DEFAULT_USE_KTS_FILE_EXTENSION,
61 | var gradleFileNamedAfterModule: Boolean = DEFAULT_GRADLE_FILE_NAMED_AFTER_MODULE,
62 | var addReadme: Boolean = DEFAULT_ADD_README,
63 | var addGitIgnore: Boolean = DEFAULT_ADD_GIT_IGNORE
64 | )
65 |
66 | @JvmStatic
67 | val instance: PreferenceServiceImpl
68 | get() = ApplicationManager.getApplication().getService(PreferenceServiceImpl::class.java)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/AndroidModuleKtsTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object AndroidModuleKtsTemplate {
4 | val data = """
5 | plugins {
6 | id("com.android.library")
7 | }
8 |
9 | android {
10 | namespace = "${'$'}{packageName}"
11 | }
12 |
13 | dependencies {
14 |
15 | }
16 | """.trimIndent()
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/AndroidModuleTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object AndroidModuleTemplate {
4 | val data = """
5 | apply plugin: "com.android.library"
6 | apply plugin: "kotlin-android"
7 |
8 | android {
9 | namespace = "${'$'}{packageName}"
10 | }
11 |
12 | dependencies {
13 |
14 | }
15 | """.trimIndent()
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/GitIgnoreTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object GitIgnoreTemplate {
4 | val data = """
5 | # Gradle files
6 | .gradle/
7 | build/
8 |
9 | # Local configuration file (sdk path, etc)
10 | local.properties
11 |
12 | # Log/OS Files
13 | *.log
14 |
15 | # Android Studio generated files and folders
16 | captures/
17 | .externalNativeBuild/
18 | .cxx/
19 | *.apk
20 | output.json
21 |
22 | # IntelliJ
23 | *.iml
24 | .idea/
25 | misc.xml
26 | deploymentTargetDropDown.xml
27 | render.experimental.xml
28 |
29 | # Keystore files
30 | *.jks
31 | *.keystore
32 |
33 | # Google Services (e.g. APIs or Firebase)
34 | google-services.json
35 |
36 | # Android Profiling
37 | *.hprof
38 | """.trimIndent()
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/KotlinModuleKtsTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object KotlinModuleKtsTemplate {
4 | val data = """
5 | plugins {
6 | "kotlin"
7 | }
8 |
9 | dependencies {
10 |
11 | }
12 | """.trimIndent()
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/KotlinModuleTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object KotlinModuleTemplate {
4 | val data = """
5 | apply plugin: "kotlin"
6 |
7 | dependencies {
8 |
9 | }
10 | """.trimIndent()
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/ModuleReadMeTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object ModuleReadMeTemplate {
4 | val data = """
5 | # ${'$'}{moduleName}
6 |
7 | TODO
8 | """.trimIndent()
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/MultiplatformKtsTemplate.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | object MultiplatformKtsTemplate {
4 | val data = """
5 | plugins {
6 | kotlin("multiplatform")
7 | kotlin("plugin.compose")
8 | id("com.android.library")
9 | id("org.jetbrains.compose")
10 | }
11 |
12 | version = "1.0-SNAPSHOT"
13 |
14 | kotlin {
15 | androidTarget()
16 | jvm("desktop")
17 | js {
18 | browser()
19 | useEsModules()
20 | }
21 | wasmJs { browser() }
22 |
23 | listOf(
24 | iosX64(),
25 | iosArm64(),
26 | iosSimulatorArm64()
27 | ).forEach { iosTarget ->
28 | iosTarget.binaries.framework {
29 | baseName = "shared"
30 | isStatic = true
31 | }
32 | }
33 |
34 | applyDefaultHierarchyTemplate()
35 |
36 | sourceSets {
37 | all {
38 | languageSettings {
39 | optIn("org.jetbrains.compose.resources.ExperimentalResourceApi")
40 | }
41 | }
42 |
43 | commonMain.dependencies {
44 | implementation(compose.runtime)
45 | implementation(compose.foundation)
46 | implementation(compose.material)
47 | implementation(compose.components.resources)
48 | }
49 |
50 | androidMain.dependencies {
51 |
52 | }
53 |
54 | val jsWasmMain by creating {
55 | dependsOn(commonMain.get())
56 | }
57 |
58 | val jsMain by getting {
59 | dependsOn(jsWasmMain)
60 | }
61 |
62 | val wasmJsMain by getting {
63 | dependsOn(jsWasmMain)
64 | }
65 |
66 | val desktopMain by getting
67 | desktopMain.dependencies {
68 | implementation(compose.desktop.common)
69 |
70 | }
71 | val desktopTest by getting
72 | desktopTest.dependencies {
73 | implementation(compose.desktop.currentOs)
74 | implementation(compose.desktop.uiTestJUnit4)
75 | }
76 | }
77 | }
78 | """.trimIndent()
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/TemplateVariable.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | enum class TemplateVariable(val templateVariable: String) {
4 | PACKAGE_NAME(
5 | """
6 | "${'$'}{packageName}"
7 | """.trimIndent()
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/template/TemplateWriter.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.template
2 |
3 | import com.joetr.modulemaker.ANDROID
4 | import com.joetr.modulemaker.FREEMARKER_VERSION
5 | import com.joetr.modulemaker.KOTLIN
6 | import com.joetr.modulemaker.MULTIPLATFORM
7 | import com.joetr.modulemaker.file.ANDROID_KEY
8 | import com.joetr.modulemaker.file.API_KEY
9 | import com.joetr.modulemaker.file.GLUE_KEY
10 | import com.joetr.modulemaker.file.IMPL_KEY
11 | import com.joetr.modulemaker.file.KOTLIN_KEY
12 | import com.joetr.modulemaker.persistence.PreferenceService
13 | import freemarker.template.Configuration
14 | import freemarker.template.Template
15 | import freemarker.template.TemplateException
16 | import java.io.File
17 | import java.io.FileWriter
18 | import java.io.IOException
19 | import java.io.Writer
20 | import java.nio.file.Paths
21 |
22 | class TemplateWriter(
23 | private val preferenceService: PreferenceService
24 | ) {
25 |
26 | private val cfg = Configuration(FREEMARKER_VERSION).apply {
27 | setClassLoaderForTemplateLoading(TemplateWriter::class.java.classLoader, "")
28 | }
29 |
30 | /**
31 | * Creates gradle file for the module from base gradle template file
32 | */
33 | fun createGradleFile(
34 | moduleFile: File,
35 | moduleName: String,
36 | moduleType: String,
37 | useKtsBuildFile: Boolean,
38 | defaultKey: String?,
39 | gradleFileFollowModule: Boolean,
40 | packageName: String,
41 | previewMode: Boolean,
42 | platformType: String
43 | ): List {
44 | try {
45 | // Build the data-model
46 | val data: MutableMap = HashMap()
47 | data["packageName"] = packageName
48 |
49 | // load gradle file from template folder
50 | val gradleTemplate: Template = when (moduleType) {
51 | KOTLIN -> {
52 | val customPreferences = getPreferenceFromKey(defaultKey, if (platformType == MULTIPLATFORM) MULTIPLATFORM else KOTLIN_KEY)
53 | if (customPreferences.isNotEmpty()) {
54 | Template(
55 | null,
56 | customPreferences,
57 | cfg
58 | )
59 | } else {
60 | val template = if (useKtsBuildFile) {
61 | KotlinModuleKtsTemplate.data
62 | } else {
63 | KotlinModuleTemplate.data
64 | }
65 | Template(
66 | null,
67 | template,
68 | cfg
69 | )
70 | }
71 | }
72 | ANDROID -> {
73 | val customPreferences = getPreferenceFromKey(defaultKey, if (platformType == MULTIPLATFORM) MULTIPLATFORM else ANDROID_KEY)
74 |
75 | if (customPreferences.isNotEmpty()) {
76 | Template(
77 | null,
78 | customPreferences,
79 | cfg
80 | )
81 | } else {
82 | val template = if (platformType == ANDROID) {
83 | if (useKtsBuildFile) {
84 | AndroidModuleKtsTemplate.data
85 | } else {
86 | AndroidModuleTemplate.data
87 | }
88 | } else if (platformType == MULTIPLATFORM) {
89 | MultiplatformKtsTemplate.data
90 | } else {
91 | throw IllegalArgumentException("Unknown platform type $platformType")
92 | }
93 | Template(
94 | null,
95 | template,
96 | cfg
97 | )
98 | }
99 | }
100 | else -> throw IllegalArgumentException("Unknown module type")
101 | }
102 |
103 | // File output
104 | val extension = if (useKtsBuildFile) {
105 | ".gradle.kts"
106 | } else {
107 | ".gradle"
108 | }
109 | val fileName = if (gradleFileFollowModule) {
110 | moduleName.plus(extension)
111 | } else {
112 | "build".plus(extension)
113 | }
114 |
115 | val filePath = Paths.get(moduleFile.absolutePath, fileName).toFile()
116 |
117 | if (previewMode.not()) {
118 | val file: Writer = FileWriter(Paths.get(moduleFile.absolutePath, fileName).toFile())
119 | gradleTemplate.process(data, file)
120 | file.flush()
121 | file.close()
122 | }
123 |
124 | return listOf(filePath)
125 | } catch (e: IOException) {
126 | e.printStackTrace()
127 | } catch (e: TemplateException) {
128 | e.printStackTrace()
129 | }
130 |
131 | return emptyList()
132 | }
133 |
134 | fun createReadmeFile(moduleFile: File, moduleName: String, previewMode: Boolean): List {
135 | try {
136 | val manifestTemplate = Template(
137 | null,
138 | ModuleReadMeTemplate.data,
139 | cfg
140 | )
141 |
142 | val data: MutableMap = HashMap()
143 |
144 | data["moduleName"] = moduleName
145 |
146 | // create directory for the readme
147 | val manifestFile = Paths.get(moduleFile.absolutePath).toFile()
148 |
149 | val filePath = Paths.get(manifestFile.absolutePath, "README.md").toFile()
150 |
151 | if (previewMode.not()) {
152 | manifestFile.mkdirs()
153 |
154 | // File output
155 | val file: Writer = FileWriter(filePath)
156 | manifestTemplate.process(data, file)
157 | file.flush()
158 | file.close()
159 | }
160 |
161 | return listOf(filePath)
162 | } catch (e: IOException) {
163 | e.printStackTrace()
164 | } catch (e: TemplateException) {
165 | e.printStackTrace()
166 | }
167 |
168 | return emptyList()
169 | }
170 |
171 | private fun getPreferenceFromKey(key: String?, fallback: String): String {
172 | return when (key ?: fallback) {
173 | IMPL_KEY -> preferenceService.preferenceState.implTemplate
174 | API_KEY -> preferenceService.preferenceState.apiTemplate
175 | GLUE_KEY -> preferenceService.preferenceState.glueTemplate
176 | ANDROID_KEY -> preferenceService.preferenceState.androidTemplate
177 | MULTIPLATFORM -> preferenceService.preferenceState.multiplatformTemplate
178 | KOTLIN_KEY -> preferenceService.preferenceState.kotlinTemplate
179 | else -> ""
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/LabelledCheckbox.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material.Checkbox
8 | import androidx.compose.material.CheckboxDefaults
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.unit.dp
15 |
16 | @Composable
17 | fun LabelledCheckbox(
18 | modifier: Modifier = Modifier,
19 | label: String,
20 | checked: Boolean,
21 | onCheckedChange: (Boolean) -> Unit
22 | ) {
23 | Row(
24 | modifier = modifier.clickable {
25 | onCheckedChange(checked.not())
26 | }.padding(end = 8.dp),
27 | horizontalArrangement = Arrangement.Start,
28 | verticalAlignment = Alignment.CenterVertically
29 | ) {
30 | Checkbox(
31 | checked = checked,
32 | onCheckedChange = {
33 | onCheckedChange(it)
34 | },
35 | enabled = true,
36 | colors = CheckboxDefaults.colors(checkedColor = MaterialTheme.colors.primary, uncheckedColor = MaterialTheme.colors.primaryVariant)
37 | )
38 | Text(text = label)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/file/FileTree.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.file
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import com.joetr.modulemaker.data.File
7 |
8 | class ExpandableFile(
9 | val file: File,
10 | val level: Int
11 | ) {
12 | var children: List by mutableStateOf(emptyList())
13 | val canExpand: Boolean get() = file.hasChildren
14 |
15 | fun toggleExpanded() {
16 | children = if (children.isEmpty()) {
17 | file.children
18 | .map { ExpandableFile(it, level + 1) }
19 | .sortedWith(compareBy({ it.file.isDirectory }, { it.file.name }))
20 | .sortedBy { !it.file.isDirectory }
21 | } else {
22 | emptyList()
23 | }
24 | }
25 | }
26 |
27 | class FileTree(root: File) {
28 |
29 | private val expandableRoot = ExpandableFile(root, 0).apply {
30 | toggleExpanded()
31 | }
32 |
33 | val items: List- get() = expandableRoot.toItems()
34 |
35 | inner class Item(
36 | internal val file: ExpandableFile
37 | ) {
38 | val name: String get() = file.file.name
39 |
40 | val level: Int get() = file.level
41 |
42 | val type: ItemType
43 | get() = if (file.file.isDirectory) {
44 | ItemType.Folder(isExpanded = file.children.isNotEmpty(), canExpand = file.canExpand)
45 | } else {
46 | ItemType.File(ext = file.file.name.substringAfterLast(".").lowercase())
47 | }
48 |
49 | fun open() = when (type) {
50 | is ItemType.Folder -> file.toggleExpanded()
51 | is ItemType.File -> Unit
52 | }
53 | }
54 |
55 | sealed class ItemType {
56 | class Folder(val isExpanded: Boolean, val canExpand: Boolean) : ItemType()
57 | class File(val ext: String) : ItemType()
58 | }
59 |
60 | private fun ExpandableFile.toItems(): List
- {
61 | fun ExpandableFile.addTo(list: MutableList
- ) {
62 | list.add(Item(this))
63 | for (child in children) {
64 | child.addTo(list)
65 | }
66 | }
67 |
68 | val list = mutableListOf
- ()
69 | addTo(list)
70 | return list
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/file/FileTreeView.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.file
2 |
3 | import androidx.compose.foundation.HorizontalScrollbar
4 | import androidx.compose.foundation.VerticalScrollbar
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.horizontalScroll
7 | import androidx.compose.foundation.hoverable
8 | import androidx.compose.foundation.interaction.MutableInteractionSource
9 | import androidx.compose.foundation.interaction.collectIsHoveredAsState
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.Row
12 | import androidx.compose.foundation.layout.fillMaxSize
13 | import androidx.compose.foundation.layout.fillMaxWidth
14 | import androidx.compose.foundation.layout.height
15 | import androidx.compose.foundation.layout.padding
16 | import androidx.compose.foundation.layout.size
17 | import androidx.compose.foundation.layout.wrapContentHeight
18 | import androidx.compose.foundation.lazy.LazyColumn
19 | import androidx.compose.foundation.lazy.rememberLazyListState
20 | import androidx.compose.foundation.rememberScrollState
21 | import androidx.compose.foundation.rememberScrollbarAdapter
22 | import androidx.compose.material.Icon
23 | import androidx.compose.material.LocalContentColor
24 | import androidx.compose.material.Surface
25 | import androidx.compose.material.Text
26 | import androidx.compose.material.icons.Icons
27 | import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
28 | import androidx.compose.material.icons.automirrored.filled.Launch
29 | import androidx.compose.material.icons.automirrored.filled.TextSnippet
30 | import androidx.compose.material.icons.filled.BrokenImage
31 | import androidx.compose.material.icons.filled.Build
32 | import androidx.compose.material.icons.filled.Code
33 | import androidx.compose.material.icons.filled.Description
34 | import androidx.compose.material.icons.filled.KeyboardArrowDown
35 | import androidx.compose.material.icons.filled.KeyboardArrowRight
36 | import androidx.compose.material.icons.filled.Launch
37 | import androidx.compose.material.icons.filled.Settings
38 | import androidx.compose.material.icons.filled.TextSnippet
39 | import androidx.compose.runtime.Composable
40 | import androidx.compose.runtime.getValue
41 | import androidx.compose.runtime.remember
42 | import androidx.compose.ui.Alignment
43 | import androidx.compose.ui.Modifier
44 | import androidx.compose.ui.draw.clipToBounds
45 | import androidx.compose.ui.graphics.Color
46 | import androidx.compose.ui.platform.LocalDensity
47 | import androidx.compose.ui.text.style.TextOverflow
48 | import androidx.compose.ui.unit.Dp
49 | import androidx.compose.ui.unit.TextUnit
50 | import androidx.compose.ui.unit.dp
51 | import androidx.compose.ui.unit.sp
52 |
53 | @Composable
54 | fun FileTreeView(model: FileTree, height: Dp, onClick: (ExpandableFile) -> Unit, modifier: Modifier) = Surface(
55 | modifier = modifier.height(height)
56 | ) {
57 | with(LocalDensity.current) {
58 | Box {
59 | val lazyListState = rememberLazyListState()
60 | val scrollState = rememberScrollState()
61 |
62 | LazyColumn(
63 | modifier = Modifier.fillMaxSize().horizontalScroll(scrollState),
64 | state = lazyListState
65 | ) {
66 | items(model.items.size) {
67 | FileTreeItemView(
68 | 14.sp,
69 | 14.sp.toDp() * 1.5f,
70 | model.items[it],
71 | onClick = onClick,
72 | // if it's the last one and the scrollbar is showing
73 | showBottomPadding = it == model.items.size - 1 && (lazyListState.canScrollForward || lazyListState.canScrollBackward),
74 | // if the scrollbar is showing
75 | showEndPadding = scrollState.canScrollForward || scrollState.canScrollBackward
76 | )
77 | }
78 | }
79 |
80 | VerticalScrollbar(
81 | rememberScrollbarAdapter(lazyListState),
82 | Modifier.align(Alignment.CenterEnd)
83 | )
84 |
85 | HorizontalScrollbar(
86 | rememberScrollbarAdapter(scrollState),
87 | Modifier.align(Alignment.BottomStart)
88 | )
89 | }
90 | }
91 | }
92 |
93 | @Composable
94 | private fun FileTreeItemView(
95 | fontSize: TextUnit,
96 | height: Dp,
97 | model: FileTree.Item,
98 | onClick: (ExpandableFile) -> Unit,
99 | showBottomPadding: Boolean,
100 | showEndPadding: Boolean
101 | ) =
102 | Row(
103 | modifier = Modifier
104 | .wrapContentHeight()
105 | .clickable {
106 | model.open()
107 |
108 | // let UI know
109 | onClick(model.file)
110 | }
111 | // give padding for scroll bar
112 | .padding(
113 | start = 24.dp * model.level,
114 | end = if (showEndPadding) 8.dp else 0.dp,
115 | bottom = if (showBottomPadding) 8.dp else 0.dp
116 | )
117 | .height(height)
118 | .fillMaxWidth()
119 | ) {
120 | val interactionSource = remember { MutableInteractionSource() }
121 | val active by interactionSource.collectIsHoveredAsState()
122 |
123 | FileItemIcon(Modifier.align(Alignment.CenterVertically), model)
124 | Text(
125 | text = model.name,
126 | color = if (active) LocalContentColor.current.copy(alpha = 0.60f) else LocalContentColor.current,
127 | modifier = Modifier
128 | .align(Alignment.CenterVertically)
129 | .clipToBounds()
130 | .hoverable(interactionSource),
131 | softWrap = true,
132 | fontSize = fontSize,
133 | overflow = TextOverflow.Ellipsis,
134 | maxLines = 1
135 | )
136 | }
137 |
138 | @Composable
139 | private fun FileItemIcon(modifier: Modifier, model: FileTree.Item) = Box(modifier.size(24.dp).padding(4.dp)) {
140 | when (val type = model.type) {
141 | is FileTree.ItemType.Folder -> when {
142 | !type.canExpand -> Unit
143 | type.isExpanded -> Icon(
144 | Icons.Default.KeyboardArrowDown,
145 | contentDescription = null,
146 | tint = LocalContentColor.current
147 | )
148 |
149 | else -> Icon(
150 | Icons.AutoMirrored.Filled.KeyboardArrowRight,
151 | contentDescription = null,
152 | tint = LocalContentColor.current
153 | )
154 | }
155 |
156 | is FileTree.ItemType.File -> when (type.ext) {
157 | in sourceCodeFileExtensions -> Icon(
158 | Icons.Default.Code,
159 | contentDescription = null,
160 | tint = Color(0xFF3E86A0)
161 | )
162 | "txt" -> Icon(Icons.Default.Description, contentDescription = null, tint = Color(0xFF87939A))
163 | "md" -> Icon(Icons.Default.Description, contentDescription = null, tint = Color(0xFF87939A))
164 | "gitignore" -> Icon(
165 | Icons.Default.BrokenImage,
166 | contentDescription = null,
167 | tint = Color(0xFF87939A)
168 | )
169 | "gradle" -> Icon(Icons.Default.Build, contentDescription = null, tint = Color(0xFF87939A))
170 | "kts" -> Icon(Icons.Default.Build, contentDescription = null, tint = Color(0xFF3E86A0))
171 | "properties" -> Icon(
172 | Icons.Default.Settings,
173 | contentDescription = null,
174 | tint = Color(0xFF62B543)
175 | )
176 | "bat" -> Icon(Icons.AutoMirrored.Filled.Launch, contentDescription = null, tint = Color(0xFF87939A))
177 | else -> Icon(Icons.AutoMirrored.Filled.TextSnippet, contentDescription = null, tint = Color(0xFF87939A))
178 | }
179 | }
180 | }
181 |
182 | private val sourceCodeFileExtensions = listOf(
183 | "java",
184 | "kt",
185 | "cpp",
186 | "c",
187 | "h",
188 | "py",
189 | "js",
190 | "html",
191 | "css",
192 | "php",
193 | "rb",
194 | "swift",
195 | "go",
196 | "scala",
197 | "rust",
198 | "dart",
199 | "lua",
200 | "xml",
201 | "pl",
202 | "sh",
203 | "sql"
204 | )
205 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val blue200 = Color(0xff90CAF9)
6 | val blue500 = Color(0xff2196F3)
7 | val blue700 = Color(0xff1976D2)
8 |
9 | val teal200 = Color(0xff80deea)
10 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
12 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.text.TextStyle
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 |
10 | val typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | ),
16 | body2 = TextStyle(
17 | fontFamily = FontFamily.Default,
18 | fontWeight = FontWeight.Normal,
19 | fontSize = 14.sp
20 | ),
21 | button = TextStyle(
22 | fontFamily = FontFamily.Default,
23 | fontWeight = FontWeight.W500,
24 | fontSize = 14.sp
25 | ),
26 | caption = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Normal,
29 | fontSize = 12.sp
30 | ),
31 | subtitle1 = TextStyle(
32 | fontFamily = FontFamily.Default,
33 | fontWeight = FontWeight.Normal,
34 | fontSize = 16.sp,
35 | color = Color.Gray
36 | ),
37 | subtitle2 = TextStyle(
38 | fontFamily = FontFamily.Default,
39 | fontWeight = FontWeight.Normal,
40 | fontSize = 14.sp,
41 | color = Color.Gray
42 | )
43 | )
44 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/WidgetTheme.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme
2 |
3 | import androidx.compose.material.MaterialTheme
4 | import androidx.compose.material.Surface
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.graphics.Color
9 | import com.joetr.modulemaker.ui.theme.intellij.SwingColor
10 | import kotlinx.serialization.json.JsonNull.content
11 |
12 | private val DarkGreenColorPalette = darkColors(
13 | primary = blue200,
14 | primaryVariant = blue700,
15 | secondary = teal200,
16 | onPrimary = Color.Black,
17 | onSecondary = Color.White,
18 | error = Color.Red
19 | )
20 |
21 | private val LightGreenColorPalette = lightColors(
22 | primary = blue500,
23 | primaryVariant = blue700,
24 | secondary = teal200,
25 | onPrimary = Color.White,
26 | onSurface = Color.Black
27 | )
28 |
29 | @Composable
30 | fun WidgetTheme(
31 | darkTheme: Boolean = false,
32 | content: @Composable()
33 | () -> Unit
34 | ) {
35 | val colors = if (darkTheme) DarkGreenColorPalette else LightGreenColorPalette
36 | val swingColor = SwingColor()
37 |
38 | MaterialTheme(
39 | colors = colors.copy(
40 | background = swingColor.background,
41 | onBackground = swingColor.onBackground,
42 | surface = swingColor.background,
43 | onSurface = swingColor.onBackground
44 | ),
45 | typography = typography,
46 | shapes = shapes
47 | ) {
48 | Surface {
49 | content()
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/intellij/SwingColor.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme.intellij
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.runtime.MutableState
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.graphics.Color
9 | import com.intellij.ide.ui.LafManagerListener
10 | import com.intellij.openapi.application.ApplicationManager
11 | import javax.swing.UIManager
12 | import java.awt.Color as AWTColor
13 |
14 | interface SwingColor {
15 | val background: Color
16 | val onBackground: Color
17 | }
18 |
19 | @Composable
20 | fun SwingColor(): SwingColor {
21 | val swingColor = remember { SwingColorImpl() }
22 |
23 | val messageBus = remember {
24 | ApplicationManager.getApplication().messageBus.connect()
25 | }
26 |
27 | remember(messageBus) {
28 | messageBus.subscribe(
29 | LafManagerListener.TOPIC,
30 | ThemeChangeListener(swingColor::updateCurrentColors)
31 | )
32 | }
33 |
34 | DisposableEffect(messageBus) {
35 | onDispose {
36 | messageBus.disconnect()
37 | }
38 | }
39 |
40 | return swingColor
41 | }
42 |
43 | private class SwingColorImpl : SwingColor {
44 | private val _backgroundState: MutableState
= mutableStateOf(getBackgroundColor)
45 | private val _onBackgroundState: MutableState = mutableStateOf(getOnBackgroundColor)
46 |
47 | override val background: Color get() = _backgroundState.value
48 | override val onBackground: Color get() = _onBackgroundState.value
49 |
50 | private val getBackgroundColor get() = getColor(BACKGROUND_KEY)
51 | private val getOnBackgroundColor get() = getColor(ON_BACKGROUND_KEY)
52 |
53 | fun updateCurrentColors() {
54 | _backgroundState.value = getBackgroundColor
55 | _onBackgroundState.value = getOnBackgroundColor
56 | }
57 |
58 | private val AWTColor.asComposeColor: Color get() = Color(red, green, blue, alpha)
59 | private fun getColor(key: String): Color = UIManager.getColor(key).asComposeColor
60 |
61 | companion object {
62 | private const val BACKGROUND_KEY = "Panel.background"
63 | private const val ON_BACKGROUND_KEY = "Panel.foreground"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/joetr/modulemaker/ui/theme/intellij/ThemeChangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.ui.theme.intellij
2 |
3 | import com.intellij.ide.ui.LafManager
4 | import com.intellij.ide.ui.LafManagerListener
5 |
6 | internal class ThemeChangeListener(
7 | val updateColors: () -> Unit
8 | ) : LafManagerListener {
9 | override fun lookAndFeelChanged(source: LafManager) {
10 | updateColors()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ccom.joetr.modulemaker
5 | Module Maker
6 | https://joetr.com
7 |
8 | com.intellij.modules.lang
9 |
10 | Enables the creation of modules with sensible defaults.
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/AndroidModuleMakerTest.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.joetr.modulemaker.file.FileWriter
4 | import com.joetr.modulemaker.persistence.PreferenceService
5 | import com.joetr.modulemaker.persistence.PreferenceServiceImpl
6 | import com.joetr.modulemaker.template.GitIgnoreTemplate
7 | import org.junit.Assert.assertEquals
8 | import org.junit.Assert.fail
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 | import org.junit.rules.TemporaryFolder
13 | import java.io.File
14 |
15 | class AndroidModuleMakerTest {
16 |
17 | @JvmField
18 | @Rule
19 | var folder = TemporaryFolder()
20 |
21 | var testState = PreferenceServiceImpl.Companion.State()
22 |
23 | private val fakePreferenceService = object : PreferenceService {
24 | override var preferenceState: PreferenceServiceImpl.Companion.State
25 | get() = testState
26 | set(value) {
27 | testState = value
28 | }
29 | }
30 |
31 | private val fileWriter = FileWriter(
32 | preferenceService = fakePreferenceService
33 | )
34 |
35 | private lateinit var settingsGradleFile: File
36 |
37 | @Before
38 | fun before() {
39 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeData()
40 | }
41 |
42 | @Test
43 | fun `android module created successfully`() {
44 | val modulePath = ":repository"
45 | val modulePathAsFile = "repository"
46 |
47 | fileWriter.createModule(
48 | settingsGradleFile = settingsGradleFile,
49 | workingDirectory = folder.root,
50 | modulePathAsString = modulePath,
51 | moduleType = ANDROID,
52 | showErrorDialog = {
53 | fail("No errors should be thrown")
54 | },
55 | showSuccessDialog = {
56 | assert(true)
57 | },
58 | enhancedModuleCreationStrategy = false,
59 | useKtsBuildFile = false,
60 | gradleFileFollowModule = false,
61 | packageName = testPackageName,
62 | addReadme = true,
63 | addGitIgnore = false,
64 | rootPathString = folder.root.toString(),
65 | previewMode = false
66 |
67 | )
68 |
69 | // assert it was added to settings.gradle
70 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
71 | assert(
72 | settingsGradleFileContents.contains("include(\":repository\")")
73 | )
74 |
75 | // assert readme was generated
76 | assert(
77 | // root/repository/README.md
78 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + readmeFile).exists()
79 | )
80 |
81 | // assert build.gradle is generated
82 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName)
83 | assert(
84 | // root/repository/build.gradle
85 | buildGradleFile.exists()
86 | )
87 |
88 | // assert package name is included in build.gradle
89 | val buildGradleFileContents = readFromFile(buildGradleFile)
90 | assert(
91 | buildGradleFileContents.contains(
92 | " namespace = \"$testPackageName\""
93 | )
94 | )
95 |
96 | // assert the correct package structure is generated
97 | assert(
98 | // root/repository/build.gradle
99 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "src/main/kotlin/com/joetr/test").exists()
100 | )
101 | }
102 |
103 | @Test
104 | fun `when a template is set, that is used instead of default for creating build gradle`() {
105 | val modulePath = ":repository"
106 | val modulePathAsFile = "repository"
107 | val template = "test template"
108 |
109 | fakePreferenceService.preferenceState.androidTemplate = template
110 |
111 | fileWriter.createModule(
112 | settingsGradleFile = settingsGradleFile,
113 | workingDirectory = folder.root,
114 | modulePathAsString = modulePath,
115 | moduleType = ANDROID,
116 | showErrorDialog = {
117 | fail("No errors should be thrown")
118 | },
119 | showSuccessDialog = {
120 | assert(true)
121 | },
122 | enhancedModuleCreationStrategy = false,
123 | useKtsBuildFile = false,
124 | gradleFileFollowModule = false,
125 | packageName = testPackageName,
126 | addReadme = false,
127 | addGitIgnore = false,
128 | rootPathString = folder.root.toString(),
129 | previewMode = false
130 | )
131 |
132 | // assert build.gradle is generated
133 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName)
134 | assert(
135 | // root/repository/build.gradle
136 | buildGradleFile.exists()
137 | )
138 |
139 | // assert package name is included in build.gradle
140 | val buildGradleFileContents = readFromFile(buildGradleFile)
141 | assert(
142 | buildGradleFileContents.contains(
143 | template
144 | )
145 | )
146 | }
147 |
148 | @Test
149 | fun `when a template is set, the package name variable is replaced`() {
150 | val modulePath = ":repository"
151 | val modulePathAsFile = "repository"
152 |
153 | val template = """
154 | this is a custom template
155 |
156 | android {
157 | namespace = "${'$'}{packageName}"
158 | }
159 | """.trimIndent()
160 |
161 | fakePreferenceService.preferenceState.androidTemplate = template
162 |
163 | fileWriter.createModule(
164 | settingsGradleFile = settingsGradleFile,
165 | workingDirectory = folder.root,
166 | modulePathAsString = modulePath,
167 | moduleType = ANDROID,
168 | showErrorDialog = {
169 | fail("No errors should be thrown")
170 | },
171 | showSuccessDialog = {
172 | assert(true)
173 | },
174 | enhancedModuleCreationStrategy = false,
175 | useKtsBuildFile = false,
176 | gradleFileFollowModule = false,
177 | packageName = testPackageName,
178 | addReadme = false,
179 | addGitIgnore = false,
180 | rootPathString = folder.root.toString(),
181 | previewMode = false
182 |
183 | )
184 |
185 | // assert build.gradle file exists and contains the package name when using a custom template
186 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName)
187 |
188 | assert(buildGradleFile.exists())
189 |
190 | val buildGradleFileContents = readFromFile(buildGradleFile)
191 |
192 | assert(
193 | buildGradleFileContents.contains(
194 | " namespace = \"$testPackageName\""
195 | )
196 | )
197 | }
198 |
199 | @Test
200 | fun `android module created successfully when using nested modules`() {
201 | val modulePath = ":repository:database"
202 | val modulePathAsFile = "repository/database"
203 |
204 | fileWriter.createModule(
205 | settingsGradleFile = settingsGradleFile,
206 | workingDirectory = folder.root,
207 | modulePathAsString = modulePath,
208 | moduleType = ANDROID,
209 | showErrorDialog = {
210 | fail("No errors should be thrown")
211 | },
212 | showSuccessDialog = {
213 | assert(true)
214 | },
215 | enhancedModuleCreationStrategy = false,
216 | useKtsBuildFile = false,
217 | gradleFileFollowModule = false,
218 | packageName = testPackageName,
219 | addReadme = true,
220 | addGitIgnore = false,
221 | rootPathString = folder.root.toString(),
222 | previewMode = false
223 |
224 | )
225 |
226 | // assert it was added to settings.gradle
227 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
228 | assert(
229 | settingsGradleFileContents.contains("include(\":repository:database\")")
230 | )
231 |
232 | // assert readme was generated
233 | assert(
234 | // root/repository/database/README.md
235 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + readmeFile).exists()
236 | )
237 |
238 | // assert build.gradle is generated
239 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName)
240 | assert(
241 | // root/repository/database/build.gradle
242 | buildGradleFile.exists()
243 | )
244 |
245 | // assert package name is included in build.gradle
246 | val buildGradleFileContents = readFromFile(buildGradleFile)
247 | assert(
248 | buildGradleFileContents.contains(
249 | " namespace = \"$testPackageName\""
250 | )
251 | )
252 |
253 | // assert the correct package structure is generated
254 | assert(
255 | // root/repository/build.gradle
256 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "src/main/kotlin/com/joetr/test").exists()
257 | )
258 | }
259 |
260 | @Test
261 | fun `android module created successfully with a kts build file`() {
262 | val modulePath = ":repository:database"
263 | val modulePathAsFile = "repository/database"
264 |
265 | fileWriter.createModule(
266 | settingsGradleFile = settingsGradleFile,
267 | workingDirectory = folder.root,
268 | modulePathAsString = modulePath,
269 | moduleType = ANDROID,
270 | showErrorDialog = {
271 | fail("No errors should be thrown")
272 | },
273 | showSuccessDialog = {
274 | assert(true)
275 | },
276 | enhancedModuleCreationStrategy = false,
277 | useKtsBuildFile = true,
278 | gradleFileFollowModule = false,
279 | packageName = testPackageName,
280 | addReadme = false,
281 | addGitIgnore = false,
282 | rootPathString = folder.root.toString(),
283 | previewMode = false
284 |
285 | )
286 |
287 | // assert build.gradle.kts is generated
288 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleKtsFileName)
289 | assert(
290 | // root/repository/database/build.gradle
291 | buildGradleFile.exists()
292 | )
293 | }
294 |
295 | @Test
296 | fun `android module created successfully when include with no parenthesis`() {
297 | settingsGradleFile = folder.populateSettingsGradleWithFakeData()
298 | val modulePath = ":repository:database"
299 |
300 | fileWriter.createModule(
301 | settingsGradleFile = settingsGradleFile,
302 | workingDirectory = folder.root,
303 | modulePathAsString = modulePath,
304 | moduleType = ANDROID,
305 | showErrorDialog = {
306 | fail("No errors should be thrown")
307 | },
308 | showSuccessDialog = {
309 | assert(true)
310 | },
311 | enhancedModuleCreationStrategy = false,
312 | useKtsBuildFile = true,
313 | gradleFileFollowModule = false,
314 | packageName = testPackageName,
315 | addReadme = false,
316 | addGitIgnore = false,
317 | rootPathString = folder.root.toString(),
318 | previewMode = false
319 |
320 | )
321 |
322 | // assert it was added to settings.gradle
323 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
324 | assert(
325 | settingsGradleFileContents.contains("include(\":repository:database\")")
326 | )
327 | }
328 |
329 | @Test
330 | fun `readme added to android module when setting is enabled`() {
331 | settingsGradleFile = folder.populateSettingsGradleWithFakeData()
332 | val modulePath = ":repository:database"
333 | val modulePathAsFile = "repository/database"
334 |
335 | fileWriter.createModule(
336 | settingsGradleFile = settingsGradleFile,
337 | workingDirectory = folder.root,
338 | modulePathAsString = modulePath,
339 | moduleType = ANDROID,
340 | showErrorDialog = {
341 | fail("No errors should be thrown")
342 | },
343 | showSuccessDialog = {
344 | assert(true)
345 | },
346 | enhancedModuleCreationStrategy = false,
347 | useKtsBuildFile = true,
348 | gradleFileFollowModule = false,
349 | packageName = testPackageName,
350 | addReadme = true,
351 | addGitIgnore = false,
352 | rootPathString = folder.root.toString(),
353 | previewMode = false
354 |
355 | )
356 |
357 | // assert readme exists
358 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "README.md")
359 | assert(
360 | buildGradleFile.exists()
361 | )
362 | }
363 |
364 | @Test
365 | fun `readme is not added to android module when setting is disabled`() {
366 | settingsGradleFile = folder.populateSettingsGradleWithFakeData()
367 | val modulePath = ":repository:database"
368 | val modulePathAsFile = "repository/database"
369 |
370 | fileWriter.createModule(
371 | settingsGradleFile = settingsGradleFile,
372 | workingDirectory = folder.root,
373 | modulePathAsString = modulePath,
374 | moduleType = ANDROID,
375 | showErrorDialog = {
376 | fail("No errors should be thrown")
377 | },
378 | showSuccessDialog = {
379 | assert(true)
380 | },
381 | enhancedModuleCreationStrategy = false,
382 | useKtsBuildFile = true,
383 | gradleFileFollowModule = false,
384 | packageName = testPackageName,
385 | addReadme = false,
386 | addGitIgnore = false,
387 | rootPathString = folder.root.toString(),
388 | previewMode = false
389 |
390 | )
391 |
392 | // assert readme does not exists
393 | val buildGradleFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "README.md")
394 | assert(
395 | buildGradleFile.exists().not()
396 | )
397 | }
398 |
399 | @Test
400 | fun `gitignore is not generated in android module when setting is disabled`() {
401 | val modulePath = ":repository"
402 | val modulePathAsFile = "repository"
403 |
404 | fileWriter.createModule(
405 | settingsGradleFile = settingsGradleFile,
406 | workingDirectory = folder.root,
407 | modulePathAsString = modulePath,
408 | moduleType = ANDROID,
409 | showErrorDialog = {
410 | fail("No errors should be thrown")
411 | },
412 | showSuccessDialog = {
413 | assert(true)
414 | },
415 | enhancedModuleCreationStrategy = false,
416 | useKtsBuildFile = false,
417 | gradleFileFollowModule = false,
418 | packageName = testPackageName,
419 | addReadme = false,
420 | addGitIgnore = false,
421 | rootPathString = folder.root.toString(),
422 | previewMode = false
423 |
424 | )
425 |
426 | // assert gitignore was not generated
427 | assert(
428 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore").exists()
429 | .not()
430 | )
431 | }
432 |
433 | @Test
434 | fun `gitignore is generated in android module with default settings when setting is enabled`() {
435 | val modulePath = ":repository"
436 | val modulePathAsFile = "repository"
437 |
438 | fileWriter.createModule(
439 | settingsGradleFile = settingsGradleFile,
440 | workingDirectory = folder.root,
441 | modulePathAsString = modulePath,
442 | moduleType = ANDROID,
443 | showErrorDialog = {
444 | fail("No errors should be thrown")
445 | },
446 | showSuccessDialog = {
447 | assert(true)
448 | },
449 | enhancedModuleCreationStrategy = false,
450 | useKtsBuildFile = false,
451 | gradleFileFollowModule = false,
452 | packageName = testPackageName,
453 | addReadme = false,
454 | addGitIgnore = true,
455 | rootPathString = folder.root.toString(),
456 | previewMode = false
457 |
458 | )
459 |
460 | // assert gitignore was generated and has the expected contents
461 | val gitignoreFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore")
462 | val gitignoreFileContents = readFromFile(file = gitignoreFile)
463 | assertEquals(
464 | GitIgnoreTemplate.data,
465 | gitignoreFileContents.joinToString("\n")
466 | )
467 | }
468 |
469 | @Test
470 | fun `gitignore is generated in android module with custom settings when setting is enabled`() {
471 | val modulePath = ":repository"
472 | val modulePathAsFile = "repository"
473 |
474 | val template = """
475 | this is a custom template
476 | """.trimIndent()
477 |
478 | fakePreferenceService.preferenceState.gitignoreTemplate = template
479 |
480 | fileWriter.createModule(
481 | settingsGradleFile = settingsGradleFile,
482 | workingDirectory = folder.root,
483 | modulePathAsString = modulePath,
484 | moduleType = ANDROID,
485 | showErrorDialog = {
486 | fail("No errors should be thrown")
487 | },
488 | showSuccessDialog = {
489 | assert(true)
490 | },
491 | enhancedModuleCreationStrategy = false,
492 | useKtsBuildFile = false,
493 | gradleFileFollowModule = false,
494 | packageName = testPackageName,
495 | addReadme = false,
496 | addGitIgnore = true,
497 | rootPathString = folder.root.toString(),
498 | previewMode = false
499 |
500 | )
501 |
502 | // assert gitignore was generated and has the expected contents
503 | val gitignoreFile = File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore")
504 | val gitignoreFileContents = readFromFile(file = gitignoreFile)
505 | assertEquals(
506 | template,
507 | gitignoreFileContents.joinToString("\n")
508 | )
509 | }
510 | }
511 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/EnhancedModuleMakerTest.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.joetr.modulemaker.file.FileWriter
4 | import com.joetr.modulemaker.persistence.PreferenceService
5 | import com.joetr.modulemaker.persistence.PreferenceServiceImpl
6 | import com.joetr.modulemaker.template.GitIgnoreTemplate
7 | import org.junit.Assert
8 | import org.junit.Assert.fail
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 | import org.junit.rules.TemporaryFolder
13 | import java.io.File
14 |
15 | class EnhancedModuleMakerTest {
16 |
17 | @JvmField
18 | @Rule
19 | var folder = TemporaryFolder()
20 |
21 | var testState = PreferenceServiceImpl.Companion.State()
22 |
23 | private val fakePreferenceService = object : PreferenceService {
24 | override var preferenceState: PreferenceServiceImpl.Companion.State
25 | get() = testState
26 | set(value) {
27 | testState = value
28 | }
29 | }
30 |
31 | private val fileWriter = FileWriter(
32 | preferenceService = fakePreferenceService
33 | )
34 |
35 | private lateinit var settingsGradleFile: File
36 |
37 | @Before
38 | fun before() {
39 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeData()
40 | }
41 |
42 | @Test
43 | fun `enhanced module created successfully`() {
44 | val modulePath = ":repository"
45 | val modulePathAsFile = "repository"
46 |
47 | fileWriter.createModule(
48 | settingsGradleFile = settingsGradleFile,
49 | workingDirectory = folder.root,
50 | modulePathAsString = modulePath,
51 | moduleType = ANDROID,
52 | showErrorDialog = {
53 | fail("No errors should be thrown")
54 | },
55 | showSuccessDialog = {
56 | assert(true)
57 | },
58 | enhancedModuleCreationStrategy = true,
59 | useKtsBuildFile = false,
60 | gradleFileFollowModule = false,
61 | packageName = testPackageName,
62 | addReadme = true,
63 | addGitIgnore = false,
64 | rootPathString = folder.root.toString(),
65 | previewMode = false
66 |
67 | )
68 |
69 | // assert it was added to settings.gradle
70 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
71 | assert(
72 | settingsGradleFileContents.contains("include(\":repository:api\")")
73 | )
74 | assert(
75 | settingsGradleFileContents.contains("include(\":repository:glue\")")
76 | )
77 | assert(
78 | settingsGradleFileContents.contains("include(\":repository:impl\")")
79 | )
80 |
81 | // assert readme was generated in the api module
82 | assert(
83 | // root/repository/api/README.md
84 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + readmeFile).exists()
85 | )
86 |
87 | // assert build.gradle is generated for all 3 modules
88 | val buildGradleFileApi =
89 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + buildGradleFileName)
90 | val buildGradleFileGlue =
91 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + buildGradleFileName)
92 | val buildGradleFileImpl =
93 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + buildGradleFileName)
94 | assert(buildGradleFileApi.exists())
95 | assert(buildGradleFileGlue.exists())
96 | assert(buildGradleFileImpl.exists())
97 |
98 | // assert package name is included in build.gradle
99 | val buildGradleApiFileContents = readFromFile(buildGradleFileApi)
100 | val buildGradleGlueFileContents = readFromFile(buildGradleFileGlue)
101 | val buildGradleImplFileContents = readFromFile(buildGradleFileImpl)
102 | assert(
103 | buildGradleApiFileContents.contains(
104 | " namespace = \"$testPackageName.api\""
105 | )
106 | )
107 | assert(
108 | buildGradleGlueFileContents.contains(
109 | " namespace = \"$testPackageName.glue\""
110 | )
111 | )
112 | assert(
113 | buildGradleImplFileContents.contains(
114 | " namespace = \"$testPackageName.impl\""
115 | )
116 | )
117 |
118 | // assert the correct package structure is generated
119 | assert(
120 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api/src/main/kotlin/com/joetr/test/api").exists()
121 | )
122 | assert(
123 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue/src/main/kotlin/com/joetr/test/glue").exists()
124 | )
125 | assert(
126 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl/src/main/kotlin/com/joetr/test/impl").exists()
127 | )
128 | }
129 |
130 | @Test
131 | fun `when a template is set, the package name variable is replaced for each module created`() {
132 | val modulePath = ":repository"
133 | val modulePathAsFile = "repository"
134 |
135 | val template = """
136 | this is a custom template
137 |
138 | android {
139 | namespace = "${'$'}{packageName}"
140 | }
141 | """.trimIndent()
142 |
143 | fakePreferenceService.preferenceState.apiTemplate = template
144 | fakePreferenceService.preferenceState.glueTemplate = template
145 | fakePreferenceService.preferenceState.implTemplate = template
146 |
147 | fileWriter.createModule(
148 | settingsGradleFile = settingsGradleFile,
149 | workingDirectory = folder.root,
150 | modulePathAsString = modulePath,
151 | moduleType = ANDROID,
152 | showErrorDialog = {
153 | fail("No errors should be thrown")
154 | },
155 | showSuccessDialog = {
156 | assert(true)
157 | },
158 | enhancedModuleCreationStrategy = true,
159 | useKtsBuildFile = false,
160 | gradleFileFollowModule = false,
161 | packageName = testPackageName,
162 | addReadme = false,
163 | addGitIgnore = false,
164 | rootPathString = folder.root.toString(),
165 | previewMode = false
166 |
167 | )
168 |
169 | // assert build.gradle is generated for all 3 modules
170 | val buildGradleFileApi =
171 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + buildGradleFileName)
172 | val buildGradleFileGlue =
173 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + buildGradleFileName)
174 | val buildGradleFileImpl =
175 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + buildGradleFileName)
176 | assert(buildGradleFileApi.exists())
177 | assert(buildGradleFileGlue.exists())
178 | assert(buildGradleFileImpl.exists())
179 |
180 | // assert package name is included in build.gradle
181 | val buildGradleApiFileContents = readFromFile(buildGradleFileApi)
182 | val buildGradleGlueFileContents = readFromFile(buildGradleFileGlue)
183 | val buildGradleImplFileContents = readFromFile(buildGradleFileImpl)
184 | assert(
185 | buildGradleApiFileContents.contains(
186 | " namespace = \"$testPackageName.api\""
187 | )
188 | )
189 | assert(
190 | buildGradleGlueFileContents.contains(
191 | " namespace = \"$testPackageName.glue\""
192 | )
193 | )
194 | assert(
195 | buildGradleImplFileContents.contains(
196 | " namespace = \"$testPackageName.impl\""
197 | )
198 | )
199 | }
200 |
201 | @Test
202 | fun `when a template is set, that is used instead of default for creating build gradle`() {
203 | val modulePath = ":repository:database"
204 | val modulePathAsFile = "repository/database"
205 |
206 | val template = "test template"
207 |
208 | fakePreferenceService.preferenceState.apiTemplate = template
209 | fakePreferenceService.preferenceState.glueTemplate = template
210 | fakePreferenceService.preferenceState.implTemplate = template
211 |
212 | fileWriter.createModule(
213 | settingsGradleFile = settingsGradleFile,
214 | workingDirectory = folder.root,
215 | modulePathAsString = modulePath,
216 | moduleType = ANDROID,
217 | showErrorDialog = {
218 | fail("No errors should be thrown")
219 | },
220 | showSuccessDialog = {
221 | assert(true)
222 | },
223 | enhancedModuleCreationStrategy = true,
224 | useKtsBuildFile = false,
225 | gradleFileFollowModule = false,
226 | packageName = testPackageName,
227 | addReadme = false,
228 | addGitIgnore = false,
229 | rootPathString = folder.root.toString(),
230 | previewMode = false
231 |
232 | )
233 |
234 | // assert build.gradle is generated for all 3 modules
235 | val buildGradleFileApi =
236 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + buildGradleFileName)
237 | val buildGradleFileGlue =
238 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + buildGradleFileName)
239 | val buildGradleFileImpl =
240 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + buildGradleFileName)
241 | assert(buildGradleFileApi.exists())
242 | assert(buildGradleFileGlue.exists())
243 | assert(buildGradleFileImpl.exists())
244 |
245 | // assert package name is included in build.gradle
246 | val buildGradleApiFileContents = readFromFile(buildGradleFileApi)
247 | val buildGradleGlueFileContents = readFromFile(buildGradleFileGlue)
248 | val buildGradleImplFileContents = readFromFile(buildGradleFileImpl)
249 | assert(
250 | buildGradleApiFileContents.contains(
251 | template
252 | )
253 | )
254 | assert(
255 | buildGradleGlueFileContents.contains(
256 | template
257 | )
258 | )
259 | assert(
260 | buildGradleImplFileContents.contains(
261 | template
262 | )
263 | )
264 | }
265 |
266 | @Test
267 | fun `readme is not generated in enhanced module when setting is disabled`() {
268 | val modulePath = ":repository"
269 | val modulePathAsFile = "repository"
270 |
271 | fileWriter.createModule(
272 | settingsGradleFile = settingsGradleFile,
273 | workingDirectory = folder.root,
274 | modulePathAsString = modulePath,
275 | moduleType = ANDROID,
276 | showErrorDialog = {
277 | fail("No errors should be thrown")
278 | },
279 | showSuccessDialog = {
280 | assert(true)
281 | },
282 | enhancedModuleCreationStrategy = true,
283 | useKtsBuildFile = false,
284 | gradleFileFollowModule = false,
285 | packageName = testPackageName,
286 | addReadme = false,
287 | addGitIgnore = false,
288 | rootPathString = folder.root.toString(),
289 | previewMode = false
290 |
291 | )
292 |
293 | // assert readme was not generated in the api module
294 | assert(
295 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + readmeFile).exists()
296 | .not()
297 | )
298 | }
299 |
300 | @Test
301 | fun `gitignore is not generated in enhanced module when setting is disabled`() {
302 | val modulePath = ":repository"
303 | val modulePathAsFile = "repository"
304 |
305 | fileWriter.createModule(
306 | settingsGradleFile = settingsGradleFile,
307 | workingDirectory = folder.root,
308 | modulePathAsString = modulePath,
309 | moduleType = ANDROID,
310 | showErrorDialog = {
311 | fail("No errors should be thrown")
312 | },
313 | showSuccessDialog = {
314 | assert(true)
315 | },
316 | enhancedModuleCreationStrategy = true,
317 | useKtsBuildFile = false,
318 | gradleFileFollowModule = false,
319 | packageName = testPackageName,
320 | addReadme = false,
321 | addGitIgnore = false,
322 | rootPathString = folder.root.toString(),
323 | previewMode = false
324 |
325 | )
326 |
327 | // assert gitignore was not generated in any of the modules module
328 | assert(
329 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + ".gitignore").exists()
330 | .not()
331 | )
332 | assert(
333 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + ".gitignore").exists()
334 | .not()
335 | )
336 | assert(
337 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + ".gitignore").exists()
338 | .not()
339 | )
340 | }
341 |
342 | @Test
343 | fun `gitignore is generated in enhanced module with default settings when setting is enabled`() {
344 | val modulePath = ":repository"
345 | val modulePathAsFile = "repository"
346 |
347 | fileWriter.createModule(
348 | settingsGradleFile = settingsGradleFile,
349 | workingDirectory = folder.root,
350 | modulePathAsString = modulePath,
351 | moduleType = ANDROID,
352 | showErrorDialog = {
353 | fail("No errors should be thrown")
354 | },
355 | showSuccessDialog = {
356 | assert(true)
357 | },
358 | enhancedModuleCreationStrategy = true,
359 | useKtsBuildFile = false,
360 | gradleFileFollowModule = false,
361 | packageName = testPackageName,
362 | addReadme = false,
363 | addGitIgnore = true,
364 | rootPathString = folder.root.toString(),
365 | previewMode = false
366 |
367 | )
368 |
369 | val apiGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + ".gitignore")
370 | val apiGitignoreFileContents = readFromFile(file = apiGitIgnore)
371 | val glueGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + ".gitignore")
372 | val glueGitignoreFileContents = readFromFile(file = glueGitIgnore)
373 | val implGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + ".gitignore")
374 | val implGitignoreFileContents = readFromFile(file = implGitIgnore)
375 |
376 | Assert.assertEquals(
377 | GitIgnoreTemplate.data,
378 | apiGitignoreFileContents.joinToString("\n")
379 | )
380 |
381 | Assert.assertEquals(
382 | GitIgnoreTemplate.data,
383 | glueGitignoreFileContents.joinToString("\n")
384 | )
385 |
386 | Assert.assertEquals(
387 | GitIgnoreTemplate.data,
388 | implGitignoreFileContents.joinToString("\n")
389 | )
390 | }
391 |
392 | @Test
393 | fun `gitignore is generated in android module with custom settings when setting is enabled`() {
394 | val modulePath = ":repository"
395 | val modulePathAsFile = "repository"
396 |
397 | val template = """
398 | this is a custom template
399 | """.trimIndent()
400 |
401 | fakePreferenceService.preferenceState.gitignoreTemplate = template
402 |
403 | fileWriter.createModule(
404 | settingsGradleFile = settingsGradleFile,
405 | workingDirectory = folder.root,
406 | modulePathAsString = modulePath,
407 | moduleType = ANDROID,
408 | showErrorDialog = {
409 | fail("No errors should be thrown")
410 | },
411 | showSuccessDialog = {
412 | assert(true)
413 | },
414 | enhancedModuleCreationStrategy = true,
415 | useKtsBuildFile = false,
416 | gradleFileFollowModule = false,
417 | packageName = testPackageName,
418 | addReadme = false,
419 | addGitIgnore = true,
420 | rootPathString = folder.root.toString(),
421 | previewMode = false
422 |
423 | )
424 |
425 | val apiGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "api" + File.separator + ".gitignore")
426 | val apiGitignoreFileContents = readFromFile(file = apiGitIgnore)
427 | val glueGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "glue" + File.separator + ".gitignore")
428 | val glueGitignoreFileContents = readFromFile(file = glueGitIgnore)
429 | val implGitIgnore = File(folder.root.path + File.separator + modulePathAsFile + File.separator + "impl" + File.separator + ".gitignore")
430 | val implGitignoreFileContents = readFromFile(file = implGitIgnore)
431 |
432 | Assert.assertEquals(
433 | template,
434 | apiGitignoreFileContents.joinToString("\n")
435 | )
436 |
437 | Assert.assertEquals(
438 | template,
439 | glueGitignoreFileContents.joinToString("\n")
440 | )
441 |
442 | Assert.assertEquals(
443 | template,
444 | implGitignoreFileContents.joinToString("\n")
445 | )
446 | }
447 |
448 | @Test
449 | fun `create module works with 2 parameters`() {
450 | settingsGradleFile.delete()
451 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeFilePathData()
452 | val modulePath = ":repository:network"
453 | val modulePathAsFile = "repository/network"
454 | val rootPathString = folder.root.toString().removePrefix("/")
455 |
456 | fileWriter.createModule(
457 | settingsGradleFile = settingsGradleFile,
458 | workingDirectory = folder.root,
459 | modulePathAsString = modulePath,
460 | moduleType = KOTLIN,
461 | showErrorDialog = {
462 | fail("No errors should be thrown")
463 | },
464 | showSuccessDialog = {
465 | assert(true)
466 | },
467 | enhancedModuleCreationStrategy = true,
468 | useKtsBuildFile = false,
469 | gradleFileFollowModule = false,
470 | packageName = testPackageName,
471 | addReadme = false,
472 | addGitIgnore = true,
473 | rootPathString = folder.root.toString(),
474 | previewMode = false
475 |
476 | )
477 |
478 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
479 | Assert.assertEquals(
480 | "include(\"$modulePath:api\", \"$rootPathString/$modulePathAsFile/api\")",
481 | settingsGradleFileContents[56]
482 | )
483 | Assert.assertEquals(
484 | "include(\"$modulePath:impl\", \"$rootPathString/$modulePathAsFile/impl\")",
485 | settingsGradleFileContents[57]
486 | )
487 | Assert.assertEquals(
488 | "include(\"$modulePath:glue\", \"$rootPathString/$modulePathAsFile/glue\")",
489 | settingsGradleFileContents[58]
490 | )
491 | }
492 |
493 | @Test
494 | fun `custom module names used when set`() {
495 | fakePreferenceService.preferenceState.glueModuleName = "customglue"
496 | fakePreferenceService.preferenceState.apiModuleName = "customapi"
497 | fakePreferenceService.preferenceState.implModuleName = "customimpl"
498 |
499 | val modulePath = ":repository"
500 | val modulePathAsFile = "repository"
501 |
502 | fileWriter.createModule(
503 | settingsGradleFile = settingsGradleFile,
504 | workingDirectory = folder.root,
505 | modulePathAsString = modulePath,
506 | moduleType = ANDROID,
507 | showErrorDialog = {
508 | fail("No errors should be thrown")
509 | },
510 | showSuccessDialog = {
511 | assert(true)
512 | },
513 | enhancedModuleCreationStrategy = true,
514 | useKtsBuildFile = false,
515 | gradleFileFollowModule = false,
516 | packageName = testPackageName,
517 | addReadme = true,
518 | addGitIgnore = false,
519 | rootPathString = folder.root.toString(),
520 | previewMode = false
521 |
522 | )
523 |
524 | // assert it was added to settings.gradle
525 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
526 | assert(
527 | settingsGradleFileContents.contains("include(\":repository:customapi\")")
528 | )
529 | assert(
530 | settingsGradleFileContents.contains("include(\":repository:customglue\")")
531 | )
532 | assert(
533 | settingsGradleFileContents.contains("include(\":repository:customimpl\")")
534 | )
535 |
536 | // assert readme was generated in the api module
537 | assert(
538 | // root/repository/api/README.md
539 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customapi" + File.separator + readmeFile).exists()
540 | )
541 |
542 | // assert build.gradle is generated for all 3 modules
543 | val buildGradleFileApi =
544 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customapi" + File.separator + buildGradleFileName)
545 | val buildGradleFileGlue =
546 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customglue" + File.separator + buildGradleFileName)
547 | val buildGradleFileImpl =
548 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customimpl" + File.separator + buildGradleFileName)
549 | assert(buildGradleFileApi.exists())
550 | assert(buildGradleFileGlue.exists())
551 | assert(buildGradleFileImpl.exists())
552 |
553 | // assert package name is included in build.gradle
554 | val buildGradleApiFileContents = readFromFile(buildGradleFileApi)
555 | val buildGradleGlueFileContents = readFromFile(buildGradleFileGlue)
556 | val buildGradleImplFileContents = readFromFile(buildGradleFileImpl)
557 | assert(
558 | buildGradleApiFileContents.contains(
559 | " namespace = \"$testPackageName.customapi\""
560 | )
561 | )
562 | assert(
563 | buildGradleGlueFileContents.contains(
564 | " namespace = \"$testPackageName.customglue\""
565 | )
566 | )
567 | assert(
568 | buildGradleImplFileContents.contains(
569 | " namespace = \"$testPackageName.customimpl\""
570 | )
571 | )
572 |
573 | // assert the correct package structure is generated
574 | assert(
575 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customapi/src/main/kotlin/com/joetr/test/customapi").exists()
576 | )
577 | assert(
578 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customglue/src/main/kotlin/com/joetr/test/customglue").exists()
579 | )
580 | assert(
581 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "customimpl/src/main/kotlin/com/joetr/test/customimpl").exists()
582 | )
583 | }
584 | }
585 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/KotlinModuleMakerTest.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.joetr.modulemaker.file.FileWriter
4 | import com.joetr.modulemaker.persistence.PreferenceService
5 | import com.joetr.modulemaker.persistence.PreferenceServiceImpl
6 | import com.joetr.modulemaker.template.GitIgnoreTemplate
7 | import org.junit.Assert
8 | import org.junit.Assert.assertEquals
9 | import org.junit.Before
10 | import org.junit.Rule
11 | import org.junit.Test
12 | import org.junit.rules.TemporaryFolder
13 | import java.io.File
14 |
15 | class KotlinModuleMakerTest {
16 |
17 | @JvmField
18 | @Rule
19 | var folder = TemporaryFolder()
20 |
21 | var testState = PreferenceServiceImpl.Companion.State()
22 |
23 | private val fakePreferenceService = object : PreferenceService {
24 | override var preferenceState: PreferenceServiceImpl.Companion.State
25 | get() = testState
26 | set(value) {
27 | testState = value
28 | }
29 | }
30 |
31 | private val fileWriter = FileWriter(
32 | preferenceService = fakePreferenceService
33 | )
34 |
35 | private lateinit var settingsGradleFile: File
36 |
37 | @Before
38 | fun before() {
39 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeData()
40 | }
41 |
42 | @Test
43 | fun `kotlin module created successfully`() {
44 | val modulePath = ":repository"
45 | val modulePathAsFile = "repository"
46 |
47 | fileWriter.createModule(
48 | settingsGradleFile = settingsGradleFile,
49 | workingDirectory = folder.root,
50 | modulePathAsString = modulePath,
51 | moduleType = KOTLIN,
52 | showErrorDialog = {
53 | Assert.fail("No errors should be thrown")
54 | },
55 | showSuccessDialog = {
56 | assert(true)
57 | },
58 | enhancedModuleCreationStrategy = false,
59 | useKtsBuildFile = false,
60 | gradleFileFollowModule = false,
61 | packageName = testPackageName,
62 | addReadme = true,
63 | addGitIgnore = false,
64 | rootPathString = folder.root.toString(),
65 | previewMode = false
66 |
67 | )
68 |
69 | // assert it was added to settings.gradle
70 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
71 | assert(
72 | settingsGradleFileContents.contains("include(\":repository\")")
73 | )
74 |
75 | // assert readme was generated
76 | assert(
77 | // root/repository/README.md
78 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + readmeFile).exists()
79 | )
80 |
81 | // assert build.gradle is generated
82 | assert(
83 | // root/repository/build.gradle
84 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName).exists()
85 | )
86 |
87 | // assert the correct package structure is generated
88 | assert(
89 | // root/repository/build.gradle
90 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "src/main/kotlin/com/joetr/test").exists()
91 | )
92 | }
93 |
94 | @Test
95 | fun `when a template is set, that is used instead of default for creating build gradle`() {
96 | val modulePath = ":repository"
97 | val modulePathAsFile = "repository"
98 | val template = "test template"
99 | fakePreferenceService.preferenceState.kotlinTemplate = template
100 |
101 | fileWriter.createModule(
102 | settingsGradleFile = settingsGradleFile,
103 | workingDirectory = folder.root,
104 | modulePathAsString = modulePath,
105 | moduleType = KOTLIN,
106 | showErrorDialog = {
107 | Assert.fail("No errors should be thrown")
108 | },
109 | showSuccessDialog = {
110 | assert(true)
111 | },
112 | enhancedModuleCreationStrategy = false,
113 | useKtsBuildFile = false,
114 | gradleFileFollowModule = false,
115 | packageName = testPackageName,
116 | addReadme = false,
117 | addGitIgnore = false,
118 | rootPathString = folder.root.toString(),
119 | previewMode = false
120 |
121 | )
122 |
123 | // assert build.gradle is generated
124 | val buildGradleFile =
125 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + buildGradleFileName)
126 | assert(
127 | // root/repository/build.gradle
128 | buildGradleFile.exists()
129 | )
130 |
131 | // assert package name is included in build.gradle
132 | val buildGradleFileContents = readFromFile(buildGradleFile)
133 | assert(
134 | buildGradleFileContents.contains(
135 | template
136 | )
137 | )
138 | }
139 |
140 | @Test
141 | fun `kotlin module created successfully with a kts build file named after module`() {
142 | val modulePath = ":repository:database"
143 | val modulePathAsFile = "repository/database"
144 |
145 | fileWriter.createModule(
146 | settingsGradleFile = settingsGradleFile,
147 | workingDirectory = folder.root,
148 | modulePathAsString = modulePath,
149 | moduleType = KOTLIN,
150 | showErrorDialog = {
151 | Assert.fail("No errors should be thrown")
152 | },
153 | showSuccessDialog = {
154 | assert(true)
155 | },
156 | enhancedModuleCreationStrategy = false,
157 | useKtsBuildFile = true,
158 | gradleFileFollowModule = true,
159 | packageName = testPackageName,
160 | addReadme = false,
161 | addGitIgnore = false,
162 | rootPathString = folder.root.toString(),
163 | previewMode = false
164 |
165 | )
166 |
167 | // assert build.gradle.kts is generated
168 | val buildGradleFile =
169 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "database.gradle.kts")
170 | assert(
171 | // root/repository/database/build.gradle
172 | buildGradleFile.exists()
173 | )
174 | }
175 |
176 | @Test
177 | fun `kotlin module created successfully with a gradle build file named after module`() {
178 | val modulePath = ":repository:database"
179 | val modulePathAsFile = "repository/database"
180 |
181 | fileWriter.createModule(
182 | settingsGradleFile = settingsGradleFile,
183 | workingDirectory = folder.root,
184 | modulePathAsString = modulePath,
185 | moduleType = KOTLIN,
186 | showErrorDialog = {
187 | Assert.fail("No errors should be thrown")
188 | },
189 | showSuccessDialog = {
190 | assert(true)
191 | },
192 | enhancedModuleCreationStrategy = false,
193 | useKtsBuildFile = false,
194 | gradleFileFollowModule = true,
195 | packageName = testPackageName,
196 | addReadme = false,
197 | addGitIgnore = false,
198 | rootPathString = folder.root.toString(),
199 | previewMode = false
200 |
201 | )
202 |
203 | // assert build.gradle.kts is generated
204 | val buildGradleFile =
205 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "database.gradle")
206 | assert(
207 | // root/repository/database/build.gradle
208 | buildGradleFile.exists()
209 | )
210 | }
211 |
212 | @Test
213 | fun `readme is not added to kotlin module when setting is disabled`() {
214 | val modulePath = ":repository:database"
215 | val modulePathAsFile = "repository/database"
216 |
217 | fileWriter.createModule(
218 | settingsGradleFile = settingsGradleFile,
219 | workingDirectory = folder.root,
220 | modulePathAsString = modulePath,
221 | moduleType = KOTLIN,
222 | showErrorDialog = {
223 | Assert.fail("No errors should be thrown")
224 | },
225 | showSuccessDialog = {
226 | assert(true)
227 | },
228 | enhancedModuleCreationStrategy = false,
229 | useKtsBuildFile = false,
230 | gradleFileFollowModule = true,
231 | packageName = testPackageName,
232 | addReadme = false,
233 | addGitIgnore = false,
234 | rootPathString = folder.root.toString(),
235 | previewMode = false
236 |
237 | )
238 |
239 | // assert readme is NOT generated
240 | val buildGradleFile =
241 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "README.md")
242 | assert(
243 | buildGradleFile.exists().not()
244 | )
245 | }
246 |
247 | @Test
248 | fun `readme is added to kotlin module when setting is enabled`() {
249 | val modulePath = ":repository:database"
250 | val modulePathAsFile = "repository/database"
251 |
252 | fileWriter.createModule(
253 | settingsGradleFile = settingsGradleFile,
254 | workingDirectory = folder.root,
255 | modulePathAsString = modulePath,
256 | moduleType = KOTLIN,
257 | showErrorDialog = {
258 | Assert.fail("No errors should be thrown")
259 | },
260 | showSuccessDialog = {
261 | assert(true)
262 | },
263 | enhancedModuleCreationStrategy = false,
264 | useKtsBuildFile = false,
265 | gradleFileFollowModule = true,
266 | packageName = testPackageName,
267 | addReadme = true,
268 | addGitIgnore = false,
269 | rootPathString = folder.root.toString(),
270 | previewMode = false
271 |
272 | )
273 |
274 | // assert readme is generated
275 | val buildGradleFile =
276 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + "README.md")
277 | assert(
278 | buildGradleFile.exists()
279 | )
280 | }
281 |
282 | @Test
283 | fun `gitignore is not generated in kotlin module when setting is disabled`() {
284 | val modulePath = ":repository"
285 | val modulePathAsFile = "repository"
286 |
287 | fileWriter.createModule(
288 | settingsGradleFile = settingsGradleFile,
289 | workingDirectory = folder.root,
290 | modulePathAsString = modulePath,
291 | moduleType = KOTLIN,
292 | showErrorDialog = {
293 | Assert.fail("No errors should be thrown")
294 | },
295 | showSuccessDialog = {
296 | assert(true)
297 | },
298 | enhancedModuleCreationStrategy = false,
299 | useKtsBuildFile = false,
300 | gradleFileFollowModule = false,
301 | packageName = testPackageName,
302 | addReadme = false,
303 | addGitIgnore = false,
304 | rootPathString = folder.root.toString(),
305 | previewMode = false
306 |
307 | )
308 |
309 | // assert gitignore was not generated
310 | assert(
311 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore").exists()
312 | .not()
313 | )
314 | }
315 |
316 | @Test
317 | fun `gitignore is generated in kotlin module with default settings when setting is enabled`() {
318 | val modulePath = ":repository"
319 | val modulePathAsFile = "repository"
320 |
321 | fileWriter.createModule(
322 | settingsGradleFile = settingsGradleFile,
323 | workingDirectory = folder.root,
324 | modulePathAsString = modulePath,
325 | moduleType = KOTLIN,
326 | showErrorDialog = {
327 | Assert.fail("No errors should be thrown")
328 | },
329 | showSuccessDialog = {
330 | assert(true)
331 | },
332 | enhancedModuleCreationStrategy = false,
333 | useKtsBuildFile = false,
334 | gradleFileFollowModule = false,
335 | packageName = testPackageName,
336 | addReadme = false,
337 | addGitIgnore = true,
338 | rootPathString = folder.root.toString(),
339 | previewMode = false
340 |
341 | )
342 |
343 | // assert gitignore was generated and has the expected contents
344 | val gitignoreFile =
345 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore")
346 | val gitignoreFileContents = readFromFile(file = gitignoreFile)
347 | assertEquals(
348 | GitIgnoreTemplate.data,
349 | gitignoreFileContents.joinToString("\n")
350 | )
351 | }
352 |
353 | @Test
354 | fun `gitignore is generated in kotlin module with custom settings when setting is enabled`() {
355 | val modulePath = ":repository"
356 | val modulePathAsFile = "repository"
357 |
358 | val template = """
359 | this is a custom template
360 | """.trimIndent()
361 |
362 | fakePreferenceService.preferenceState.gitignoreTemplate = template
363 |
364 | fileWriter.createModule(
365 | settingsGradleFile = settingsGradleFile,
366 | workingDirectory = folder.root,
367 | modulePathAsString = modulePath,
368 | moduleType = KOTLIN,
369 | showErrorDialog = {
370 | Assert.fail("No errors should be thrown")
371 | },
372 | showSuccessDialog = {
373 | assert(true)
374 | },
375 | enhancedModuleCreationStrategy = false,
376 | useKtsBuildFile = false,
377 | gradleFileFollowModule = false,
378 | packageName = testPackageName,
379 | addReadme = false,
380 | addGitIgnore = true,
381 | rootPathString = folder.root.toString(),
382 | previewMode = false
383 |
384 | )
385 |
386 | // assert gitignore was generated and has the expected contents
387 | val gitignoreFile =
388 | File(folder.root.path + File.separator + modulePathAsFile + File.separator + File.separator + ".gitignore")
389 | val gitignoreFileContents = readFromFile(file = gitignoreFile)
390 | assertEquals(
391 | template,
392 | gitignoreFileContents.joinToString("\n")
393 | )
394 | }
395 |
396 | @Test
397 | fun `create module works with 2 parameters`() {
398 | settingsGradleFile.delete()
399 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeFilePathData()
400 | val modulePath = ":repository:network"
401 | val modulePathAsFile = "repository/network"
402 | val rootPathString = folder.root.toString().removePrefix("/")
403 |
404 | fileWriter.createModule(
405 | settingsGradleFile = settingsGradleFile,
406 | workingDirectory = folder.root,
407 | modulePathAsString = modulePath,
408 | moduleType = KOTLIN,
409 | showErrorDialog = {
410 | Assert.fail("No errors should be thrown")
411 | },
412 | showSuccessDialog = {
413 | assert(true)
414 | },
415 | enhancedModuleCreationStrategy = false,
416 | useKtsBuildFile = false,
417 | gradleFileFollowModule = false,
418 | packageName = testPackageName,
419 | addReadme = false,
420 | addGitIgnore = true,
421 | rootPathString = folder.root.toString(),
422 | previewMode = false
423 |
424 | )
425 |
426 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
427 | assertEquals(
428 | "include(\"$modulePath\", \"$rootPathString/$modulePathAsFile\")",
429 | settingsGradleFileContents[56]
430 | )
431 | }
432 |
433 | @Test
434 | fun `create module works with 2 parameters and custom include for modules`() {
435 | settingsGradleFile.delete()
436 | settingsGradleFile = folder.populateSettingsGradleKtsWithFakeFilePathDataAndCustomInclude()
437 | val modulePath = ":repository:network"
438 | val modulePathAsFile = "repository/network"
439 | val rootPathString = folder.root.toString().removePrefix("/")
440 |
441 | fileWriter.createModule(
442 | settingsGradleFile = settingsGradleFile,
443 | workingDirectory = folder.root,
444 | modulePathAsString = modulePath,
445 | moduleType = KOTLIN,
446 | showErrorDialog = {
447 | Assert.fail("No errors should be thrown")
448 | },
449 | showSuccessDialog = {
450 | assert(true)
451 | },
452 | enhancedModuleCreationStrategy = false,
453 | useKtsBuildFile = false,
454 | gradleFileFollowModule = false,
455 | packageName = testPackageName,
456 | addReadme = false,
457 | addGitIgnore = true,
458 | rootPathString = folder.root.toString(),
459 | previewMode = false
460 |
461 | )
462 |
463 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
464 | assertEquals(
465 | "includeBuild(\"$modulePath\", \"$rootPathString/$modulePathAsFile\")",
466 | settingsGradleFileContents[56]
467 | )
468 | }
469 |
470 | @Test
471 | fun `module added correctly settings gradle with one big include`() {
472 | settingsGradleFile.delete()
473 | settingsGradleFile = folder.populateSettingsGradleKtsWithTiviSettingsGradleKts()
474 | val modulePath = ":repository:network"
475 |
476 | fileWriter.createModule(
477 | settingsGradleFile = settingsGradleFile,
478 | workingDirectory = folder.root,
479 | modulePathAsString = modulePath,
480 | moduleType = KOTLIN,
481 | showErrorDialog = {
482 | Assert.fail("No errors should be thrown")
483 | },
484 | showSuccessDialog = {
485 | assert(true)
486 | },
487 | enhancedModuleCreationStrategy = false,
488 | useKtsBuildFile = false,
489 | gradleFileFollowModule = false,
490 | packageName = testPackageName,
491 | addReadme = false,
492 | addGitIgnore = true,
493 | rootPathString = folder.root.toString(),
494 | previewMode = false
495 |
496 | )
497 |
498 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
499 | assertEquals(
500 | "include(\"$modulePath\")",
501 | settingsGradleFileContents[45]
502 | )
503 | }
504 |
505 | @Test
506 | fun `custom include keyword is used when specified`() {
507 | settingsGradleFile.delete()
508 | settingsGradleFile = folder.populateSettingsGradleKtsWithTiviWithCustomIncludeSettingsGradleKts()
509 | val modulePath = ":repository:network"
510 |
511 | fakePreferenceService.preferenceState.includeProjectKeyword = "testIncludeProject"
512 |
513 | fileWriter.createModule(
514 | settingsGradleFile = settingsGradleFile,
515 | workingDirectory = folder.root,
516 | modulePathAsString = modulePath,
517 | moduleType = KOTLIN,
518 | showErrorDialog = {
519 | Assert.fail("No errors should be thrown")
520 | },
521 | showSuccessDialog = {
522 | assert(true)
523 | },
524 | enhancedModuleCreationStrategy = false,
525 | useKtsBuildFile = false,
526 | gradleFileFollowModule = false,
527 | packageName = testPackageName,
528 | addReadme = false,
529 | addGitIgnore = true,
530 | rootPathString = folder.root.toString(),
531 | previewMode = false
532 |
533 | )
534 |
535 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
536 | assertEquals(
537 | "testIncludeProject(\"$modulePath\")",
538 | settingsGradleFileContents[45]
539 | )
540 | }
541 |
542 | @Test
543 | fun `module added correctly settings gradle with one include statement`() {
544 | settingsGradleFile.delete()
545 | settingsGradleFile = folder.populateSettingsGradleWithFakeData()
546 | val modulePath = ":repository:network"
547 |
548 | fileWriter.createModule(
549 | settingsGradleFile = settingsGradleFile,
550 | workingDirectory = folder.root,
551 | modulePathAsString = modulePath,
552 | moduleType = KOTLIN,
553 | showErrorDialog = {
554 | Assert.fail("No errors should be thrown")
555 | },
556 | showSuccessDialog = {
557 | assert(true)
558 | },
559 | enhancedModuleCreationStrategy = false,
560 | useKtsBuildFile = false,
561 | gradleFileFollowModule = false,
562 | packageName = testPackageName,
563 | addReadme = false,
564 | addGitIgnore = true,
565 | rootPathString = folder.root.toString(),
566 | previewMode = false
567 |
568 | )
569 |
570 | val settingsGradleFileContents = readFromFile(file = settingsGradleFile)
571 | assertEquals(
572 | "include(\"$modulePath\")",
573 | settingsGradleFileContents[16]
574 | )
575 | }
576 |
577 | @Test
578 | fun `no files created in preview mode`() {
579 | settingsGradleFile.delete()
580 | settingsGradleFile = folder.populateSettingsGradleWithFakeData()
581 | val modulePath = ":repository:network"
582 |
583 | val rootFiles = folder.root.listFiles()
584 |
585 | val settingsGradleFileContentsBefore = readFromFile(file = settingsGradleFile)
586 |
587 | val filesToReturn = fileWriter.createModule(
588 | settingsGradleFile = settingsGradleFile,
589 | workingDirectory = folder.root,
590 | modulePathAsString = modulePath,
591 | moduleType = KOTLIN,
592 | showErrorDialog = {
593 | Assert.fail("No errors should be thrown")
594 | },
595 | showSuccessDialog = {
596 | assert(true)
597 | },
598 | enhancedModuleCreationStrategy = false,
599 | useKtsBuildFile = false,
600 | gradleFileFollowModule = false,
601 | packageName = testPackageName,
602 | addReadme = false,
603 | addGitIgnore = true,
604 | rootPathString = folder.root.toString(),
605 | previewMode = true
606 |
607 | )
608 |
609 | val settingsGradleFileContentsAfter = readFromFile(file = settingsGradleFile)
610 |
611 | assertEquals(
612 | settingsGradleFileContentsBefore,
613 | settingsGradleFileContentsAfter
614 | )
615 |
616 | assertEquals(
617 | filesToReturn.size,
618 | 3
619 | )
620 |
621 | assertEquals(
622 | rootFiles!!.size,
623 | folder.root.listFiles()!!.size
624 | )
625 | }
626 | }
627 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/TestConstants.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | const val settingsGradleKts = "settings.gradle.kts"
4 | const val settingsGradle = "settings.gradle"
5 | const val readmeFile = "README.md"
6 | const val buildGradleFileName = "build.gradle"
7 | const val buildGradleKtsFileName = "build.gradle.kts"
8 | const val testPackageName = "com.joetr.test"
9 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/TestUtilities.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker
2 |
3 | import com.joetr.modulemaker.settings.DefaultTemplateSettingsGradle
4 | import com.joetr.modulemaker.settings.NowInAndroidSettingsGradleKts
5 | import com.joetr.modulemaker.settings.TiviSettingsGradleKts
6 | import org.junit.rules.TemporaryFolder
7 | import java.io.BufferedReader
8 | import java.io.File
9 | import java.io.FileReader
10 | import java.io.FileWriter
11 |
12 | fun TemporaryFolder.populateSettingsGradleKtsWithFakeData(): File {
13 | val settingsGradleKts = this.newFile(settingsGradleKts)
14 | val writer = FileWriter(settingsGradleKts)
15 | writer.write(NowInAndroidSettingsGradleKts.data)
16 | writer.close()
17 | return settingsGradleKts
18 | }
19 |
20 | fun TemporaryFolder.populateSettingsGradleKtsWithFakeFilePathData(): File {
21 | val settingsGradleKts = this.newFile(settingsGradleKts)
22 | val writer = FileWriter(settingsGradleKts)
23 | writer.write(NowInAndroidSettingsGradleKts.filePathData)
24 | writer.close()
25 | return settingsGradleKts
26 | }
27 |
28 | fun TemporaryFolder.populateSettingsGradleKtsWithFakeFilePathDataAndCustomInclude(): File {
29 | val settingsGradleKts = this.newFile(settingsGradleKts)
30 | val writer = FileWriter(settingsGradleKts)
31 | writer.write(NowInAndroidSettingsGradleKts.filePathDataWithCustomIncludeBuildData)
32 | writer.close()
33 | return settingsGradleKts
34 | }
35 |
36 | fun TemporaryFolder.populateSettingsGradleKtsWithTiviSettingsGradleKts(): File {
37 | val settingsGradleKts = this.newFile(settingsGradleKts)
38 | val writer = FileWriter(settingsGradleKts)
39 | writer.write(TiviSettingsGradleKts.data)
40 | writer.close()
41 | return settingsGradleKts
42 | }
43 |
44 | fun TemporaryFolder.populateSettingsGradleKtsWithTiviWithCustomIncludeSettingsGradleKts(): File {
45 | val settingsGradleKts = this.newFile(settingsGradleKts)
46 | val writer = FileWriter(settingsGradleKts)
47 | writer.write(TiviSettingsGradleKts.dataWithCustomIncludeProject)
48 | writer.close()
49 | return settingsGradleKts
50 | }
51 |
52 | fun TemporaryFolder.populateSettingsGradleWithFakeData(): File {
53 | val settingsGradle = this.newFile(settingsGradle)
54 | val writer = FileWriter(settingsGradle)
55 | writer.write(DefaultTemplateSettingsGradle.data)
56 | writer.close()
57 | return settingsGradle
58 | }
59 |
60 | fun readFromFile(file: File): List {
61 | val fileReader = FileReader(file)
62 | val bufferedReader = BufferedReader(fileReader)
63 | val data = mutableListOf()
64 | try {
65 | var line: String?
66 | while (bufferedReader.readLine().also { line = it } != null) {
67 | data.add(line.orEmpty())
68 | }
69 | } finally {
70 | fileReader.close()
71 | bufferedReader.close()
72 | }
73 | return data
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/settings/DefaultTemplateSettingsGradle.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.settings
2 |
3 | object DefaultTemplateSettingsGradle {
4 | val data = """
5 | pluginManagement {
6 | repositories {
7 | google()
8 | mavenCentral()
9 | gradlePluginPortal()
10 | }
11 | }
12 | dependencyResolutionManagement {
13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 | rootProject.name = "ModuleMakerTest"
20 | include ':app'
21 | """.trimIndent()
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/settings/NowInAndroidSettingsGradleKts.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.settings
2 |
3 | object NowInAndroidSettingsGradleKts {
4 | val data = """
5 | /*
6 | * Copyright 2021 The Android Open Source Project
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * https://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | */
20 |
21 | pluginManagement {
22 | includeBuild("build-logic")
23 | repositories {
24 | google()
25 | mavenCentral()
26 | gradlePluginPortal()
27 | }
28 | }
29 |
30 | dependencyResolutionManagement {
31 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
32 | repositories {
33 | google()
34 | mavenCentral()
35 | }
36 | }
37 | rootProject.name = "nowinandroid"
38 | include(":app")
39 | include(":app-nia-catalog")
40 | include(":benchmarks")
41 | include(":core:common")
42 | include(":core:data")
43 | include(":core:data-test")
44 | include(":core:database")
45 | include(":core:datastore")
46 | include(":core:datastore-test")
47 | include(":core:designsystem")
48 | include(":core:domain")
49 | include(":core:model")
50 | include(":core:network")
51 | include(":core:ui")
52 | include(":core:testing")
53 | include(":core:analytics")
54 | include(":core:notifications")
55 |
56 | include(":feature:foryou")
57 | include(":feature:interests")
58 | include(":feature:bookmarks")
59 | include(":feature:topic")
60 | include(":feature:settings")
61 | include(":lint")
62 | include(":sync:work")
63 | include(":sync:sync-test")
64 | include(":ui-test-hilt-manifest")
65 | """.trimIndent()
66 |
67 | val filePathData = """
68 | /*
69 | * Copyright 2021 The Android Open Source Project
70 | *
71 | * Licensed under the Apache License, Version 2.0 (the "License");
72 | * you may not use this file except in compliance with the License.
73 | * You may obtain a copy of the License at
74 | *
75 | * https://www.apache.org/licenses/LICENSE-2.0
76 | *
77 | * Unless required by applicable law or agreed to in writing, software
78 | * distributed under the License is distributed on an "AS IS" BASIS,
79 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
80 | * See the License for the specific language governing permissions and
81 | * limitations under the License.
82 | */
83 |
84 | pluginManagement {
85 | includeBuild("build-logic")
86 | repositories {
87 | google()
88 | mavenCentral()
89 | gradlePluginPortal()
90 | }
91 | }
92 |
93 | dependencyResolutionManagement {
94 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
95 | repositories {
96 | google()
97 | mavenCentral()
98 | }
99 | }
100 | rootProject.name = "nowinandroid"
101 | include(":app", "path/to")
102 | include(":app-nia-catalog", "path/to")
103 | include(":benchmarks", "path/to")
104 | include(":core:common", "path/to")
105 | include(":core:data", "path/to")
106 | include(":core:data-test", "path/to")
107 | include(":core:database", "path/to")
108 | include(":core:datastore", "path/to")
109 | include(":core:datastore-test", "path/to")
110 | include(":core:designsystem", "path/to")
111 | include(":core:domain", "path/to")
112 | include(":core:model", "path/to")
113 | include(":core:network", "path/to")
114 | include(":core:ui", "path/to")
115 | include(":core:testing", "path/to")
116 | include(":core:analytics", "path/to")
117 | include(":core:notifications", "path/to)"
118 |
119 | include(":feature:foryou", "path/to")
120 | include(":feature:interests", "path/to")
121 | include(":feature:bookmarks", "path/to")
122 | include(":feature:topic", "path/to")
123 | include(":feature:settings", "path/to")
124 | include(":lint", "path/to")
125 | include(":sync:work", "path/to")
126 | include(":sync:sync-test", "path/to")
127 | include(":ui-test-hilt-manifest", "path/to")
128 | """.trimIndent()
129 |
130 | val filePathDataWithCustomIncludeBuildData = """
131 | /*
132 | * Copyright 2021 The Android Open Source Project
133 | *
134 | * Licensed under the Apache License, Version 2.0 (the "License");
135 | * you may not use this file except in compliance with the License.
136 | * You may obtain a copy of the License at
137 | *
138 | * https://www.apache.org/licenses/LICENSE-2.0
139 | *
140 | * Unless required by applicable law or agreed to in writing, software
141 | * distributed under the License is distributed on an "AS IS" BASIS,
142 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143 | * See the License for the specific language governing permissions and
144 | * limitations under the License.
145 | */
146 |
147 | pluginManagement {
148 | includeBuild("build-logic")
149 | repositories {
150 | google()
151 | mavenCentral()
152 | gradlePluginPortal()
153 | }
154 | }
155 |
156 | dependencyResolutionManagement {
157 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
158 | repositories {
159 | google()
160 | mavenCentral()
161 | }
162 | }
163 | rootProject.name = "nowinandroid"
164 | includeBuild(":app", "path/to")
165 | includeBuild(":app-nia-catalog", "path/to")
166 | includeBuild(":benchmarks", "path/to")
167 | includeBuild(":core:common", "path/to")
168 | includeBuild(":core:data", "path/to")
169 | includeBuild(":core:data-test", "path/to")
170 | includeBuild(":core:database", "path/to")
171 | includeBuild(":core:datastore", "path/to")
172 | includeBuild(":core:datastore-test", "path/to")
173 | includeBuild(":core:designsystem", "path/to")
174 | includeBuild(":core:domain", "path/to")
175 | includeBuild(":core:model", "path/to")
176 | includeBuild(":core:network", "path/to")
177 | includeBuild(":core:ui", "path/to")
178 | includeBuild(":core:testing", "path/to")
179 | includeBuild(":core:analytics", "path/to")
180 | includeBuild(":core:notifications", "path/to)"
181 |
182 | includeBuild(":feature:foryou", "path/to")
183 | includeBuild(":feature:interests", "path/to")
184 | includeBuild(":feature:bookmarks", "path/to")
185 | includeBuild(":feature:topic", "path/to")
186 | includeBuild(":feature:settings", "path/to")
187 | includeBuild(":lint", "path/to")
188 | includeBuild(":sync:work", "path/to")
189 | includeBuild(":sync:sync-test", "path/to")
190 | includeBuild(":ui-test-hilt-manifest", "path/to")
191 | """.trimIndent()
192 | }
193 |
--------------------------------------------------------------------------------
/src/test/kotlin/com/joetr/modulemaker/settings/TiviSettingsGradleKts.kt:
--------------------------------------------------------------------------------
1 | package com.joetr.modulemaker.settings
2 |
3 | object TiviSettingsGradleKts {
4 | val data = """
5 | // Copyright 2023, Christopher Banes and the Tivi project contributors
6 | // SPDX-License-Identifier: Apache-2.0
7 |
8 | pluginManagement {
9 | includeBuild("gradle/build-logic")
10 |
11 | repositories {
12 | gradlePluginPortal()
13 | google()
14 | mavenCentral()
15 | }
16 | }
17 |
18 | dependencyResolutionManagement {
19 | repositories {
20 | google()
21 | mavenCentral()
22 | mavenLocal()
23 |
24 | // Needed when using the 'dev' Compose Compiler
25 | // maven("https://androidx.dev/storage/compose-compiler/repository/")
26 |
27 | // Used for snapshots if needed
28 | // maven("https://oss.sonatype.org/content/repositories/snapshots/")
29 | // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
30 | }
31 | }
32 |
33 | plugins {
34 | id("com.gradle.enterprise") version "3.13.4"
35 | }
36 |
37 | gradleEnterprise {
38 | buildScan {
39 | termsOfServiceUrl = "https://gradle.com/terms-of-service"
40 | termsOfServiceAgree = "yes"
41 | }
42 | }
43 |
44 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
45 | // https://docs.gradle.org/7.6/userguide/configuration_cache.html#config_cache:stable
46 | // enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
47 |
48 | rootProject.name = "tivi"
49 |
50 | include(
51 | ":core:analytics",
52 | ":core:base",
53 | ":core:logging",
54 | ":core:performance",
55 | ":core:powercontroller",
56 | ":core:preferences",
57 | ":common:ui:circuit-overlay",
58 | ":common:ui:resources",
59 | ":common:ui:strings",
60 | ":common:ui:compose",
61 | ":common:ui:screens",
62 | ":common:imageloading",
63 | ":data:db",
64 | ":data:db-sqldelight",
65 | ":data:legacy",
66 | ":data:models",
67 | ":data:test",
68 | ":data:episodes",
69 | ":data:followedshows",
70 | ":data:popularshows",
71 | ":data:recommendedshows",
72 | ":data:relatedshows",
73 | ":data:search",
74 | ":data:shows",
75 | ":data:showimages",
76 | ":data:traktauth",
77 | ":data:traktusers",
78 | ":data:trendingshows",
79 | ":data:watchedshows",
80 | ":api:trakt",
81 | ":api:tmdb",
82 | ":tasks",
83 | ":domain",
84 | ":shared",
85 | ":ui:discover",
86 | ":ui:episode:details",
87 | ":ui:episode:track",
88 | ":ui:trending",
89 | ":ui:popular",
90 | ":ui:recommended",
91 | ":ui:search",
92 | ":ui:show:details",
93 | ":ui:show:seasons",
94 | ":ui:settings",
95 | ":ui:library",
96 | ":ui:account",
97 | ":ui:upnext",
98 | ":ui:root",
99 | ":android-app:app",
100 | ":android-app:benchmark",
101 | ":android-app:common-test",
102 | ":desktop-app",
103 | ":thirdparty:swipe",
104 | ":thirdparty:compose-material-dialogs:datetime",
105 | )
106 | """.trimIndent()
107 |
108 | val dataWithCustomIncludeProject = """
109 | // Copyright 2023, Christopher Banes and the Tivi project contributors
110 | // SPDX-License-Identifier: Apache-2.0
111 |
112 | pluginManagement {
113 | includeBuild("gradle/build-logic")
114 |
115 | repositories {
116 | gradlePluginPortal()
117 | google()
118 | mavenCentral()
119 | }
120 | }
121 |
122 | dependencyResolutionManagement {
123 | repositories {
124 | google()
125 | mavenCentral()
126 | mavenLocal()
127 |
128 | // Needed when using the 'dev' Compose Compiler
129 | // maven("https://androidx.dev/storage/compose-compiler/repository/")
130 |
131 | // Used for snapshots if needed
132 | // maven("https://oss.sonatype.org/content/repositories/snapshots/")
133 | // maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
134 | }
135 | }
136 |
137 | plugins {
138 | id("com.gradle.enterprise") version "3.13.4"
139 | }
140 |
141 | gradleEnterprise {
142 | buildScan {
143 | termsOfServiceUrl = "https://gradle.com/terms-of-service"
144 | termsOfServiceAgree = "yes"
145 | }
146 | }
147 |
148 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
149 | // https://docs.gradle.org/7.6/userguide/configuration_cache.html#config_cache:stable
150 | // enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
151 |
152 | rootProject.name = "tivi"
153 |
154 | testIncludeProject(
155 | ":core:analytics",
156 | ":core:base",
157 | ":core:logging",
158 | ":core:performance",
159 | ":core:powercontroller",
160 | ":core:preferences",
161 | ":common:ui:circuit-overlay",
162 | ":common:ui:resources",
163 | ":common:ui:strings",
164 | ":common:ui:compose",
165 | ":common:ui:screens",
166 | ":common:imageloading",
167 | ":data:db",
168 | ":data:db-sqldelight",
169 | ":data:legacy",
170 | ":data:models",
171 | ":data:test",
172 | ":data:episodes",
173 | ":data:followedshows",
174 | ":data:popularshows",
175 | ":data:recommendedshows",
176 | ":data:relatedshows",
177 | ":data:search",
178 | ":data:shows",
179 | ":data:showimages",
180 | ":data:traktauth",
181 | ":data:traktusers",
182 | ":data:trendingshows",
183 | ":data:watchedshows",
184 | ":api:trakt",
185 | ":api:tmdb",
186 | ":tasks",
187 | ":domain",
188 | ":shared",
189 | ":ui:discover",
190 | ":ui:episode:details",
191 | ":ui:episode:track",
192 | ":ui:trending",
193 | ":ui:popular",
194 | ":ui:recommended",
195 | ":ui:search",
196 | ":ui:show:details",
197 | ":ui:show:seasons",
198 | ":ui:settings",
199 | ":ui:library",
200 | ":ui:account",
201 | ":ui:upnext",
202 | ":ui:root",
203 | ":android-app:app",
204 | ":android-app:benchmark",
205 | ":android-app:common-test",
206 | ":desktop-app",
207 | ":thirdparty:swipe",
208 | ":thirdparty:compose-material-dialogs:datetime",
209 | )
210 | """.trimIndent()
211 | }
212 |
--------------------------------------------------------------------------------