From 5dc2ecbeb16b672879ee8e72a37249fb8843cadb Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Mon, 26 Jun 2017 15:37:51 +0200 Subject: [PATCH] Introduce PathSpecification to config and migrate properties to methods so PathSpecification can be passed in --- src/DocNet/Config.cs | 23 +++++- src/DocNet/Docnet.csproj | 1 + src/DocNet/Engine.cs | 7 +- src/DocNet/INavigationElement.cs | 17 +++-- src/DocNet/NavigatedPath.cs | 14 ++-- src/DocNet/NavigationElement.cs | 17 +++-- src/DocNet/NavigationLevel.cs | 132 +++++++++++++++++++--------------- src/DocNet/PathSpecification.cs | 9 +++ src/DocNet/SimpleNavigationElement.cs | 54 +++++++------- 9 files changed, 173 insertions(+), 101 deletions(-) create mode 100644 src/DocNet/PathSpecification.cs diff --git a/src/DocNet/Config.cs b/src/DocNet/Config.cs index 482e665..bd21cc3 100644 --- a/src/DocNet/Config.cs +++ b/src/DocNet/Config.cs @@ -131,7 +131,7 @@ namespace Docnet private void GenerateSearchDataIndex() { var collectedSearchEntries = new List(); - this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath()); + this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath(), this.PathSpecification); JObject searchIndex = new JObject(new JProperty("docs", new JArray( collectedSearchEntries.Select(e=>new JObject( @@ -164,7 +164,7 @@ namespace Docnet searchSimpleElement.ExtraScriptProducerFunc = e=> @" "; - searchSimpleElement.GenerateOutput(this, activePath); + searchSimpleElement.GenerateOutput(this, activePath, this.PathSpecification); activePath.Pop(); } @@ -256,6 +256,25 @@ namespace Docnet get { return _templateContents ?? string.Empty; } } + public PathSpecification PathSpecification + { + get + { + var pathSpecification = PathSpecification.Full; + + var pathSpecificationAsString = _configData.PathSpecification; + if (!string.IsNullOrWhiteSpace(pathSpecificationAsString)) + { + if (!Enum.TryParse(pathSpecificationAsString, true, out pathSpecification)) + { + pathSpecification = PathSpecification.Full; + } + } + + return pathSpecification; + } + } + public NavigationLevel Pages { get diff --git a/src/DocNet/Docnet.csproj b/src/DocNet/Docnet.csproj index ff52d74..2340235 100644 --- a/src/DocNet/Docnet.csproj +++ b/src/DocNet/Docnet.csproj @@ -60,6 +60,7 @@ + diff --git a/src/DocNet/Engine.cs b/src/DocNet/Engine.cs index 28435cf..5078bd1 100644 --- a/src/DocNet/Engine.cs +++ b/src/DocNet/Engine.cs @@ -70,11 +70,14 @@ namespace Docnet Console.WriteLine("Errors occurred, can't continue!"); return null; } - if(config.Pages.IndexElement == null) + + var indexElement = config.Pages.GetIndexElement(config.PathSpecification); + if(indexElement == null) { Console.WriteLine("[ERROR] Root __index not found. The root navigationlevel is required to have an __index element"); return null; } + return config; } @@ -95,7 +98,7 @@ namespace Docnet Console.WriteLine("Copying source folders to copy."); _loadedConfig.CopySourceFoldersToCopy(); Console.WriteLine("Generating pages in '{0}'", _loadedConfig.Destination); - _loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath()); + _loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), _loadedConfig.PathSpecification); Console.WriteLine("Generating search index"); _loadedConfig.GenerateSearchData(); Console.WriteLine("Done!"); diff --git a/src/DocNet/INavigationElement.cs b/src/DocNet/INavigationElement.cs index 0e97aac..c665d16 100644 --- a/src/DocNet/INavigationElement.cs +++ b/src/DocNet/INavigationElement.cs @@ -35,23 +35,31 @@ namespace Docnet /// /// The active configuration to use for the output. /// The active path navigated through the ToC to reach this element. - void GenerateOutput(Config activeConfig, NavigatedPath activePath); - + /// The path specification. + void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); /// /// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. /// /// The navigated path to the current element, which doesn't necessarily have to be this element. /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); + string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); /// /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. /// /// The collected entries. /// The active path currently navigated. - void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath); + /// The path specification. + void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); + /// + /// Gets the target URL with respect to the . + /// + /// The path specification. + /// + string GetTargetURL(PathSpecification pathSpecification); /// /// Gets a value indicating whether this element is the __index element @@ -59,7 +67,6 @@ namespace Docnet bool IsIndexElement { get; set; } string Name { get; set; } object Value { get; set; } - string TargetURL { get; } NavigationLevel ParentContainer { get; set; } } } diff --git a/src/DocNet/NavigatedPath.cs b/src/DocNet/NavigatedPath.cs index b53f6c1..c93f21c 100644 --- a/src/DocNet/NavigatedPath.cs +++ b/src/DocNet/NavigatedPath.cs @@ -35,17 +35,18 @@ namespace Docnet public class NavigatedPath : Stack { /// - /// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters. + /// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters. /// /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - public string CreateBreadCrumbsHTML(string relativePathToRoot) + public string CreateBreadCrumbsHTML(string relativePathToRoot, PathSpecification pathSpecification) { var fragments = new List(); // we enumerate a stack, which enumerates from top to bottom, so we have to reverse things first. foreach(var element in this.Reverse()) { - var targetURL = element.TargetURL; + var targetURL = element.GetTargetURL(pathSpecification); if(string.IsNullOrWhiteSpace(targetURL)) { fragments.Add(string.Format("
  • {0}
  • ", element.Name)); @@ -73,11 +74,12 @@ namespace Docnet /// /// Creates the ToC HTML for the element reached by the elements in this path. All containers in this path are expanded, all elements inside these containers which - /// aren't, are not expanded. + /// aren't, are not expanded. /// /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - public string CreateToCHTML(string relativePathToRoot) + public string CreateToCHTML(string relativePathToRoot, PathSpecification pathSpecification) { // the root container is the bottom element of this path. We use that container to build the root and navigate any node open along the navigated path. var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel; @@ -86,7 +88,7 @@ namespace Docnet // no root container, no TOC return string.Empty; } - return rootContainer.GenerateToCFragment(this, relativePathToRoot); + return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification); } } } diff --git a/src/DocNet/NavigationElement.cs b/src/DocNet/NavigationElement.cs index dd86175..9ca86d5 100644 --- a/src/DocNet/NavigationElement.cs +++ b/src/DocNet/NavigationElement.cs @@ -36,25 +36,32 @@ namespace Docnet ///
    /// The active configuration to use for the output. /// The active path navigated through the ToC to reach this element. - /// true if everything went well, false otherwise - public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath); + /// The path specification. + public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification); /// /// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu. /// /// The navigated path to the current element, which doesn't necessarily have to be this element. /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot); + public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification); /// /// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element. /// /// The collected entries. /// The active path currently navigated. - public abstract void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath); + /// The path specification. + public abstract void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification); + /// + /// Gets the target URL with respect to the . + /// + /// The path specification. + /// + public abstract string GetTargetURL(PathSpecification pathSpecification); #region Properties - public abstract string TargetURL { get; } /// /// Gets / sets a value indicating whether this element is the __index element /// diff --git a/src/DocNet/NavigationLevel.cs b/src/DocNet/NavigationLevel.cs index bfc2ba1..b607c8e 100644 --- a/src/DocNet/NavigationLevel.cs +++ b/src/DocNet/NavigationLevel.cs @@ -77,20 +77,20 @@ namespace Docnet else { toAdd = new SimpleNavigationElement - { - Name = nameToUse, - Value = childValue, - IsIndexElement = isIndexElement - }; + { + Name = nameToUse, + Value = childValue, + IsIndexElement = isIndexElement + }; } } else { var subLevel = new NavigationLevel(_rootDirectory) - { - Name = child.Key, - IsRoot = false - }; + { + Name = child.Key, + IsRoot = false + }; subLevel.Load((JObject)child.Value); toAdd = subLevel; } @@ -105,12 +105,13 @@ namespace Docnet /// /// The collected entries. /// The active path currently navigated. - public override void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath) + /// The path specification. + public override void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) { activePath.Push(this); foreach (var element in this.Value) { - element.CollectSearchIndexEntries(collectedEntries, activePath); + element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification); } activePath.Pop(); } @@ -121,14 +122,15 @@ namespace Docnet /// /// The active configuration to use for the output. /// The active path navigated through the ToC to reach this element. - public override void GenerateOutput(Config activeConfig, NavigatedPath activePath) + /// The path specification. + public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) { activePath.Push(this); int i = 0; while (i < this.Value.Count) { var element = this.Value[i]; - element.GenerateOutput(activeConfig, activePath); + element.GenerateOutput(activeConfig, activePath, pathSpecification); i++; } activePath.Pop(); @@ -140,8 +142,9 @@ namespace Docnet /// /// The navigated path to the current element, which doesn't necessarily have to be this element. /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) + public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) { var fragments = new List(); if (!this.IsRoot) @@ -163,7 +166,7 @@ namespace Docnet // first render the level header, which is the index element, if present or a label. The root always has an __index element otherwise we'd have stopped at load. var elementStartTag = "
  • "; - var indexElement = this.IndexElement; + var indexElement = this.GetIndexElement(pathSpecification); if (indexElement == null) { fragments.Add(string.Format("{0}{1}
  • ", elementStartTag, this.Name)); @@ -172,18 +175,18 @@ namespace Docnet { if (this.IsRoot) { - fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot)); + fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); } else { - fragments.Add(string.Format("{0}{3}", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL), + fragments.Add(string.Format("{0}{3}", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.GetTargetURL(pathSpecification)), this.Name)); } } // then the elements in the container. Index elements are skipped here. foreach (var element in this.Value) { - fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot)); + fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification)); } fragments.Add(""); } @@ -191,7 +194,7 @@ namespace Docnet { // just a link fragments.Add(string.Format(" {2}", - relativePathToRoot, HttpUtility.UrlPathEncode(this.TargetURL), this.Name)); + relativePathToRoot, HttpUtility.UrlPathEncode(this.GetTargetURL(pathSpecification)), this.Name)); } if (!this.IsRoot) { @@ -204,9 +207,9 @@ namespace Docnet private NavigationLevel CreateGeneratedLevel(string path) { var root = new NavigationLevel(_rootDirectory) - { - ParentContainer = this - }; + { + ParentContainer = this + }; foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly)) { @@ -217,11 +220,11 @@ namespace Docnet } var item = new SimpleNavigationElement - { - Name = name, - Value = Utils.MakeRelativePath(mdFile, _rootDirectory), - ParentContainer = root - }; + { + Name = name, + Value = Utils.MakeRelativePath(mdFile, _rootDirectory), + ParentContainer = root + }; root.Value.Add(item); } @@ -270,49 +273,64 @@ namespace Docnet return title; } - - #region Properties - public override string TargetURL + public override string GetTargetURL(PathSpecification pathSpecification) { - get + var defaultElement = this.GetIndexElement(pathSpecification); + if (defaultElement == null) { - var defaultElement = this.IndexElement; - if (defaultElement == null) - { - return string.Empty; - } - return defaultElement.TargetURL ?? string.Empty; + return string.Empty; } + return defaultElement.GetTargetURL(pathSpecification) ?? string.Empty; } - - public SimpleNavigationElement IndexElement + public SimpleNavigationElement GetIndexElement(PathSpecification pathSpecification) { - get + var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; + if (toReturn == null) { - var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; - if (toReturn == null) + // no index element, add an artificial one. + var path = string.Empty; + if (this.ParentContainer != null) { - // no index element, add an artificial one. - var path = string.Empty; - if (this.ParentContainer != null) - { - path = Path.GetDirectoryName(this.ParentContainer.TargetURL); - } - var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); - if (string.IsNullOrWhiteSpace(nameToUse)) - { - return null; - } - toReturn = new SimpleNavigationElement() { ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true }; - this.Value.Add(toReturn); + path = Path.GetDirectoryName(this.ParentContainer.GetTargetURL(pathSpecification)); + } + var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); + if (string.IsNullOrWhiteSpace(nameToUse)) + { + return null; } - return toReturn; + var value = string.Empty; + + switch (pathSpecification) + { + case PathSpecification.Full: + value = string.Format("{0}{1}.md", path, nameToUse); + break; + + case PathSpecification.Relative: + value = Path.Combine(path ?? string.Empty, nameToUse + ".md"); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(pathSpecification), pathSpecification, null); + } + + toReturn = new SimpleNavigationElement + { + ParentContainer = this, + Value = value, + Name = this.Name, + IsIndexElement = true + }; + + this.Value.Add(toReturn); } - } + return toReturn; + } + #region Properties /// /// Gets / sets a value indicating whether this element is the __index element /// diff --git a/src/DocNet/PathSpecification.cs b/src/DocNet/PathSpecification.cs new file mode 100644 index 0000000..bfe2736 --- /dev/null +++ b/src/DocNet/PathSpecification.cs @@ -0,0 +1,9 @@ +namespace Docnet +{ + public enum PathSpecification + { + Full, + + Relative + } +} \ No newline at end of file diff --git a/src/DocNet/SimpleNavigationElement.cs b/src/DocNet/SimpleNavigationElement.cs index c47f2b7..1c9f877 100644 --- a/src/DocNet/SimpleNavigationElement.cs +++ b/src/DocNet/SimpleNavigationElement.cs @@ -49,7 +49,9 @@ namespace Docnet /// /// The active configuration to use for the output. /// The active path navigated through the ToC to reach this element. - public override void GenerateOutput(Config activeConfig, NavigatedPath activePath) + /// The path specification. + /// + public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification) { // if we're the __index element, we're not pushing ourselves on the path, as we're representing the container we're in, which is already on the path. if(!this.IsIndexElement) @@ -58,7 +60,7 @@ namespace Docnet } _relativeH2LinksOnPage.Clear(); var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value); - var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.TargetURL); + var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.GetTargetURL(pathSpecification)); var sb = new StringBuilder(activeConfig.PageTemplateContents.Length + 2048); var content = string.Empty; this.MarkdownFromFile = string.Empty; @@ -87,7 +89,7 @@ namespace Docnet { continue; } - defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, HttpUtility.UrlPathEncode(sibling.TargetURL), Environment.NewLine); + defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, HttpUtility.UrlPathEncode(sibling.GetTargetURL(pathSpecification)), Environment.NewLine); } defaultMarkdown.Append(Environment.NewLine); content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks); @@ -109,8 +111,8 @@ namespace Docnet sb.Replace("{{Path}}", relativePathToRoot); sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/')); sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/')); - sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot)); - sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot)); + sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, pathSpecification)); + sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, pathSpecification)); sb.Replace("{{ExtraScript}}", (this.ExtraScriptProducerFunc == null) ? string.Empty : this.ExtraScriptProducerFunc(this)); // the last action has to be replacing the content marker, so markers in the content which we have in the template as well aren't replaced @@ -129,14 +131,15 @@ namespace Docnet /// /// The collected entries. /// The active path currently navigated. - public override void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath) + /// The path specification. + public override void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification) { activePath.Push(this); // simply convert ourselves into an entry if we're not an index if(!this.IsIndexElement) { var toAdd = new SearchIndexEntry(); - toAdd.Fill(this.MarkdownFromFile, this.TargetURL, this.Name, activePath); + toAdd.Fill(this.MarkdownFromFile, this.GetTargetURL(pathSpecification), this.Name, activePath); collectedEntries.Add(toAdd); } activePath.Pop(); @@ -148,8 +151,9 @@ namespace Docnet /// /// The navigated path to the current element, which doesn't necessarily have to be this element. /// The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path. + /// The path specification. /// - public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) + public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) { // index elements are rendered in the parent container. if(this.IsIndexElement) @@ -157,7 +161,7 @@ namespace Docnet return string.Empty; } - return PerformGenerateToCFragment(navigatedPath, relativePathToRoot); + return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification); } @@ -167,8 +171,9 @@ namespace Docnet /// /// The navigated path. /// The relative path to root. + /// The path specification. /// - public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot) + public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification) { // we can't navigate deeper from here. If we are the element being navigated to, we are the current and will have to emit any additional relative URLs too. bool isCurrent = navigatedPath.Contains(this); @@ -184,7 +189,7 @@ namespace Docnet string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass), string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass), relativePathToRoot, - HttpUtility.UrlPathEncode(this.TargetURL), + HttpUtility.UrlPathEncode(this.GetTargetURL(pathSpecification)), this.Name)); if(isCurrent && _relativeH2LinksOnPage.Any()) { @@ -203,26 +208,27 @@ namespace Docnet return string.Join(Environment.NewLine, fragments.ToArray()); } - - #region Properties - public override string TargetURL + /// + /// Gets the target URL with respect to the . + /// + /// The path specification. + /// + /// + public override string GetTargetURL(PathSpecification pathSpecification) { - get + if (_targetURLForHTML == null) { - if(_targetURLForHTML==null) + _targetURLForHTML = (this.Value ?? string.Empty); + if (_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) { - _targetURLForHTML = (this.Value ?? string.Empty); - if(_targetURLForHTML.ToLowerInvariant().EndsWith(".md")) - { - _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length-3) + ".htm"; - } - _targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); + _targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - 3) + ".htm"; } - return _targetURLForHTML; + _targetURLForHTML = _targetURLForHTML.Replace("\\", "/"); } + return _targetURLForHTML; } - + #region Properties /// /// Gets / sets a value indicating whether this element is the __index element ///