示例#1
0
        private List <string> MatchingFiles(RunningDeployment deployment, string target)
        {
            var files = fileSystem.EnumerateFiles(deployment.CurrentDirectory, target).Select(Path.GetFullPath).ToList();

            foreach (var path in deployment.Variables.GetStrings(SpecialVariables.Action.AdditionalPaths).Where(s => !string.IsNullOrWhiteSpace(s)))
            {
                var pathFiles = fileSystem.EnumerateFiles(path, target).Select(Path.GetFullPath);
                files.AddRange(pathFiles);
            }

            return(files);
        }
        public void ShouldFindAndCallModifyOnTargetFile()
        {
            fileSystem.EnumerateFiles(Arg.Any <string>(), "appsettings.environment.json")
            .Returns(new[] { TestEnvironment.ConstructRootedPath("applications", "Acme", "1.0.0", "appsettings.environment.json") });

            deployment.Variables.Set(SpecialVariables.Package.JsonConfigurationVariablesEnabled, "true");
            deployment.Variables.Set(SpecialVariables.Package.JsonConfigurationVariablesTargets, "appsettings.environment.json");
            var convention = new JsonConfigurationVariablesConvention(configurationVariableReplacer, fileSystem);

            convention.Install(deployment);
            configurationVariableReplacer.Received().ModifyJsonFile(TestEnvironment.ConstructRootedPath("applications", "Acme", "1.0.0", "appsettings.environment.json"), deployment.Variables);
        }
示例#3
0
        void AddParts(Package package, PackageDefinition manifest, string directory, string baseDataStorePath)
        {
            foreach (var file in fileSystem.EnumerateFiles(directory))
            {
                var partUri = new Uri(baseDataStorePath + "/" + Path.GetFileName(file), UriKind.Relative);
                AddContent(package, manifest, partUri, file);
            }

            foreach (var subDirectory in fileSystem.EnumerateDirectories(directory).Select(x => new DirectoryInfo(x)))
            {
                AddParts(package, manifest, subDirectory.FullName, baseDataStorePath + "/" + subDirectory.Name);
            }
        }
示例#4
0
        public void ShouldPerformSubstitutions()
        {
            string substitutionTarget = Path.Combine("subFolder", "config.json");

            fileSystem.EnumerateFiles(StagingDirectory, substitutionTarget).Returns(new[] { Path.Combine(StagingDirectory, substitutionTarget) });

            variables.Set(SpecialVariables.Package.SubstituteInFilesTargets, substitutionTarget);
            variables.Set(SpecialVariables.Package.SubstituteInFilesEnabled, true.ToString());

            CreateConvention().Install(deployment);

            substituter.Received().PerformSubstitution(Path.Combine(StagingDirectory, substitutionTarget), variables);
        }
示例#5
0
        public static void LogDirectoryContents(ILog log, ICalamariFileSystem fileSystem, string workingDirectory, string currentDirectoryRelativePath, int depth = 0)
        {
            var directory = new DirectoryInfo(Path.Combine(workingDirectory, currentDirectoryRelativePath));

            var files = fileSystem.EnumerateFiles(directory.FullName).ToList();

            for (int i = 0; i < files.Count; i++)
            {
                // Only log the first 50 files in each directory
                if (i == 50)
                {
                    log.VerboseFormat("{0}And {1} more files...", Indent(depth), files.Count - i);
                    break;
                }

                var file = files[i];
                log.Verbose(Indent(depth) + Path.GetFileName(file));
            }

            foreach (var subDirectory in fileSystem.EnumerateDirectories(directory.FullName).Select(x => new DirectoryInfo(x)))
            {
                log.Verbose(Indent(depth + 1) + "\\" + subDirectory.Name);
                LogDirectoryContents(log, fileSystem, workingDirectory, Path.Combine(currentDirectoryRelativePath, subDirectory.Name), depth + 1);
            }
        }
        public void Install(RunningDeployment deployment)
        {
            if (!deployment.Variables.GetFlag(SpecialVariables.Package.JsonConfigurationVariablesEnabled))
            {
                return;
            }

            foreach (var target in deployment.Variables.GetPaths(SpecialVariables.Package.JsonConfigurationVariablesTargets))
            {
                if (fileSystem.DirectoryExists(target))
                {
                    Log.Warn($"Skipping JSON variable replacement on '{target}' because it is a directory.");
                    continue;
                }

                var matchingFiles = fileSystem.EnumerateFiles(deployment.CurrentDirectory, target)
                                    .Select(Path.GetFullPath).ToList();

                if (!matchingFiles.Any())
                {
                    Log.Warn($"No files were found that match the replacement target pattern '{target}'");
                    continue;
                }

                foreach (var file in matchingFiles)
                {
                    Log.Info($"Performing JSON variable replacement on '{file}'");
                    jsonConfigurationVariableReplacer.ModifyJsonFile(file, deployment.Variables);
                }
            }
        }
示例#7
0
        public void Install(RunningDeployment deployment)
        {
            if (!deployment.Variables.GetFlag(SpecialVariables.Package.SubstituteInFilesEnabled))
            {
                return;
            }

            foreach (var target in deployment.Variables.GetPaths(SpecialVariables.Package.SubstituteInFilesTargets))
            {
                var matchingFiles = fileSystem.EnumerateFiles(deployment.CurrentDirectory, target)
                                    .Select(Path.GetFullPath).ToList();

                if (!matchingFiles.Any())
                {
                    Log.WarnFormat("No files were found that match the substitution target pattern '{0}'", target);
                    continue;
                }

                foreach (var file in matchingFiles)
                {
                    Log.Info("Performing variable substitution on '{0}'", file);
                    substituter.PerformSubstitution(file, deployment.Variables);
                }
            }
        }
        IEnumerable <string> FindScripts(RunningDeployment deployment)
        {
            var supportedScriptExtensions = scriptEngine.GetSupportedTypes();
            var searchPatterns            = supportedScriptExtensions.Select(e => "*." + e.FileExtension()).ToArray();

            return(from file in fileSystem.EnumerateFiles(deployment.CurrentDirectory, searchPatterns)
                   let nameWithoutExtension = Path.GetFileNameWithoutExtension(file)
                                              where nameWithoutExtension.Equals(scriptFilePrefix, StringComparison.OrdinalIgnoreCase)
                                              select file);
        }
示例#9
0
        public IEnumerable <string> DetermineTransformFileNames(string sourceFile, XmlConfigTransformDefinition transformation)
        {
            var defaultTransformFileName = DetermineTransformFileName(sourceFile, transformation, true);
            var transformFileName        = DetermineTransformFileName(sourceFile, transformation, false);

            string fullTransformPath;

            if (Path.IsPathRooted(transformFileName))
            {
                fullTransformPath = Path.GetFullPath(GetDirectoryName(transformFileName));
            }
            else
            {
                var relativeTransformPath = fileSystem.GetRelativePath(sourceFile, transformFileName);
                fullTransformPath = Path.GetFullPath(Path.Combine(GetDirectoryName(sourceFile), GetDirectoryName(relativeTransformPath)));
            }

            if (!fileSystem.DirectoryExists(fullTransformPath))
            {
                yield break;
            }

            // The reason we use fileSystem.EnumerateFiles here is to get the actual file-names from the physical file-system.
            // This prevents any issues with mis-matched casing in transform specifications.
            foreach (var transformFile in fileSystem.EnumerateFiles(fullTransformPath, GetFileName(defaultTransformFileName), GetFileName(transformFileName)))
            {
                var sourceFileName = (transformation?.SourcePattern?.Contains(Path.DirectorySeparatorChar) ?? false)
                    ? fileSystem.GetRelativePath(transformFile, sourceFile).TrimStart('.', Path.DirectorySeparatorChar)
                    : GetFileName(sourceFile);

                if (transformation.Advanced && !transformation.IsSourceWildcard && !string.Equals(transformation.SourcePattern, sourceFileName, StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                if (transformation.Advanced && transformation.IsSourceWildcard && !DoesFileMatchWildcardPattern(sourceFileName, transformation.SourcePattern))
                {
                    continue;
                }

                if (!fileSystem.FileExists(transformFile))
                {
                    continue;
                }

                if (string.Equals(sourceFile, transformFile, StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                yield return(transformFile);
            }
        }
示例#10
0
        public void SetUp()
        {
            fileSystem = Substitute.For <ICalamariFileSystem>();
            fileSystem.EnumerateFiles(Arg.Any <string>(), Arg.Any <string[]>()).Returns(new[] { TestEnvironment.ConstructRootedPath("App", "MyApp", "Hello.ps1"), TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.ps1"), TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.csx"), TestEnvironment.ConstructRootedPath("App", "MyApp", "PreDeploy.ps1"), TestEnvironment.ConstructRootedPath("App", "MyApp", "PreDeploy.sh"), TestEnvironment.ConstructRootedPath("App", "MyApp", "PostDeploy.ps1"), TestEnvironment.ConstructRootedPath("App", "MyApp", "PostDeploy.sh"), TestEnvironment.ConstructRootedPath("App", "MyApp", "DeployFailed.ps1"), TestEnvironment.ConstructRootedPath("App", "MyApp", "DeployFailed.sh") });

            commandResult = new CommandResult("PowerShell.exe foo bar", 0, null);
            scriptEngine  = Substitute.For <IScriptEngine>();
            scriptEngine.Execute(Arg.Any <Script>(), Arg.Any <IVariables>(), Arg.Any <ICommandLineRunner>()).Returns(c => commandResult);
            scriptEngine.GetSupportedTypes().Returns(new[] { ScriptSyntax.CSharp, ScriptSyntax.PowerShell, ScriptSyntax.Bash });
            runner     = Substitute.For <ICommandLineRunner>();
            deployment = new RunningDeployment(TestEnvironment.ConstructRootedPath("Packages"), new CalamariVariables());
            log        = new InMemoryLog();
        }
示例#11
0
        public void ShouldNotSubstituteWhenFlagUnset()
        {
            const string substitutionTarget = "web.config";

            fileSystem.EnumerateFiles(StagingDirectory, substitutionTarget)
            .Returns(new[] { Path.Combine(StagingDirectory, substitutionTarget) });

            variables.Set(SpecialVariables.Package.SubstituteInFilesTargets, substitutionTarget);
            variables.Set(SpecialVariables.Package.SubstituteInFilesEnabled, false.ToString());

            CreateConvention().Install(deployment);

            substituter.DidNotReceive().PerformSubstitution(Arg.Any <string>(), Arg.Any <VariableDictionary>());
        }
示例#12
0
        private string[] PerformTransform(string sourceFile)
        {
            fileSystem.EnumerateFiles(Arg.Any <string>(), Arg.Any <string>(), Arg.Any <string>())
            .Returns(callInfo => files.Where(file => FilesMatch(callInfo, file)));
            var realFileSystem = CalamariPhysicalFileSystem.GetPhysicalFileSystem();

            fileSystem.GetRelativePath(Arg.Any <string>(), Arg.Any <string>())
            .Returns(x => GetRelativePath(x, realFileSystem));
            var        transformFileLocator     = new TransformFileLocator(fileSystem);
            var        transform                = new XmlConfigTransformDefinition(transformDefinition);
            const bool diagnosticLoggingEnabled = false;
            var        result = transformFileLocator.DetermineTransformFileNames(sourceFile, transform, diagnosticLoggingEnabled).ToArray();

            return(result);
        }
        public void SetUp()
        {
            fileSystem = Substitute.For<ICalamariFileSystem>();
            fileSystem.EnumerateFiles(Arg.Any<string>(), Arg.Any<string[]>()).Returns(new[]
            {
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Hello.ps1"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.ps1"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.csx"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "PreDeploy.ps1")
            });

            commandResult = new CommandResult("PowerShell.exe foo bar", 0, null);
            scriptEngine = Substitute.For<IScriptEngine>();
            scriptEngine.Execute(Arg.Any<string>(), Arg.Any<CalamariVariableDictionary>(), Arg.Any<ICommandLineRunner>()).Returns(c => commandResult);
            scriptEngine.GetSupportedExtensions().Returns(new[] {"csx", "ps1"});
            runner = Substitute.For<ICommandLineRunner>();
            deployment = new RunningDeployment(TestEnvironment.ConstructRootedPath("Packages"), new CalamariVariableDictionary());
        }
        public void SetUp()
        {
            fileSystem = Substitute.For <ICalamariFileSystem>();
            fileSystem.EnumerateFiles(Arg.Any <string>(), Arg.Any <string[]>()).Returns(new[]
            {
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Hello.ps1"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.ps1"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "Deploy.csx"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "PreDeploy.ps1"),
                TestEnvironment.ConstructRootedPath("App", "MyApp", "PostDeploy.ps1")
            });

            commandResult = new CommandResult("PowerShell.exe foo bar", 0, null);
            scriptEngine  = Substitute.For <IScriptEngine>();
            scriptEngine.Execute(Arg.Any <string>(), Arg.Any <CalamariVariableDictionary>(), Arg.Any <ICommandLineRunner>()).Returns(c => commandResult);
            scriptEngine.GetSupportedExtensions().Returns(new[] { "csx", "ps1" });
            runner     = Substitute.For <ICommandLineRunner>();
            deployment = new RunningDeployment(TestEnvironment.ConstructRootedPath("Packages"), new CalamariVariableDictionary());
        }
        public void SetUp()
        {
            fileSystem = Substitute.For <ICalamariFileSystem>();
            fileSystem.EnumerateFiles(Arg.Any <string>(), Arg.Any <string[]>()).Returns(new[]
            {
                "C:\\App\\MyApp\\Hello.ps1",
                "C:\\App\\MyApp\\Deploy.ps1",
                "C:\\App\\MyApp\\Deploy.csx",
                "C:\\App\\MyApp\\PreDeploy.ps1"
            });

            commandResult = new CommandResult("PowerShell.exe foo bar", 0, null);
            engine        = Substitute.For <IScriptEngine>();
            engine.Execute(Arg.Any <string>(), Arg.Any <VariableDictionary>(), Arg.Any <ICommandLineRunner>()).Returns(c => commandResult);
            selector = Substitute.For <IScriptEngineSelector>();
            selector.GetSupportedExtensions().Returns(new[] { "csx", "ps1" });
            selector.SelectEngine(Arg.Any <string>()).Returns(engine);
            runner     = Substitute.For <ICommandLineRunner>();
            deployment = new RunningDeployment("C:\\Packages", new VariableDictionary());
        }
        private IEnumerable <string> DetermineTransformFileNames(string sourceFile, XmlConfigTransformDefinition transformation)
        {
            var defaultTransformFileName = DetermineTransformFileName(sourceFile, transformation, true);
            var transformFileName        = DetermineTransformFileName(sourceFile, transformation, false);

            var relativeTransformPath = fileSystem.GetRelativePath(sourceFile, transformFileName);
            var fullTransformPath     = Path.GetFullPath(Path.Combine(GetDirectoryName(sourceFile), GetDirectoryName(relativeTransformPath)));

            if (!fileSystem.DirectoryExists(fullTransformPath))
            {
                return(Enumerable.Empty <string>());
            }

            // The reason we use fileSystem.EnumerateFiles here is to get the actual file-names from the physical file-system.
            // This prevents any issues with mis-matched casing in transform specifications.
            return(fileSystem.EnumerateFiles(fullTransformPath,
                                             GetFileName(defaultTransformFileName),
                                             GetFileName(transformFileName)
                                             ));
        }
示例#17
0
        private string[] PerformTransform(string sourceFile)
        {
            fileSystem.EnumerateFiles(Arg.Any <string>(), Arg.Any <string>(), Arg.Any <string>())
            .Returns(callInfo => files.Where(file => FilesMatch(callInfo, file)));
            var realFileSystem = CalamariPhysicalFileSystem.GetPhysicalFileSystem();

            fileSystem.GetRelativePath(Arg.Any <string>(), Arg.Any <string>())
            .Returns(x => GetRelativePath(x, realFileSystem));
            var transformFileLocator = new TransformFileLocator(fileSystem);
            var transform            = new XmlConfigTransformDefinition(transformDefinition);

            var deploymentVariables = new CalamariVariables();

            deploymentVariables[KnownVariables.OriginalPackageDirectoryPath] = extractionDirectory;
            var deployment = new RunningDeployment(null, deploymentVariables);

            const bool diagnosticLoggingEnabled = false;
            var        result = transformFileLocator.DetermineTransformFileNames(sourceFile, transform, diagnosticLoggingEnabled, deployment).ToArray();

            return(result);
        }
        public void ShouldUploadPackage()
        {
            const string packageFileName = "Acme.cspkg";
            var          packageFilePath = Path.Combine(stagingDirectory, packageFileName);

            variables.Set(SpecialVariables.Package.NuGetPackageVersion, "1.0.0");
            variables.Set(SpecialVariables.Action.Azure.CloudServicePackagePath, packageFilePath);
            fileSystem.EnumerateFiles(stagingDirectory, "*.cspkg")
            .Returns(new[] { packageFilePath });
            fileSystem.OpenFile(packageFilePath, Arg.Any <FileMode>())
            .Returns(new MemoryStream(Encoding.UTF8.GetBytes("blah blah blah")));

            var uploadedUri = new Uri("http://azure.com/wherever/my-package.cspkg");

            packageUploader.Upload(
                Arg.Is <SubscriptionCloudCredentials>(cred => cred.SubscriptionId == azureSubscriptionId),
                storageAccountName, packageFilePath, Arg.Any <string>())
            .Returns(uploadedUri);

            convention.Install(deployment);

            Assert.AreEqual(uploadedUri.ToString(), variables.Get(SpecialVariables.Action.Azure.UploadedPackageUri));
        }
示例#19
0
        string FindPackage(string workingDirectory)
        {
            var packages = fileSystem.EnumerateFiles(workingDirectory, "*.cspkg").ToList();

            if (packages.Count == 0)
            {
                // Try subdirectories
                packages = fileSystem.EnumerateFilesRecursively(workingDirectory, "*.cspkg").ToList();
            }

            if (packages.Count == 0)
            {
                throw new CommandException("Your package does not appear to contain any Azure Cloud Service package (.cspkg) files.");
            }

            if (packages.Count > 1)
            {
                throw new CommandException("Your deployment package contains more than one Cloud Service package (.cspkg) file, which is unsupported. Files: "
                                           + string.Concat(packages.Select(p => Environment.NewLine + " - " + p)));
            }

            return(packages.Single());
        }
示例#20
0
        private PackagePhysicalFileMetadata DownloadChart(string packageId, IVersion version, Uri feedUri,
                                                          ICredentials feedCredentials, string cacheDirectory)
        {
            var cred = feedCredentials.GetCredential(feedUri, "basic");

            var syntax = new[] { ScriptSyntax.PowerShell, ScriptSyntax.Bash }.First(syntx =>
                                                                                    scriptEngine.GetSupportedTypes().Contains(syntx));

            var tempDirectory = fileSystem.CreateTemporaryDirectory();

            using (new TemporaryDirectory(tempDirectory))
            {
                var file   = GetFetchScript(tempDirectory, syntax);
                var result = scriptEngine.Execute(new Script(file), new CalamariVariableDictionary()
                {
                    ["Password"] = cred.Password,
                    ["Username"] = cred.UserName,
                    ["Version"]  = version.OriginalString,
                    ["Url"]      = feedUri.ToString(),
                    ["Package"]  = packageId,
                }, commandLineRunner,
                                                  new Dictionary <string, string>());
                if (!result.HasErrors)
                {
                    var localDownloadName =
                        Path.Combine(cacheDirectory, PackageName.ToCachedFileName(packageId, version, Extension));

                    var packageFile = fileSystem.EnumerateFiles(Path.Combine(tempDirectory, "staging")).First();
                    fileSystem.MoveFile(packageFile, localDownloadName);
                    return(PackagePhysicalFileMetadata.Build(localDownloadName));
                }
                else
                {
                    throw new Exception("Unable to download chart");
                }
            }
        }
        private static void MockSearchableFiles(ICalamariFileSystem fileSystem, string parentDirectory, string[] files, string searchPattern)
        {
            fileSystem.EnumerateFilesRecursively(parentDirectory,
                Arg.Is<string[]>(x => new List<string>(x).Contains(searchPattern))).Returns(files);

            foreach (var file in files)
            {
                fileSystem.FileExists(file).Returns(true);
                fileSystem.EnumerateFiles(Path.GetDirectoryName(files[0]), Arg.Is<string[]>(s => s.Contains(GetRelativePathToTransformFile(files[0], file)))).Returns(new[] {file});
            }
        }
        public IEnumerable <string> DetermineTransformFileNames(string sourceFile, XmlConfigTransformDefinition transformation, bool diagnosticLoggingEnabled, string currentDirectory)
        {
            var defaultTransformFileName = DetermineTransformFileName(sourceFile, transformation, true);
            var transformFileName        = DetermineTransformFileName(sourceFile, transformation, false);

            string fullTransformDirectoryPath;

            if (Path.IsPathRooted(transformFileName))
            {
                fullTransformDirectoryPath = Path.GetFullPath(GetDirectoryName(transformFileName));
            }
            else
            {
                var relativeTransformPath = fileSystem.GetRelativePath(sourceFile, transformFileName);
                fullTransformDirectoryPath = Path.GetFullPath(Path.Combine(GetDirectoryName(sourceFile), GetDirectoryName(relativeTransformPath)));
            }

            if (!fileSystem.DirectoryExists(fullTransformDirectoryPath))
            {
                if (diagnosticLoggingEnabled)
                {
                    log.Verbose($" - Skipping as transform folder \'{fullTransformDirectoryPath}\' does not exist");
                }
                yield break;
            }

            // The reason we use fileSystem.EnumerateFiles here is to get the actual file-names from the physical file-system.
            // This prevents any issues with mis-matched casing in transform specifications.
            var enumerateFiles = fileSystem.EnumerateFiles(fullTransformDirectoryPath, GetFileName(defaultTransformFileName), GetFileName(transformFileName)).Distinct().ToArray();

            if (enumerateFiles.Any())
            {
                foreach (var transformFile in enumerateFiles)
                {
                    var sourceFileName = GetSourceFileName(sourceFile, transformation, transformFileName, transformFile, currentDirectory);

                    if (transformation.Advanced && !transformation.IsSourceWildcard &&
                        !string.Equals(transformation.SourcePattern, sourceFileName, StringComparison.OrdinalIgnoreCase))
                    {
                        if (diagnosticLoggingEnabled)
                        {
                            log.Verbose($" - Skipping as file name \'{sourceFileName}\' does not match the target pattern \'{transformation.SourcePattern}\'");
                        }
                        continue;
                    }

                    if (transformation.Advanced && transformation.IsSourceWildcard &&
                        !DoesFileMatchWildcardPattern(sourceFileName, transformation.SourcePattern))
                    {
                        if (diagnosticLoggingEnabled)
                        {
                            log.Verbose($" - Skipping as file name \'{sourceFileName}\' does not match the wildcard target pattern \'{transformation.SourcePattern}\'");
                        }
                        continue;
                    }

                    if (!fileSystem.FileExists(transformFile))
                    {
                        if (diagnosticLoggingEnabled)
                        {
                            log.Verbose($" - Skipping as transform \'{transformFile}\' does not exist");
                        }
                        continue;
                    }

                    if (string.Equals(sourceFile, transformFile, StringComparison.OrdinalIgnoreCase))
                    {
                        if (diagnosticLoggingEnabled)
                        {
                            log.Verbose($" - Skipping as target \'{sourceFile}\' is the same as transform \'{transformFile}\'");
                        }
                        continue;
                    }

                    yield return(transformFile);
                }
            }
            else if (diagnosticLoggingEnabled)
            {
                if (GetFileName(defaultTransformFileName) == GetFileName(transformFileName))
                {
                    log.Verbose($" - skipping as transform \'{GetFileName(defaultTransformFileName)}\' could not be found in \'{fullTransformDirectoryPath}\'");
                }
                else
                {
                    log.Verbose($" - skipping as neither transform \'{GetFileName(defaultTransformFileName)}\' nor transform \'{GetFileName(transformFileName)}\' could be found in \'{fullTransformDirectoryPath}\'");
                }
            }
        }