< Summary

Class:Willykc.Templ.Editor.Entry.TemplEntryFacade
Assembly:Willykc.Templ.Editor
File(s):/github/workspace/Packages/package.to.test/Editor/Entry/TemplEntryFacade.cs
Covered lines:177
Uncovered lines:9
Coverable lines:186
Total lines:387
Line coverage:95.1% (177 of 186)
Covered branches:0
Total branches:0
Covered methods:10
Total methods:10
Method coverage:100% (10 of 10)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
TemplEntryFacade(...)0%110100%
GetEntries()0%330100%
AddEntry[T](...)0%14.0314094.83%
UpdateEntry(...)0%29.0828088.89%
RemoveEntry(...)0%550100%
EntryExist(...)0%440100%
ForceRenderEntry(...)0%660100%
ForceRenderAllValidEntries()0%330100%
IsValidEntryType(...)0%440100%
IsValidInputField(...)0%220100%

File(s)

/github/workspace/Packages/package.to.test/Editor/Entry/TemplEntryFacade.cs

#LineLine coverage
 1/*
 2 * Copyright (c) 2024 Willy Alberto Kuster
 3 *
 4 * Permission is hereby granted, free of charge, to any person obtaining a copy
 5 * of this software and associated documentation files (the "Software"), to deal
 6 * in the Software without restriction, including without limitation the rights
 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 8 * copies of the Software, and to permit persons to whom the Software is
 9 * furnished to do so, subject to the following conditions:
 10 *
 11 * The above copyright notice and this permission notice shall be included in
 12 * all copies or substantial portions of the Software.
 13 *
 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 20 * THE SOFTWARE.
 21 */
 22using System;
 23using System.IO;
 24using System.Linq;
 25using System.Reflection;
 26using UnityEditor;
 27using UnityObject = UnityEngine.Object;
 28
 29namespace Willykc.Templ.Editor.Entry
 30{
 31    using Abstraction;
 32
 33    internal sealed class TemplEntryFacade : ITemplEntryFacade
 34    {
 35        private const int ValidInputFieldCount = 1;
 36
 5537        private readonly object lockHandle = new object();
 38        private readonly ISettingsProvider settingsProvider;
 39        private readonly IAssetDatabase assetDatabase;
 40        private readonly IEditorUtility editorUtility;
 41        private readonly ITemplEntryCore entryCore;
 42
 5543        internal TemplEntryFacade(
 44            ISettingsProvider settingsProvider,
 45            IAssetDatabase assetDatabase,
 46            IEditorUtility editorUtility,
 47            ITemplEntryCore entryCore)
 5548        {
 5549            this.settingsProvider = settingsProvider;
 5550            this.assetDatabase = assetDatabase;
 5551            this.editorUtility = editorUtility;
 5552            this.entryCore = entryCore;
 5553        }
 54
 55        TemplEntry[] ITemplEntryFacade.GetEntries()
 256        {
 257            if (!settingsProvider.SettingsExist())
 158            {
 159                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 60            }
 61
 162            lock (lockHandle)
 163            {
 164                return settingsProvider.GetSettings().Entries.ToArray();
 65            }
 166        }
 67
 68        string ITemplEntryFacade.AddEntry<T>(
 69            UnityObject inputAsset,
 70            ScribanAsset template,
 71            string outputAssetPath)
 1972        {
 1973            if (!settingsProvider.SettingsExist())
 174            {
 175                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 76            }
 77
 1878            inputAsset = inputAsset
 79                ? inputAsset
 80                : throw new ArgumentNullException(nameof(inputAsset));
 1781            template = template
 82                ? template
 83                : throw new ArgumentNullException(nameof(template));
 1684            outputAssetPath = !string.IsNullOrWhiteSpace(outputAssetPath)
 85                ? outputAssetPath
 86                : throw new ArgumentException(
 87                    $"{nameof(outputAssetPath)} must not be null or empty");
 88
 1489            var settings = settingsProvider.GetSettings();
 90
 1491            if (inputAsset == settings)
 192            {
 193                throw new ArgumentException(
 94                    $"{nameof(inputAsset)} can not be {nameof(TemplSettings)}", nameof(inputAsset));
 95            }
 96
 1397            if (inputAsset.GetType() == typeof(ScribanAsset))
 198            {
 199                throw new ArgumentException(
 100                    $"{nameof(inputAsset)} can not be a {nameof(ScribanAsset)}",
 101                    nameof(inputAsset));
 102            }
 103
 12104            if (template.HasErrors)
 1105            {
 1106                throw new ArgumentException($"{nameof(template)} has syntax errors",
 107                    nameof(template));
 108            }
 109
 11110            outputAssetPath = outputAssetPath.SanitizePath();
 11111            string filename = null;
 112
 113            try
 11114            {
 11115                filename = Path.GetFileName(outputAssetPath);
 11116            }
 0117            catch (Exception exception)
 0118            {
 0119                throw new ArgumentException($"'{outputAssetPath}' is not a valid path",
 120                    nameof(outputAssetPath), exception);
 121            }
 122
 11123            if(!filename.IsValidFileName())
 2124            {
 2125                throw new ArgumentException($"'{filename}' is not a valid file name",
 126                    nameof(outputAssetPath));
 127            }
 128
 9129            var directoryPath = Path.GetDirectoryName(outputAssetPath);
 9130            var directory = assetDatabase.LoadAssetAtPath<DefaultAsset>(directoryPath);
 131
 9132            if (!directory || !assetDatabase.IsValidFolder(directoryPath))
 1133            {
 1134                throw new DirectoryNotFoundException(
 135                    $"Directory does not exist in the asset database: '{directoryPath}'");
 136            }
 137
 8138            var entryType = typeof(T);
 139
 8140            if (!IsValidEntryType(entryType))
 1141            {
 1142                throw new InvalidOperationException(
 143                    $"'{entryType.Name}' is not a valid entry type");
 144            }
 145
 7146            if (settings.Entries.Any(e => e.OutputAssetPath.PathsEquivalent(outputAssetPath)))
 2147            {
 2148                throw new InvalidOperationException("Existing entry already uses " +
 149                    $"'{outputAssetPath}' as output asset path");
 150            }
 151
 5152            var newEntry = Activator.CreateInstance(entryType) as TemplEntry;
 153
 154            try
 5155            {
 5156                newEntry.InputAsset = inputAsset;
 4157            }
 1158            catch (Exception exception)
 1159            {
 1160                throw new ArgumentException(
 161                    $"Could not assign {nameof(inputAsset)} to entry. " +
 162                    "Type must match entry input field",
 163                    nameof(inputAsset), exception);
 164            }
 165
 4166            newEntry.Template = template;
 4167            newEntry.Directory = directory;
 4168            newEntry.Filename = filename;
 169
 4170            lock (lockHandle)
 4171            {
 4172                settingsProvider.GetSettings().Entries.Add(newEntry);
 173
 4174                editorUtility.SetDirty(settings);
 4175                assetDatabase.SaveAssets();
 4176            }
 177
 4178            return newEntry.Id;
 4179        }
 180
 181        void ITemplEntryFacade.UpdateEntry(
 182            string id,
 183            UnityObject inputAsset,
 184            ScribanAsset template,
 185            string outputAssetPath)
 16186        {
 16187            if (!settingsProvider.SettingsExist())
 1188            {
 1189                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 190            }
 191
 15192            id = id ?? throw new ArgumentNullException(nameof(id));
 193
 14194            var settings = settingsProvider.GetSettings();
 195
 14196            if (inputAsset && inputAsset == settings)
 1197            {
 1198                throw new ArgumentException(
 199                    $"{nameof(inputAsset)} can not be {nameof(TemplSettings)}", nameof(inputAsset));
 200            }
 201
 13202            if (inputAsset && inputAsset.GetType() == typeof(ScribanAsset))
 1203            {
 1204                throw new ArgumentException(
 205                    $"{nameof(inputAsset)} can not be a {nameof(ScribanAsset)}",
 206                    nameof(inputAsset));
 207            }
 208
 12209            if (template && template.HasErrors)
 1210            {
 1211                throw new ArgumentException($"{nameof(template)} has syntax errors",
 212                    nameof(template));
 213            }
 214
 11215            outputAssetPath = outputAssetPath != null
 216                ? outputAssetPath.SanitizePath()
 217                : outputAssetPath;
 218
 11219            string filename = null;
 220
 221            try
 11222            {
 11223                filename = outputAssetPath != null
 224                    ? Path.GetFileName(outputAssetPath)
 225                    : filename;
 11226            }
 0227            catch (Exception exception)
 0228            {
 0229                throw new ArgumentException($"'{outputAssetPath}' is not a valid path",
 230                    nameof(outputAssetPath), exception);
 231            }
 232
 11233            if (outputAssetPath != null && !filename.IsValidFileName())
 2234            {
 2235                throw new ArgumentException($"'{filename}' is not a valid file name",
 236                    nameof(outputAssetPath));
 237            }
 238
 9239            var directoryPath = outputAssetPath != null
 240                ? Path.GetDirectoryName(outputAssetPath)
 241                : string.Empty;
 9242            var directory = assetDatabase.LoadAssetAtPath<DefaultAsset>(directoryPath);
 243
 9244            if (outputAssetPath != null &&
 245                (!directory || !assetDatabase.IsValidFolder(directoryPath)))
 1246            {
 1247                throw new DirectoryNotFoundException(
 248                    $"Directory does not exist in the asset database: '{directoryPath}'");
 249            }
 250
 17251            var entry = settings.Entries.FirstOrDefault(e => e.Id == id);
 252
 8253            if (entry == null)
 1254            {
 1255                throw new InvalidOperationException($"No entry could be found with id '{id}'");
 256            }
 257
 7258            var pathConflicts = settings.Entries
 14259                .Any(e => e != entry && e.OutputAssetPath.PathsEquivalent(outputAssetPath));
 260
 6261            if (pathConflicts)
 2262            {
 2263                throw new InvalidOperationException("Existing entry already uses " +
 264                    $"'{outputAssetPath}' as output asset path");
 265            }
 266
 4267            lock (lockHandle)
 4268            {
 269                try
 4270                {
 4271                    entry.InputAsset = inputAsset ? inputAsset : entry.InputAsset;
 4272                }
 0273                catch (Exception exception)
 0274                {
 0275                    throw new ArgumentException(
 276                        $"Could not assign {nameof(inputAsset)} to entry. " +
 277                        "Type must match entry input field",
 278                        nameof(inputAsset), exception);
 279                }
 280
 4281                entry.Template = template ? template : entry.Template;
 4282                entry.Directory = outputAssetPath != null ? directory : entry.Directory;
 4283                entry.Filename = outputAssetPath != null ? filename : entry.Filename;
 284
 4285                editorUtility.SetDirty(settings);
 4286                assetDatabase.SaveAssets();
 4287            }
 4288        }
 289
 290        void ITemplEntryFacade.RemoveEntry(string id)
 6291        {
 6292            if (!settingsProvider.SettingsExist())
 1293            {
 1294                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 295            }
 296
 5297            id = id ?? throw new ArgumentNullException(nameof(id));
 298
 4299            var settings = settingsProvider.GetSettings();
 300
 9301            var entry = settings.Entries.FirstOrDefault(e => e.Id == id);
 302
 4303            if (entry == null)
 1304            {
 1305                throw new InvalidOperationException($"No entry could be found with id '{id}'");
 306            }
 307
 3308            lock (lockHandle)
 3309            {
 3310                settings.Entries.Remove(entry);
 311
 3312                editorUtility.SetDirty(settings);
 3313                assetDatabase.SaveAssets();
 3314            }
 3315        }
 316
 317        bool ITemplEntryFacade.EntryExist(string outputAssetPath)
 5318        {
 5319            if (!settingsProvider.SettingsExist())
 1320            {
 1321                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 322            }
 323
 4324            outputAssetPath = outputAssetPath
 325                ?? throw new ArgumentNullException(nameof(outputAssetPath));
 326
 3327            var settings = settingsProvider.GetSettings();
 328
 3329            lock (lockHandle)
 3330            {
 3331                return settings.Entries
 4332                    .Any(e => e.OutputAssetPath.PathsEquivalent(outputAssetPath));
 333            }
 3334        }
 335
 336        void ITemplEntryFacade.ForceRenderEntry(string id)
 5337        {
 5338            if (!settingsProvider.SettingsExist())
 1339            {
 1340                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 341            }
 342
 4343            id = id ?? throw new ArgumentNullException(nameof(id));
 344
 3345            var settings = settingsProvider.GetSettings();
 346
 7347            var entry = settings.Entries.FirstOrDefault(e => e.Id == id);
 348
 3349            if (entry == null)
 1350            {
 1351                throw new InvalidOperationException($"No entry could be found with id '{id}'");
 352            }
 353
 2354            if (!entry.IsValid)
 1355            {
 1356                throw new InvalidOperationException($"Can not render invalid entry with id '{id}'");
 357            }
 358
 1359            lock (lockHandle)
 1360            {
 1361                entryCore.RenderEntry(id);
 1362            }
 1363        }
 364
 365        void ITemplEntryFacade.ForceRenderAllValidEntries()
 2366        {
 2367            if (!settingsProvider.SettingsExist())
 1368            {
 1369                throw new InvalidOperationException($"{nameof(TemplSettings)} not found");
 370            }
 371
 1372            lock (lockHandle)
 1373            {
 1374                entryCore.RenderAllValidEntries();
 1375            }
 1376        }
 377
 378        internal static bool IsValidEntryType(Type type) =>
 8379            type.IsSubclassOf(typeof(TemplEntry)) && !type.IsAbstract &&
 380            type.IsDefined(typeof(TemplEntryInfoAttribute), false) &&
 381            type.GetFields().Count(IsValidInputField) == ValidInputFieldCount;
 382
 383        private static bool IsValidInputField(FieldInfo field) =>
 7384            field.IsDefined(typeof(TemplInputAttribute), false) &&
 385            field.FieldType.IsSubclassOf(typeof(UnityObject));
 386    }
 387}