diff --git a/src/DocNet/Config.cs b/src/DocNet/Config.cs index 2ff4195..482e665 100644 --- a/src/DocNet/Config.cs +++ b/src/DocNet/Config.cs @@ -263,7 +263,7 @@ namespace Docnet if(_pages == null) { JObject rawPages = _configData.Pages; - _pages = new NavigationLevel() {Name = "Home", IsRoot = true}; + _pages = new NavigationLevel(Source) {Name = "Home", IsRoot = true}; _pages.Load(rawPages); } return _pages; diff --git a/src/DocNet/NavigationLevel.cs b/src/DocNet/NavigationLevel.cs index 57bc38b..bfc2ba1 100644 --- a/src/DocNet/NavigationLevel.cs +++ b/src/DocNet/NavigationLevel.cs @@ -33,30 +33,64 @@ namespace Docnet { public class NavigationLevel : NavigationElement> { - public NavigationLevel() : base() + #region Members + private readonly string _rootDirectory; + #endregion + + public NavigationLevel(string rootDirectory) + : base() { + this._rootDirectory = rootDirectory; this.Value = new List(); } public void Load(JObject dataFromFile) { - foreach(KeyValuePair child in dataFromFile) + foreach (KeyValuePair child in dataFromFile) { INavigationElement toAdd; - if(child.Value.Type == JTokenType.String) + if (child.Value.Type == JTokenType.String) { var nameToUse = child.Key; + var isIndexElement = child.Key == "__index"; - if(isIndexElement) + if (isIndexElement) { nameToUse = this.Name; } - toAdd = new SimpleNavigationElement() { Name = nameToUse, Value = child.Value.ToObject(), IsIndexElement = isIndexElement}; + + var childValue = child.Value.ToObject(); + if (childValue.EndsWith("**")) + { + var path = childValue.Replace("**", string.Empty) + .Replace('\\', Path.DirectorySeparatorChar) + .Replace('/', Path.DirectorySeparatorChar); + + if (!Path.IsPathRooted(path)) + { + path = Path.Combine(_rootDirectory, path); + } + toAdd = CreateGeneratedLevel(path); + toAdd.Name = nameToUse; + } + else + { + toAdd = new SimpleNavigationElement + { + Name = nameToUse, + Value = childValue, + IsIndexElement = isIndexElement + }; + } } else { - var subLevel = new NavigationLevel() { Name = child.Key, IsRoot = false}; + var subLevel = new NavigationLevel(_rootDirectory) + { + Name = child.Key, + IsRoot = false + }; subLevel.Load((JObject)child.Value); toAdd = subLevel; } @@ -74,7 +108,7 @@ namespace Docnet public override void CollectSearchIndexEntries(List collectedEntries, NavigatedPath activePath) { activePath.Push(this); - foreach(var element in this.Value) + foreach (var element in this.Value) { element.CollectSearchIndexEntries(collectedEntries, activePath); } @@ -91,7 +125,7 @@ namespace Docnet { activePath.Push(this); int i = 0; - while(i(); - if(!this.IsRoot) + if (!this.IsRoot) { fragments.Add("
  • "); } - if(navigatedPath.Contains(this)) + if (navigatedPath.Contains(this)) { // we're expanded. If we're not root and on the top of the navigated path stack, our index page is the page we're currently generating the ToC for, so // we have to mark the entry as 'current' - if(navigatedPath.Peek() == this && !this.IsRoot) + if (navigatedPath.Peek() == this && !this.IsRoot) { fragments.Add("
      "); } @@ -130,24 +164,24 @@ 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; - if(indexElement == null) + if (indexElement == null) { fragments.Add(string.Format("{0}{1}
    • ", elementStartTag, this.Name)); } else { - if(this.IsRoot) + if (this.IsRoot) { fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot)); } else { - fragments.Add(string.Format("{0}{3}", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL), + fragments.Add(string.Format("{0}{3}", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL), this.Name)); } } // then the elements in the container. Index elements are skipped here. - foreach(var element in this.Value) + foreach (var element in this.Value) { fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot)); } @@ -156,10 +190,10 @@ namespace Docnet else { // just a link - fragments.Add(string.Format(" {2}", + fragments.Add(string.Format(" {2}", relativePathToRoot, HttpUtility.UrlPathEncode(this.TargetURL), this.Name)); } - if(!this.IsRoot) + if (!this.IsRoot) { fragments.Add(""); } @@ -167,13 +201,83 @@ namespace Docnet } + private NavigationLevel CreateGeneratedLevel(string path) + { + var root = new NavigationLevel(_rootDirectory) + { + ParentContainer = this + }; + + foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly)) + { + var name = FindTitleInMdFile(mdFile); + if (string.IsNullOrWhiteSpace(name)) + { + continue; + } + + var item = new SimpleNavigationElement + { + Name = name, + Value = Utils.MakeRelativePath(mdFile, _rootDirectory), + ParentContainer = root + }; + + root.Value.Add(item); + } + + foreach (var directory in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly)) + { + var subDirectoryNavigationElement = CreateGeneratedLevel(directory); + subDirectoryNavigationElement.Name = new DirectoryInfo(directory).Name; + subDirectoryNavigationElement.ParentContainer = root; + + root.Value.Add(subDirectoryNavigationElement); + } + return root; + } + + + private string FindTitleInMdFile(string path) + { + var title = string.Empty; + + using (var fileStream = File.OpenRead(path)) + { + using (var streamReader = new StreamReader(fileStream)) + { + var line = string.Empty; + while (string.IsNullOrWhiteSpace(line)) + { + line = streamReader.ReadLine(); + if (!string.IsNullOrWhiteSpace(line)) + { + line = line.Trim(); + while (line.StartsWith("#")) + { + line = line.Substring(1).Trim(); + } + if (!string.IsNullOrWhiteSpace(line)) + { + title = line; + break; + } + } + } + } + } + + return title; + } + + #region Properties public override string TargetURL { get { var defaultElement = this.IndexElement; - if(defaultElement == null) + if (defaultElement == null) { return string.Empty; } @@ -187,20 +291,20 @@ namespace Docnet get { var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement; - if(toReturn == null) + if (toReturn == null) { // no index element, add an artificial one. var path = string.Empty; - if(this.ParentContainer != null) + if (this.ParentContainer != null) { path = Path.GetDirectoryName(this.ParentContainer.TargetURL); } var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", ""); - if(string.IsNullOrWhiteSpace(nameToUse)) + if (string.IsNullOrWhiteSpace(nameToUse)) { return null; } - toReturn = new SimpleNavigationElement() {ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true}; + toReturn = new SimpleNavigationElement() { ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true }; this.Value.Add(toReturn); } @@ -216,7 +320,8 @@ namespace Docnet { // never an index get { return false; } - set { + set + { // nop; } }