You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/10/16 17:08:49 UTC
(grails-core) branch 8.0.x updated (38216edf7b -> caa413f5cb)
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a change to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
from 38216edf7b Merge pull request #16625 from MartinJesusDev/fix/16600-trait-default-values-8.0.x
add 9ce99cde3f Return the page's property keys from the layout Content adapter
add c471280c53 Combine patterns and extract template variables as AntPathMatcher documents
add 81e4dc8373 Test File values for null instead of truth in the file system interaction
add 18d8daf59e Return the relative path of a spring script from getPathFromRoot
add e77e3cd9cc Report a misplaced endObject as such and let JSONTokener.next(n) read to the end
add bd674957eb Resolve every CompositeConfig view from the config that has the key
add 3b960eda28 Merge branch '7.0.x' into 7.1.x
add bd36ccad7a Merge branch '7.1.x' into 7.2.x
new caa413f5cb Merge branch '7.2.x' into 8.0.x
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../org/grails/io/support/AntPathMatcher.java | 36 ++++++-----
.../grails/io/support/AntPathMatcherSpec.groovy | 29 ++++++++-
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 75 ++++++++++++++++++++++
.../core/cli/io/FileSystemInteractionImpl.groovy | 9 +--
.../groovy/org/grails/config/CompositeConfig.java | 59 ++++++++++-------
.../cli/io/FileSystemInteractionImplSpec.groovy | 75 ++++++++++++++++++++++
.../org/grails/config/CompositeConfigSpec.groovy | 40 +++++++++---
.../org/grails/io/support/GrailsResourceUtils.java | 4 +-
.../io/support/GrailsResourceUtilsSpec.groovy | 8 +--
.../web/layout/TokenizedHTMLPage2Content.java | 2 +-
.../web/layout/GrailsHTMLPageParserSpec.groovy | 1 +
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 22 +++++++
.../templates/TemplateRendererImplSpec.groovy | 4 +-
.../groovy/org/grails/web/json/JSONTokener.java | 2 +-
.../groovy/org/grails/web/json/JSONWriter.java | 3 +-
.../org/grails/web/json/PrettyPrintJSONWriter.java | 6 +-
.../org/grails/web/json/JSONTokenerApiSpec.groovy | 10 +++
.../org/grails/web/json/JSONWriterApiSpec.groovy | 5 +-
.../web/json/PrettyPrintJSONWriterApiSpec.groovy | 19 +++++-
21 files changed, 344 insertions(+), 83 deletions(-)
create mode 100644 grails-core-cli-legacy/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
create mode 100644 grails-core/src/test-cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImplSpec.groovy
(grails-core) 01/01: Merge branch '7.2.x' into 8.0.x
Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit caa413f5cbc97f9ab8facb6f6916b972d2f3ec45
Merge: 38216edf7b bd36ccad7a
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Fri Oct 16 13:03:49 2026 -0400
Merge branch '7.2.x' into 8.0.x
.../org/grails/io/support/AntPathMatcher.java | 36 ++++++-----
.../grails/io/support/AntPathMatcherSpec.groovy | 29 ++++++++-
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 75 ++++++++++++++++++++++
.../core/cli/io/FileSystemInteractionImpl.groovy | 9 +--
.../groovy/org/grails/config/CompositeConfig.java | 59 ++++++++++-------
.../cli/io/FileSystemInteractionImplSpec.groovy | 75 ++++++++++++++++++++++
.../org/grails/config/CompositeConfigSpec.groovy | 40 +++++++++---
.../org/grails/io/support/GrailsResourceUtils.java | 4 +-
.../io/support/GrailsResourceUtilsSpec.groovy | 8 +--
.../web/layout/TokenizedHTMLPage2Content.java | 2 +-
.../web/layout/GrailsHTMLPageParserSpec.groovy | 1 +
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 22 +++++++
.../templates/TemplateRendererImplSpec.groovy | 4 +-
.../groovy/org/grails/web/json/JSONTokener.java | 2 +-
.../groovy/org/grails/web/json/JSONWriter.java | 3 +-
.../org/grails/web/json/PrettyPrintJSONWriter.java | 6 +-
.../org/grails/web/json/JSONTokenerApiSpec.groovy | 10 +++
.../org/grails/web/json/JSONWriterApiSpec.groovy | 5 +-
.../web/json/PrettyPrintJSONWriterApiSpec.groovy | 19 +++++-
21 files changed, 344 insertions(+), 83 deletions(-)
diff --cc grails-core-cli-legacy/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
index 5d8095c3f5,0000000000..d5efdbfe6b
mode 100644,000000..100644
--- a/grails-core-cli-legacy/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
+++ b/grails-core-cli-legacy/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
@@@ -1,284 -1,0 +1,285 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package grails.dev.commands.io
+
+import groovy.transform.CompileStatic
+
+import grails.util.BuildSettings
+import org.grails.io.support.DefaultResourceLoader
+import org.grails.io.support.FileSystemResource
+import org.grails.io.support.GrailsResourceUtils
+import org.grails.io.support.PathMatchingResourcePatternResolver
+import org.grails.io.support.Resource
+import org.grails.io.support.ResourceLoader
+import org.grails.io.support.ResourceLocator
+import org.grails.io.support.SpringIOUtils
+
+/**
+ * Utility methods exposed to scripts for interacting with resources (found on the file system or jars) and the file system
+ *
+ * @author Graeme Rocher
+ * @since 3.0
+ * @deprecated since 8.0, use {@link org.apache.grails.core.cli.io.FileSystemInteractionImpl}. Retained only for backwards compatibility with Grails 7 command plugins and slated for removal in a future major release.
+ */
+@Deprecated
+@CompileStatic
+class FileSystemInteractionImpl implements FileSystemInteraction {
+
+ File baseDir
+ ResourceLoader resourceLoader
+ PathMatchingResourcePatternResolver resourcePatternResolver
+ ResourceLocator resourceLocator
+
+ FileSystemInteractionImpl(File baseDir, ResourceLoader resourceLoader = new DefaultResourceLoader()) {
+ this.baseDir = baseDir
+ this.resourceLoader = resourceLoader
+ this.resourceLocator = new ResourceLocator()
+ this.resourceLocator.setSearchLocation(baseDir.absolutePath)
+ this.resourcePatternResolver = new PathMatchingResourcePatternResolver(resourceLoader)
+ }
+
+ /**
+ * Makes a directory
+ *
+ * @param path The path to the directory
+ */
+ @Override
+ FileSystemInteractionImpl mkdir(path) {
+ file(path)?.mkdirs()
+ return this
+ }
+
+ /**
+ * Deletes a file
+ *
+ * @param path The path to the file
+ */
+ @Override
+ FileSystemInteractionImpl delete(path) {
+ file(path)?.delete()
+ return this
+ }
+
+ /**
+ * Allows Gradle style simple copy specs
+ *
+ * @param callable The callable
+ * @return this
+ */
+ @Override
+ FileSystemInteractionImpl copy(@DelegatesTo(FileSystemInteraction.CopySpec) Closure callable) {
+ FileSystemInteraction.CopySpec spec = new FileSystemInteraction.CopySpec()
+ callable.delegate = spec
+ callable.call()
- if (spec.from && spec.into) {
++ if (spec.from && spec.into != null) {
+ if (spec.from instanceof Iterable) {
+ copyAll((Iterable) spec.from, spec.into)
+ }
+ else {
+ copy(spec.from, spec.into)
+ }
+ }
+ return this
+ }
+ /**
+ * Copies a resource to the target destination
+ *
+ * @param path The path
+ * @param destination The destination
+ */
+ @Override
+ FileSystemInteractionImpl copy(path, destination) {
+ def from = resource(path)
+ def to = file(destination)
+ copy(from, to)
+ return this
+ }
+
+ /**
+ * Copies resources to the target destination
+ *
+ * @param path The path
+ * @param destination The destination
+ */
+ @Override
+ FileSystemInteractionImpl copyAll(Iterable resources, destination) {
+ mkdir(destination)
+ for (path in resources) {
+ def from = resource(path)
+ def to = file(destination)
+ copy(from, to)
+ }
+ return this
+ }
+
+ /**
+ * Copy a Resource from the given location to the given directory or location
+ *
+ * @param from The resource to copy
+ * @param to The location to copy to
+ * @return The {@FileSystemInteraction} instance
+ */
+ @Override
+ FileSystemInteractionImpl copy(Resource from, File to) {
+ if (!to?.exists()) mkdir(to)
+ if (from && to) {
+ if (to.isDirectory()) {
+ mkdir(to)
+ to = new File(to, from.filename)
+ }
+ SpringIOUtils.copy(from, to)
+ println("Copied ${from.filename} to location ${to.canonicalPath}")
+ }
+ return this
+ }
+
+ /**
+ * Obtain a file for the given path
+ *
+ * @param path The path
+ * @return The file
+ */
+ @Override
+ File file(Object path) {
+ if (path instanceof File) return (File) path
+ else if (path instanceof Resource) return ((Resource) path).file
+ else {
- new File(baseDir ?: new File('.'), path.toString())
++ new File(baseDir != null ? baseDir : new File('.'), path.toString())
+ }
+ }
+
+ /**
+ * @return The target build directory
+ */
+ @Override
+ File getBuildDir() {
+ BuildSettings.TARGET_DIR
+ }
+
+ /**
+ * @return The directory where resources are processed to
+ */
+ @Override
+ File getResourcesDir() {
+ BuildSettings.RESOURCES_DIR
+ }
+
+ /**
+ * @return The directory where classes are compiled to
+ */
+ @Override
+ File getClassesDir() {
+ BuildSettings.CLASSES_DIR
+ }
+
+ /**
+ * Finds a source file for the given class name
+ * @param className The class name
+ * @return The source resource
+ */
+ @Override
+ Resource source(String className) {
+ resourceLocator.findResourceForClassName(className)
+ }
+
+ /**
+ * The class name of the given resource
+ *
+ * @param resource The resource
+ * @return The class name
+ */
+ String className(Resource resource) {
+ GrailsResourceUtils.getClassName(resource)
+ }
+
+ /**
+ * Obtain a resource for the given path
+ * @param path The path
+ * @return The resource
+ */
+ @Override
+ Resource resource(Object path) {
- if (!path) return null
++ if (path == null || path.toString().isEmpty()) return null
+ if (path instanceof Resource) return (Resource) path
+ def f = file(path)
+ if (f?.exists() && f.isFile()) {
+ return new FileSystemResource(f)
+ }
+ else {
+ def pathStr = path.toString()
+ def resource = resourceLoader.getResource(pathStr)
+ if (resource.exists()) {
+ return resource
+ }
+ else {
+ def allResources = resources(pathStr)
+ if (allResources) {
+ return allResources[0]
+ }
+ else {
+ return resource
+ }
+ }
+ }
+ }
+
+ /**
+ * Obtain resources for the given pattern
+ *
+ * @param pattern The pattern
+ * @return The resources
+ */
+ @Override
+ Collection<Resource> resources(String pattern) {
+ try {
+ return resourcePatternResolver.getResources(pattern).toList()
+ } catch (e) {
+ return []
+ }
+ }
+
+ /**
+ * Obtain the path of the resource relative to the current project
+ *
+ * @param path The path to inspect
+ * @return The relative path
+ */
+ @Override
+ String projectPath(Object path) {
+ def file = file(path)
- if (file) {
++ // a File that does not exist is false on Groovy 5 and later, so test for null explicitly
++ if (file != null) {
+ def basePath = baseDir.canonicalPath
+ return (file.canonicalPath - basePath).substring(1)
+ }
+ return ''
+ }
+
+ /**
+ * Get files matching the given pattern
+ *
+ * @param pattern The pattern
+ * @return the files
+ */
+ @Override
+ Collection<File> files(String pattern) {
+ resources(pattern).collect() { Resource res -> res.file }
+ }
+
+}
diff --cc grails-core-cli-legacy/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
index 0000000000,ddd22a28f0..ddd22a28f0
mode 000000,100644..100644
--- a/grails-core-cli-legacy/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
+++ b/grails-core-cli-legacy/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
diff --cc grails-core/src/cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImpl.groovy
index adebd52b9f,0000000000..25deb49dd9
mode 100644,000000..100644
--- a/grails-core/src/cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImpl.groovy
+++ b/grails-core/src/cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImpl.groovy
@@@ -1,281 -1,0 +1,282 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.core.cli.io
+
+import groovy.transform.CompileStatic
+
+import grails.util.BuildSettings
+import org.grails.io.support.DefaultResourceLoader
+import org.grails.io.support.FileSystemResource
+import org.grails.io.support.GrailsResourceUtils
+import org.grails.io.support.PathMatchingResourcePatternResolver
+import org.grails.io.support.Resource
+import org.grails.io.support.ResourceLoader
+import org.grails.io.support.ResourceLocator
+import org.grails.io.support.SpringIOUtils
+
+/**
+ * Utility methods exposed to scripts for interacting with resources (found on the file system or jars) and the file system
+ *
+ * @since 3.0
+ */
+@CompileStatic
+class FileSystemInteractionImpl implements FileSystemInteraction {
+
+ File baseDir
+ ResourceLoader resourceLoader
+ PathMatchingResourcePatternResolver resourcePatternResolver
+ ResourceLocator resourceLocator
+
+ FileSystemInteractionImpl(File baseDir, ResourceLoader resourceLoader = new DefaultResourceLoader()) {
+ this.baseDir = baseDir
+ this.resourceLoader = resourceLoader
+ this.resourceLocator = new ResourceLocator()
+ this.resourceLocator.setSearchLocation(baseDir.absolutePath)
+ this.resourcePatternResolver = new PathMatchingResourcePatternResolver(resourceLoader)
+ }
+
+ /**
+ * Makes a directory
+ *
+ * @param path The path to the directory
+ */
+ @Override
+ FileSystemInteractionImpl mkdir(path) {
+ file(path)?.mkdirs()
+ return this
+ }
+
+ /**
+ * Deletes a file
+ *
+ * @param path The path to the file
+ */
+ @Override
+ FileSystemInteractionImpl delete(path) {
+ file(path)?.delete()
+ return this
+ }
+
+ /**
+ * Allows Gradle style simple copy specs
+ *
+ * @param callable The callable
+ * @return this
+ */
+ @Override
+ FileSystemInteractionImpl copy(@DelegatesTo(FileSystemInteraction.CopySpec) Closure callable) {
+ FileSystemInteraction.CopySpec spec = new FileSystemInteraction.CopySpec()
+ callable.delegate = spec
+ callable.call()
- if (spec.from && spec.into) {
++ if (spec.from && spec.into != null) {
+ if (spec.from instanceof Iterable) {
+ copyAll((Iterable) spec.from, spec.into)
+ }
+ else {
+ copy(spec.from, spec.into)
+ }
+ }
+ return this
+ }
+ /**
+ * Copies a resource to the target destination
+ *
+ * @param path The path
+ * @param destination The destination
+ */
+ @Override
+ FileSystemInteractionImpl copy(path, destination) {
+ def from = resource(path)
+ def to = file(destination)
+ copy(from, to)
+ return this
+ }
+
+ /**
+ * Copies resources to the target destination
+ *
+ * @param path The path
+ * @param destination The destination
+ */
+ @Override
+ FileSystemInteractionImpl copyAll(Iterable resources, destination) {
+ mkdir(destination)
+ for (path in resources) {
+ def from = resource(path)
+ def to = file(destination)
+ copy(from, to)
+ }
+ return this
+ }
+
+ /**
+ * Copy a Resource from the given location to the given directory or location
+ *
+ * @param from The resource to copy
+ * @param to The location to copy to
+ * @return The {@FileSystemInteraction} instance
+ */
+ @Override
+ FileSystemInteractionImpl copy(Resource from, File to) {
+ if (!to?.exists()) mkdir(to)
+ if (from && to) {
+ if (to.isDirectory()) {
+ mkdir(to)
+ to = new File(to, from.filename)
+ }
+ SpringIOUtils.copy(from, to)
+ println("Copied ${from.filename} to location ${to.canonicalPath}")
+ }
+ return this
+ }
+
+ /**
+ * Obtain a file for the given path
+ *
+ * @param path The path
+ * @return The file
+ */
+ @Override
+ File file(Object path) {
+ if (path instanceof File) return (File) path
+ else if (path instanceof Resource) return ((Resource) path).file
+ else {
- new File(baseDir ?: new File('.'), path.toString())
++ new File(baseDir != null ? baseDir : new File('.'), path.toString())
+ }
+ }
+
+ /**
+ * @return The target build directory
+ */
+ @Override
+ File getBuildDir() {
+ BuildSettings.TARGET_DIR
+ }
+
+ /**
+ * @return The directory where resources are processed to
+ */
+ @Override
+ File getResourcesDir() {
+ BuildSettings.RESOURCES_DIR
+ }
+
+ /**
+ * @return The directory where classes are compiled to
+ */
+ @Override
+ File getClassesDir() {
+ BuildSettings.CLASSES_DIR
+ }
+
+ /**
+ * Finds a source file for the given class name
+ * @param className The class name
+ * @return The source resource
+ */
+ @Override
+ Resource source(String className) {
+ resourceLocator.findResourceForClassName(className)
+ }
+
+ /**
+ * The class name of the given resource
+ *
+ * @param resource The resource
+ * @return The class name
+ */
+ String className(Resource resource) {
+ GrailsResourceUtils.getClassName(resource)
+ }
+
+ /**
+ * Obtain a resource for the given path
+ * @param path The path
+ * @return The resource
+ */
+ @Override
+ Resource resource(Object path) {
- if (!path) return null
++ if (path == null || path.toString().isEmpty()) return null
+ if (path instanceof Resource) return (Resource) path
+ def f = file(path)
+ if (f?.exists() && f.isFile()) {
+ return new FileSystemResource(f)
+ }
+ else {
+ def pathStr = path.toString()
+ def resource = resourceLoader.getResource(pathStr)
+ if (resource.exists()) {
+ return resource
+ }
+ else {
+ def allResources = resources(pathStr)
+ if (allResources) {
+ return allResources[0]
+ }
+ else {
+ return resource
+ }
+ }
+ }
+ }
+
+ /**
+ * Obtain resources for the given pattern
+ *
+ * @param pattern The pattern
+ * @return The resources
+ */
+ @Override
+ Collection<Resource> resources(String pattern) {
+ try {
+ return resourcePatternResolver.getResources(pattern).toList()
+ } catch (e) {
+ return []
+ }
+ }
+
+ /**
+ * Obtain the path of the resource relative to the current project
+ *
+ * @param path The path to inspect
+ * @return The relative path
+ */
+ @Override
+ String projectPath(Object path) {
+ def file = file(path)
- if (file) {
++ // a File that does not exist is false on Groovy 5 and later, so test for null explicitly
++ if (file != null) {
+ def basePath = baseDir.canonicalPath
+ return (file.canonicalPath - basePath).substring(1)
+ }
+ return ''
+ }
+
+ /**
+ * Get files matching the given pattern
+ *
+ * @param pattern The pattern
+ * @return the files
+ */
+ @Override
+ Collection<File> files(String pattern) {
+ resources(pattern).collect() { Resource res -> res.file }
+ }
+
+}
diff --cc grails-core/src/test-cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImplSpec.groovy
index 0000000000,0000000000..c217b9436c
new file mode 100644
--- /dev/null
+++ b/grails-core/src/test-cli/groovy/org/apache/grails/core/cli/io/FileSystemInteractionImplSpec.groovy
@@@ -1,0 -1,0 +1,75 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * https://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied. See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.grails.core.cli.io
++
++import org.grails.io.support.FileSystemResource
++import spock.lang.Specification
++import spock.lang.TempDir
++
++class FileSystemInteractionImplSpec extends Specification {
++
++ @TempDir
++ File directory
++
++ FileSystemInteractionImpl interaction
++
++ void setup() {
++ interaction = new FileSystemInteractionImpl(directory)
++ }
++
++ def 'files and resources are resolved relative to the project and existing objects are preserved'() {
++ given:
++ def file = new File(directory, 'example.txt')
++ file.text = 'example'
++ def resource = new FileSystemResource(file)
++
++ expect:
++ interaction.file('example.txt') == file
++ interaction.file(file).is(file)
++ interaction.file(resource) == file
++ interaction.resource(resource).is(resource)
++ interaction.resource('example.txt').file == file
++ interaction.resource(null) == null
++ interaction.resource('') == null
++ !interaction.resource('missing.txt').exists()
++ interaction.projectPath(file) == 'example.txt'
++ }
++
++ def 'files that do not exist yet are resolved like existing ones'() {
++ given: 'a project directory that has not been created and files below it'
++ def pending = new File(directory, 'pending')
++ def pendingInteraction = new FileSystemInteractionImpl(pending)
++ def missing = new File(directory, 'nested/missing.txt')
++ def existing = interaction.file('present.txt')
++ existing.text = 'present'
++
++ when:
++ interaction.copy {
++ from(existing)
++ into(new File(directory, 'target'))
++ }
++
++ then:
++ interaction.projectPath(missing) == 'nested/missing.txt'
++ interaction.projectPath('missing.txt') == 'missing.txt'
++ !interaction.resource(missing).exists()
++ pendingInteraction.file('build.gradle') == new File(pending, 'build.gradle')
++ interaction.file('target/present.txt').text == 'present'
++ }
++}