Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
d079f01
Add Python guide support
n0tl3ss Sep 22, 2026
6a02947
Address Python guide review feedback
n0tl3ss Sep 22, 2026
a4da5dc
Fix Python guide validation workflow
n0tl3ss Sep 22, 2026
d42b251
Make index link test CI-independent
n0tl3ss Sep 22, 2026
ea4f713
Make zip test independent of file order
n0tl3ss Sep 22, 2026
16227ef
Do not copy stale test resources state
n0tl3ss Sep 23, 2026
d9ffac3
Use a Python response in SendGrid guide tests (#1858)
n0tl3ss Sep 24, 2026
7657db4
Use asyncio in Python SendGrid guide
n0tl3ss Sep 24, 2026
ffab74f
Fix generated Pyronaut test script separation
n0tl3ss Sep 24, 2026
84f524c
Test Pyronaut command separation directly
n0tl3ss Sep 24, 2026
6ba608e
Use snake_case for OpenAPI Python controller callback (#1862)
n0tl3ss Sep 24, 2026
0b7ec2b
Merge pull request #1861 from micronaut-projects/fix/pyronaut-test-sc…
n0tl3ss Sep 24, 2026
8f8661c
Merge branch 'master' into python-guide-support
sdelamo Sep 28, 2026
7926e17
remove python boolean
sdelamo Sep 28, 2026
bae7ab3
Use Python workbook library in Excel download guide (#1868)
n0tl3ss Sep 30, 2026
26c35ad
Load GraphQL Python schema without Java IO interop
n0tl3ss Sep 30, 2026
f369350
Merge pull request #1871 from micronaut-projects/codex/python-graphql…
n0tl3ss Sep 30, 2026
d7d8509
Merge branch 'python' into python-guide-support
n0tl3ss Sep 30, 2026
e4d8c3d
Merge branch 'python' into python-guide-support
n0tl3ss Sep 30, 2026
3e13761
Apply local Pyronaut versions without repository override
n0tl3ss Sep 30, 2026
42313cc
Declare language-specific guide generation outputs
n0tl3ss Sep 30, 2026
42aef4c
Fix buildSrc expectations after Python branch sync
n0tl3ss Sep 30, 2026
8174b76
Merge pull request #1879 from micronaut-projects/codex/guide-ci-schem…
n0tl3ss Sep 30, 2026
7e2a2f7
Merge pull request #1875 from micronaut-projects/codex/pyronaut-local…
n0tl3ss Sep 30, 2026
c2f2641
Merge pull request #1877 from micronaut-projects/codex/gradle94-pytho…
n0tl3ss Sep 30, 2026
ba219a9
Exclude unsupported Python variant from Azure secrets guide
n0tl3ss Sep 30, 2026
b9f0485
Merge pull request #1881 from micronaut-projects/codex/guide-ci-azure…
n0tl3ss Sep 30, 2026
cf0bb84
Make cache guide Python-facing
n0tl3ss Sep 30, 2026
4a49cd6
Merge pull request #1883 from micronaut-projects/codex/micronaut-cach…
n0tl3ss Sep 30, 2026
0adc821
many-to-many guides
sdelamo Sep 30, 2026
a80a5e3
Use Python Pageable import in JDBC guide
n0tl3ss Sep 30, 2026
bebcecb
Merge pull request #1884 from micronaut-projects/codex/python-data-jd…
n0tl3ss Sep 30, 2026
a99e963
Use Python optionals in one-to-many guide
n0tl3ss Sep 30, 2026
9025413
Merge pull request #1885 from micronaut-projects/fix/pythonic-one-to-…
n0tl3ss Sep 30, 2026
2e9ac31
micronaut-security-api-key
sdelamo Sep 30, 2026
4a1d44f
remove views duplication
sdelamo Sep 30, 2026
41d1b89
Use pytest for all generated Python guides
sdelamo Sep 30, 2026
65cf5e5
micronaut-security-session
sdelamo Sep 30, 2026
d28acba
micronaut-views-thymeleaf
sdelamo Sep 30, 2026
af3959e
micronaut-scheduled
sdelamo Sep 30, 2026
4f297f5
micronaut-cache python
sdelamo Sep 30, 2026
1a0b54d
server-filter-request python
sdelamo Sep 30, 2026
855ad2f
Use async Python APIs in Eureka recommendation guide (#1886)
n0tl3ss Oct 1, 2026
03a26bf
Improve Python token propagation examples (#1873)
n0tl3ss Oct 1, 2026
e56e3ec
Track Pyronaut helper as a Python test task input (#1888)
n0tl3ss Oct 1, 2026
5055a31
Merge pull request #1859 from micronaut-projects/fix/python-sendgrid-…
n0tl3ss Oct 1, 2026
3414ddc
Avoid following symlinks during guide cleanup and licensing
n0tl3ss Oct 1, 2026
91a608e
Merge pull request #1891 from micronaut-projects/fix/guide-generation…
n0tl3ss Oct 1, 2026
ecba555
Remove AOT-only properties from Python OIDC tests
n0tl3ss Oct 1, 2026
e391748
Merge pull request #1892 from micronaut-projects/fix/python-oidc-runt…
n0tl3ss Oct 1, 2026
d9610e2
Use Python ModelAndView API in content negotiation guide
n0tl3ss Oct 1, 2026
3b3adad
Merge pull request #1893 from micronaut-projects/codex/content-negoti…
n0tl3ss Oct 1, 2026
0a58af3
Use Python collections and context-bound clients in guide samples
n0tl3ss Oct 1, 2026
38007b5
Merge pull request #1894 from micronaut-projects/codex/pythonic-guide…
n0tl3ss Oct 1, 2026
b6c1a4f
Use Python PDF libraries and native collections in guide samples
n0tl3ss Oct 2, 2026
32ac2e0
Merge pull request #1895 from micronaut-projects/codex/pythonic-guide…
n0tl3ss Oct 2, 2026
ff76eda
Use Python bean lookup and builtin collection annotations in guide sa…
n0tl3ss Oct 2, 2026
4b45dc1
Merge pull request #1896 from micronaut-projects/codex/pythonic-mongo…
n0tl3ss Oct 4, 2026
e48ca45
Enable appropriate existing Python guide implementations
n0tl3ss Oct 4, 2026
7e85374
Make Python guide samples use Python-facing APIs and nullable queries
n0tl3ss Oct 5, 2026
0d239bf
Use normal configuration imports instead of bootstrap in Python cloud…
n0tl3ss Oct 5, 2026
82a2e46
Make configuration security and OpenAPI samples Python-facing
n0tl3ss Oct 5, 2026
20eece6
Order rendering after all language documentation tasks
n0tl3ss Oct 5, 2026
77b87e2
Use nullable repositories and built-in SMTP authentication in Python …
n0tl3ss Oct 5, 2026
f673351
Use Micronaut Test Resources for Python HTTP integration fixtures
n0tl3ss Oct 5, 2026
39cd636
remove pyenv steps
sdelamo Oct 5, 2026
7abab0d
Merge master into python-guide-support
n0tl3ss Oct 5, 2026
8cee5e5
Merge latest master test-script cleanup into python-guide-support
n0tl3ss Oct 5, 2026
658f499
Merge latest python-guide-support into Python guide enablement
n0tl3ss Oct 5, 2026
5bb6f94
Make Oracle database and streaming guides Python-facing
n0tl3ss Oct 5, 2026
3f00fd0
Use Python async HTTP clients and Micronaut authentication
n0tl3ss Oct 6, 2026
67c0723
jvmFeatures
sdelamo Oct 6, 2026
ebad5ea
Refresh declared outputs before language-filtered sample generation
n0tl3ss Oct 6, 2026
15d3fae
excel python
sdelamo Oct 6, 2026
c069abb
poi 4.0.2
sdelamo Oct 6, 2026
fa3cf4a
Use Python async clients in the Zipkin recommendation guide
n0tl3ss Oct 6, 2026
32b6a1b
Support Python H2 development alongside Oracle Streaming repositories
n0tl3ss Oct 6, 2026
dda3ccf
Use Python resolver methods and snake_case GraphQL models
n0tl3ss Oct 6, 2026
4ceb4e9
Support documentation-only cloud database guides in Python
n0tl3ss Oct 6, 2026
3d0c523
Add Python Oracle ETag optimistic locking sample
n0tl3ss Oct 6, 2026
0c2a5b2
Merge pull request #1900 from micronaut-projects/codex/guide-language…
n0tl3ss Oct 6, 2026
baadaa7
Merge pull request #1898 from micronaut-projects/codex/enable-python-…
n0tl3ss Oct 6, 2026
f674459
Merge pull request #1902 from micronaut-projects/codex/pythonic-adapt…
n0tl3ss Oct 6, 2026
655fc2f
Merge pull request #1904 from micronaut-projects/codex/pythonic-adapt…
n0tl3ss Oct 6, 2026
e1bb506
Merge pull request #1906 from micronaut-projects/codex/python-languag…
n0tl3ss Oct 6, 2026
da5c231
Merge pull request #1907 from micronaut-projects/codex/pythonic-adapt…
n0tl3ss Oct 6, 2026
b490e43
Merge pull request #1908 from micronaut-projects/codex/python-oracle-…
n0tl3ss Oct 6, 2026
1f292ee
Merge pull request #1909 from micronaut-projects/codex/pythonic-graph…
n0tl3ss Oct 6, 2026
8584709
Merge pull request #1911 from micronaut-projects/codex/python-cloud-d…
n0tl3ss Oct 6, 2026
d67abe7
Merge pull request #1912 from micronaut-projects/codex/python-oracle-…
n0tl3ss Oct 6, 2026
9233def
Merge master into Python guide support, preserving JVM and Python fea…
n0tl3ss Oct 6, 2026
afc3e54
Merge pull request #1913 from micronaut-projects/codex/integrate-pyth…
n0tl3ss Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .agents/skills/micronaut-guides-authoring/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Each app entry usually includes:

Feature names must be valid Micronaut Starter features. If a guide needs a feature not provided by Starter, add a feature class under `buildSrc/src/main/java/io/micronaut/guides/feature` and keep dependency coordinates in `buildSrc/src/main/resources/pom.xml`.

Use optional metadata only when needed: `languages`, `buildTools`, `testFramework`, `minimumJavaVersion`, `maximumJavaVersion`, `cloud`, `publish`, `base`, `zipIncludes`, `skipGradleTests`, `skipMavenTests`, `env`, app-specific `javaFeatures`, `kotlinFeatures`, `groovyFeatures`, `invisibleFeatures`, `excludeTest`, and `excludeSource`.
Use optional metadata only when needed: `languages`, `buildTools`, `testFramework`, `minimumJavaVersion`, `maximumJavaVersion`, `cloud`, `publish`, `base`, `zipIncludes`, `skipGradleTests`, `skipMavenTests`, `env`, app-specific `javaFeatures`, `kotlinFeatures`, `groovyFeatures`, `jvmFeatures` (shared by Java, Kotlin, and Groovy but not Python), `invisibleFeatures`, `excludeTest`, and `excludeSource`.

Omit `languages` when a guide supports all default languages: Java, Groovy, and Kotlin. Add `languages` only when the guide intentionally narrows support to a subset.

Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/python-guides.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Validate Python Guides
on:
push:
branches:
- python
paths:
- '.github/workflows/python-guides.yml'
- 'buildSrc/**'
- 'guides/**'
- 'settings.gradle'
- 'src/docs/common/**'
pull_request:
branches:
- python
paths:
- '.github/workflows/python-guides.yml'
- 'buildSrc/**'
- 'guides/**'
- 'settings.gradle'
- 'src/docs/common/**'

jobs:
validate:
name: Generate Python Guides
runs-on: ubuntu-latest
env:
LOCAL_GIT_MICRONAUT_STARTER: ${{ github.workspace }}/micronaut-starter
steps:
- uses: actions/checkout@v6.0.2
- uses: actions/checkout@v6.0.2
with:
repository: n0tl3ss/micronaut-starter
# Pin micronaut-starter#3136 until its fix is merged upstream.
ref: 763920aaa46c06aeebbaba995a66fc3bef643801
path: micronaut-starter
- uses: actions/setup-java@v5.2.0
with:
distribution: adopt
java-version: '25'
- uses: actions/cache@v5.0.5
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Validate build infrastructure
run: ./gradlew buildSrc:test --stacktrace
- name: Generate Python guides
run: ./gradlew generateAllPythonGuideTestScripts --stacktrace
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ To build a single guide, run the dynamic task created by `GuidesPlugin`; convert
./gradlew micronautHttpClientBuild
```

### Use local composite builds

Local Micronaut Core, Pyronaut, and Starter checkouts can be included with
either the matching Gradle property or environment variable:

| Checkout | Gradle property | Environment variable |
| --- | --- | --- |
| Micronaut Core | `local.git.micronaut-core` | `LOCAL_GIT_MICRONAUT_CORE` |
| Pyronaut | `local.git.pyronaut` | `LOCAL_GIT_PYRONAUT` |
| Micronaut Starter | `local.git.micronaut-starter` | `LOCAL_GIT_MICRONAUT_STARTER` |

For example: `./gradlew -Plocal.git.pyronaut=/path/to/pyronaut help`.
Gradle properties take precedence when both forms are set. Pyronaut's GraalPy
bundle repository is discovered from a local Core or Pyronaut checkout when
available; set `micronaut.graalpy.bundle.repo` to override it.

## Create a new guide

For a high-level overview of the guides infrastructure, take a look at this [blog post](https://micronaut.io/2021/04/12/improving-the-micronaut-guides-infrastructure/).
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ repositories {
}

ext {
micronautStarterVersion = "5.2.0"
micronautStarterVersion = "5.2.0-SNAPSHOT"
}

dependencies {
Expand Down Expand Up @@ -83,5 +83,5 @@ tasks.named('test', Test) {
}

tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-Amicronaut.jsonschema.baseUri=https://micronaut-projects.github.io/micronaut-guides/schemas") // (1)
options.compilerArgs.add("-Amicronaut.jsonschema.baseUri=https://guides.micronaut.io/schemas") // (1)
}
10 changes: 10 additions & 0 deletions buildSrc/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

rootProject.name = "micronaut-guides-buildsrc"

apply from: file('../gradle/local-composite-builds.gradle')
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import org.gradle.api.GradleException
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.io.IOException
import java.nio.file.Files
import java.nio.file.FileVisitResult
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.SimpleFileVisitor
import java.time.LocalDate
import java.nio.file.attribute.BasicFileAttributes
import java.time.LocalDate
import java.util.Locale
import java.util.regex.Pattern

import static groovy.io.FileType.FILES
Expand Down Expand Up @@ -153,7 +155,7 @@ class GuideProjectGenerator implements AutoCloseable {
String packageAndName = BASE_PACKAGE + '.' + app.name()

guidesGenerator.generateAppIntoDirectory(destination, app.applicationType(), packageAndName, app.framework(),
appFeatures, buildTool, app.testFramework() ?: testFramework, lang, javaVersion)
appFeatures, buildTool, lang == PYTHON ? PYTEST : (app.testFramework() ?: testFramework), lang, javaVersion)

if (lang == PYTHON) {
movePythonResources(destinationPath)
Expand All @@ -166,13 +168,17 @@ class GuideProjectGenerator implements AutoCloseable {

copyGuideSourceFiles(inputDir, destinationPath, appName, guidesOption.language.toString())

if (buildTool == BuildTool.PYRONAUT && lang == PYTHON) {
ensurePyronautBlockingExecutorConfig(destinationPath.resolve('config/application.toml'))
}

if (app.excludeSource()) {
for (String mainSource : app.excludeSource()) {
File f = fileToDelete(destination, GuideAsciidocGenerator.mainPath(appName, mainSource), guidesOption)
File f = fileToDelete(destination, GuideAsciidocGenerator.mainPath(appName, mainSource, guidesOption), guidesOption)
if (f.exists()) {
f.delete()
}
f = fileToDelete(destination, GuideAsciidocGenerator.mainPath(EMPTY_STRING, mainSource), guidesOption)
f = fileToDelete(destination, GuideAsciidocGenerator.mainPath(EMPTY_STRING, mainSource, guidesOption), guidesOption)
if (f.exists()) {
f.delete()
}
Expand All @@ -181,14 +187,20 @@ class GuideProjectGenerator implements AutoCloseable {

if (app.excludeTest()) {
for (String testSource : app.excludeTest()) {
File f = fileToDelete(destination, GuideAsciidocGenerator.testPath(appName, testSource, testFramework), guidesOption)
File f = fileToDelete(destination, GuideAsciidocGenerator.testPath(appName, testSource, guidesOption), guidesOption)
if (f.exists()) {
f.delete()
}
f = fileToDelete(destination, GuideAsciidocGenerator.testPath(EMPTY_STRING, testSource, testFramework), guidesOption)
f = fileToDelete(destination, GuideAsciidocGenerator.testPath(EMPTY_STRING, testSource, guidesOption), guidesOption)
if (f.exists()) {
f.delete()
}
if (buildTool == BuildTool.PYRONAUT && lang == PYTHON) {
f = new File(destination, "tests/${pythonTestModuleName(testSource)}.${lang.extension}")
if (f.exists()) {
f.delete()
}
}
}
}

Expand All @@ -198,11 +210,41 @@ class GuideProjectGenerator implements AutoCloseable {
copyFile(inputDir, destinationRoot, zipInclude)
}
}
if (buildTool == BuildTool.PYRONAUT && lang == PYTHON) {
removePythonPackageMarkerFiles(destination)
}
addLicenses(new File(outputDir.absolutePath, folder))
}
}
}

private static void removePythonPackageMarkerFiles(File destination) {
Files.walkFileTree(destination.toPath(), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isRegularFile() && file.fileName.toString() == '__init__.py') {
Files.delete(file)
}
FileVisitResult.CONTINUE
}
})
}

private static String pythonModuleName(String target) {
if (target.contains('_') || target == target.toLowerCase(Locale.ENGLISH)) {
return target
}
target.replaceAll(/([a-z0-9])([A-Z])/, '$1_$2')
.replaceAll(/([A-Z]+)([A-Z][a-z])/, '$1_$2')
.toLowerCase(Locale.ENGLISH)
}

private static String pythonTestModuleName(String target) {
String normalized = target.endsWith('Test') ? target.substring(0, target.length() - 'Test'.length()) : target
normalized = pythonModuleName(normalized)
normalized.startsWith('test_') ? normalized : "test_${normalized}"
}

private static void movePythonResources(Path destinationPath) {
movePythonResourceFolder(destinationPath, 'views')
movePythonResourceFolder(destinationPath, 'static')
Expand Down Expand Up @@ -239,14 +281,18 @@ class GuideProjectGenerator implements AutoCloseable {

void addLicenses(File folder) {
String licenseHeader = licenseHeaderText()
folder.eachFileRecurse (FILES) { file ->
if (
(file.path.endsWith(EXTENSION_JAVA) || file.path.endsWith(EXTENSION_GROOVY) || file.path.endsWith(EXTENSION_KT))
&& !file.text.contains("Licensed under")
) {
file.text = licenseHeader + file.text
Files.walkFileTree(folder.toPath(), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
File file = path.toFile()
if (attrs.isRegularFile()
&& (file.path.endsWith(EXTENSION_JAVA) || file.path.endsWith(EXTENSION_GROOVY) || file.path.endsWith(EXTENSION_KT))
&& !file.text.contains("Licensed under")) {
file.text = licenseHeader + file.text
}
FileVisitResult.CONTINUE
}
}
})
}

@Memoized
Expand All @@ -263,35 +309,27 @@ class GuideProjectGenerator implements AutoCloseable {
// look for a common 'src' directory shared by multiple languages and copy those files first
final String srcFolder = 'src'
Path srcPath = Paths.get(inputDir.absolutePath, appName, srcFolder)
Path sourcePath = Paths.get(inputDir.absolutePath, appName, language)
if (Files.exists(srcPath)) {
Path resourcesPath = srcPath.resolve('main').resolve('resources')
if (language == PYTHON.toString() && Files.exists(resourcesPath)) {
Path configPath = Paths.get(destinationPath.toString(), 'config')
Files.createDirectories(configPath)
Files.walkFileTree(resourcesPath, new CopyFileVisitor(configPath))
Path destinationResourcesPath = Paths.get(destinationPath.toString(), srcFolder, 'main', 'resources')
Files.walkFileTree(resourcesPath, new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
Files.deleteIfExists(destinationResourcesPath.resolve(resourcesPath.relativize(file)))
FileVisitResult.CONTINUE
}
})
if (language == PYTHON.toString()) {
copySharedPythonResources(srcPath, sourcePath, destinationPath)
Path pythonResources = srcPath.resolve('main').resolve('resources')
Path pythonTestResources = srcPath.resolve('test').resolve('resources')
Path pythonTestResourcesLegacy = srcPath.resolve('test-resources')
Files.walkFileTree(srcPath, new CopyFileVisitor(Paths.get(destinationPath.toString(), srcFolder)) {
@Override
FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (dir.equals(resourcesPath)) {
if (dir.equals(pythonResources) || dir.equals(pythonTestResources) || dir.equals(pythonTestResourcesLegacy)) {
return FileVisitResult.SKIP_SUBTREE
}
super.preVisitDirectory(dir, attrs)
return super.preVisitDirectory(dir, attrs)
}
})
} else {
Files.walkFileTree(srcPath, new CopyFileVisitor(Paths.get(destinationPath.toString(), srcFolder)))
}
}

Path sourcePath = Paths.get(inputDir.absolutePath, appName, language)
if (!Files.exists(sourcePath)) {
sourcePath.toFile().mkdir()
}
Expand All @@ -303,6 +341,80 @@ class GuideProjectGenerator implements AutoCloseable {
}
}

private static void ensurePyronautBlockingExecutorConfig(Path applicationConfig) {
String config = Files.exists(applicationConfig) ? Files.readString(applicationConfig) : ''
if (config.contains('[micronaut.executors.blocking]')) {
return
}
String suffix = config.isEmpty() || config.endsWith('\n') ? '' : '\n'
Files.createDirectories(applicationConfig.parent)
Files.writeString(applicationConfig, config + suffix + '''
[micronaut.executors.blocking]
type = 'CACHED'
virtual = false
''')
}

private static void copySharedPythonResources(Path srcPath, Path sourcePath, Path destinationPath) {
Path pythonConfigPath = sourcePath.resolve('config')
copySharedPythonResourceDirectory(srcPath.resolve('main/resources'), destinationPath.resolve('config'), pythonConfigPath)
Path pythonTestConfigPath = sourcePath.resolve('tests-config')
copySharedPythonResourceDirectory(srcPath.resolve('test/resources'), destinationPath.resolve('tests-config'), pythonTestConfigPath)
copySharedPythonResourceDirectory(srcPath.resolve('test-resources'), destinationPath.resolve('tests-config'), pythonTestConfigPath)
}

private static void copySharedPythonResourceDirectory(Path resourcePath, Path destinationPath, Path pythonSpecificPath) {
if (!Files.exists(resourcePath)) {
return
}
def paths = Files.walk(resourcePath)
try {
paths.filter(Files::isRegularFile).forEach { Path source ->
Path relative = resourcePath.relativize(source)
if (!GuideUtils.isBootstrapConfiguration(relative) && !isOverriddenByPythonConfig(relative, pythonSpecificPath)) {
Path destination = destinationPath.resolve(relative)
Files.createDirectories(destination.parent)
Files.copy(source, destination, REPLACE_EXISTING)
}
}
} finally {
paths.close()
}
}

private static boolean isOverriddenByPythonConfig(Path relative, Path pythonSpecificPath) {
if (Files.exists(pythonSpecificPath.resolve(relative))) {
return true
}
if (relative.nameCount != 1 || !Files.isDirectory(pythonSpecificPath)) {
return false
}
String filename = relative.fileName.toString()
int extensionIndex = filename.lastIndexOf('.')
if (extensionIndex < 1) {
return false
}
String basename = filename.substring(0, extensionIndex)
if (!isEnvironmentConfigBasename(basename)) {
return false
}
def paths = Files.list(pythonSpecificPath)
try {
paths.filter(Files::isRegularFile)
.map { Path path -> path.fileName.toString() }
.anyMatch { String name -> name.startsWith("${basename}.") }
} finally {
paths.close()
}
}

private static boolean isEnvironmentConfigBasename(String basename) {
basename == 'application' ||
basename.startsWith('application-') ||
basename == 'bootstrap' ||
basename.startsWith('bootstrap-')
}

private static File fileToDelete(File destination, String path, GuidesOption guidesOption) {
Paths.get(destination.absolutePath, path
.replace("@lang@", guidesOption.language.toString())
Expand Down Expand Up @@ -360,12 +472,12 @@ class GuideProjectGenerator implements AutoCloseable {

private static TestFramework testFrameworkOption(@NonNull Language language,
@Nullable TestFramework testFramework) {
if (testFramework != null) {
return testFramework
}
if (language == PYTHON) {
return PYTEST
}
if (testFramework != null) {
return testFramework
}
if (language == GROOVY) {
return SPOCK
}
Expand Down
Loading
Loading