private string ReadTemplateByName(string templateName)
        {
            var templatePath = ResolveTemplatePath(templateName);

            if (string.IsNullOrEmpty(templatePath))
            {
                throw new FileSystemException($"The template '{templateName}' was not found. The following locations were searched:<br/>{string.Join("<br/>", DiscoveryPaths)}");
            }

            var cacheKey = CacheKey.With(GetType(), "ReadTemplateByName", templatePath);

            return(_memoryCache.GetOrCreate(cacheKey, (cacheItem) =>
            {
                cacheItem.AddExpirationToken(new CompositeChangeToken(new[] { ThemeEngineCacheRegion.CreateChangeToken(), _themeBlobProvider.Watch(templatePath) }));
                using (var stream = _themeBlobProvider.OpenRead(templatePath))
                {
                    return stream.ReadToString();
                }
            }));
        }
        /// <summary>
        /// Read localization resources
        /// </summary>
        /// <returns></returns>
        public JObject ReadLocalization()
        {
            var cacheKey = CacheKey.With(GetType(), "ReadLocalization", CurrentThemeLocalePath, WorkContext.CurrentLanguage.CultureName);

            return(_memoryCache.GetOrCreateExclusive(cacheKey, (cacheItem) =>
            {
                var result = new JObject();
                cacheItem.AddExpirationToken(new CompositeChangeToken(new[] { ThemeEngineCacheRegion.CreateChangeToken(), _themeBlobProvider.Watch(CurrentThemeLocalePath + "/*") }));
                //Try to load localization resources from base theme path and merge them with resources for local theme
                if (BaseThemeLocalePath != null)
                {
                    cacheItem.AddExpirationToken(new CompositeChangeToken(new[] { ThemeEngineCacheRegion.CreateChangeToken(), _themeBlobProvider.Watch(BaseThemeLocalePath + "/*") }));
                    result = InnerReadLocalization(_themeBlobProvider, BaseThemeLocalePath, WorkContext.CurrentLanguage);
                }
                result.Merge(InnerReadLocalization(_themeBlobProvider, CurrentThemeLocalePath, WorkContext.CurrentLanguage) ?? new JObject(), new JsonMergeSettings {
                    MergeArrayHandling = MergeArrayHandling.Merge
                });
                return result;
            }));
        }
        /// <summary>
        /// Render template by content and parameters
        /// </summary>
        /// <param name="templateContent"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public ValueTask <string> RenderTemplateAsync(string templateContent, string templatePath, object context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (!(context is IScriptObject scriptObject))
            {
                throw new StorefrontException($"{ nameof(context) } must implement IScriptObject");
            }

            if (string.IsNullOrEmpty(templateContent))
            {
                return(new ValueTask <string>(templateContent));
            }

            var isLiquidTemplate = _isLiquid.Match(templateContent);

            if (!isLiquidTemplate.Success)
            {
                return(new ValueTask <string>(templateContent));
            }

            //TODO: Handle _options.RethrowLiquidRenderErrors
            var cacheKey       = CacheKey.With(GetType(), "ParseTemplate", templatePath ?? templateContent);
            var parsedTemplate = _memoryCache.GetOrCreate(cacheKey, (cacheItem) =>
            {
                if (!string.IsNullOrEmpty(templatePath))
                {
                    cacheItem.AddExpirationToken(new CompositeChangeToken(new[] { ThemeEngineCacheRegion.CreateChangeToken(), _themeBlobProvider.Watch(templatePath) }));
                }
                else
                {
                    cacheItem.AddExpirationToken(ThemeEngineCacheRegion.CreateChangeToken());
                }
                return(Template.ParseLiquid(templateContent, templatePath));
            });

            if (parsedTemplate.HasErrors)
            {
                throw new InvalidOperationException(string.Join("\n", parsedTemplate.Messages));
            }


            var templateContext = new TemplateContext()
            {
                TemplateLoader            = this,
                EnableRelaxedMemberAccess = true,
                NewLine = Environment.NewLine,
                TemplateLoaderLexerOptions = new LexerOptions
                {
                    Mode = ScriptMode.Liquid
                }
            };

            templateContext.PushGlobal(scriptObject);

            var result = parsedTemplate.Render(templateContext);

            return(new ValueTask <string>(result));
        }