├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml ├── workflows │ └── merge-dependabot.yml └── stale.yml ├── src ├── Tests │ ├── IncludeFinder │ │ └── file.include.md │ ├── IncludeFileFinder │ │ ├── Simple │ │ │ ├── other.txt │ │ │ ├── file1.include.md │ │ │ └── file2.include.md │ │ ├── Nested │ │ │ └── nested │ │ │ │ └── nested │ │ │ │ ├── file.include.md │ │ │ │ └── other.txt │ │ └── VerifyLambdasAreCalled │ │ │ └── subpath │ │ │ └── file.include.md │ ├── GitDirs │ │ ├── WithRef │ │ │ ├── HEAD │ │ │ └── refs │ │ │ │ └── heads │ │ │ │ └── master │ │ └── NoRef │ │ │ └── HEAD │ ├── DirectoryMarkdownProcessor │ │ ├── Mdx │ │ │ ├── sourceFile.txt │ │ │ └── one.source.mdx │ │ ├── Convention │ │ │ ├── one.source.md │ │ │ └── mdsource │ │ │ │ └── two.source.md │ │ ├── NonMd │ │ │ ├── one.source.txt │ │ │ └── mdsource │ │ │ │ └── two.source.txt │ │ ├── ReadOnly │ │ │ └── one.source.md │ │ ├── FileSnippet │ │ │ ├── sourceFile.txt │ │ │ └── one.source.md │ │ ├── MissingInclude │ │ │ └── one.source.md │ │ ├── BinaryFileSnippet │ │ │ ├── sourceFile.dot │ │ │ └── one.source.md │ │ ├── MixedCaseInclude │ │ │ ├── fileToInclude.txt │ │ │ └── one.source.md │ │ ├── ValidationErrors │ │ │ └── one.source.md │ │ ├── ExplicitFileInclude │ │ │ ├── fileToInclude1.txt │ │ │ ├── fileToInclude2.txt │ │ │ ├── Nested │ │ │ │ ├── fileToInclude3.txt │ │ │ │ ├── fileToInclude4.txt │ │ │ │ ├── fileToInclude5.txt │ │ │ │ └── fileToInclude6.txt │ │ │ └── one.source.md │ │ ├── FileSnippetMissing │ │ │ └── one.source.md │ │ ├── InPlaceOverwriteExists │ │ │ ├── fileToInclude.txt │ │ │ ├── includeWithCode.txt │ │ │ ├── multiLineFileToInclude.txt │ │ │ └── file.md │ │ ├── InPlaceOverwriteExistsMdx │ │ │ ├── fileToInclude.txt │ │ │ ├── includeWithCode.txt │ │ │ ├── multiLineFileToInclude.txt │ │ │ └── file.mdx │ │ ├── InPlaceOverwriteNotExists │ │ │ ├── fileToInclude.txt │ │ │ ├── includeWithCode.txt │ │ │ ├── multiLineFileToInclude.txt │ │ │ └── file.md │ │ ├── ConventionWithNestedDir │ │ │ └── mdsource │ │ │ │ └── Nested │ │ │ │ └── one.source.md │ │ ├── ExplicitFileIncludeWithSnippetAtEnd │ │ │ ├── one.source.md │ │ │ └── fileToInclude.txt │ │ ├── FileSnippetWithWhiteSpace │ │ │ ├── sourceFile.txt │ │ │ └── one.source.md │ │ ├── ExplicitFileIncludeWithMergedSnippet │ │ │ ├── one.source.md │ │ │ └── fileToInclude.txt │ │ ├── InPlaceOverwriteWithFileSnippetMissing │ │ │ └── file.md │ │ ├── UrlInclude │ │ │ └── one.source.md │ │ ├── UrlSnippet │ │ │ └── one.source.md │ │ ├── UrlIncludeMissing │ │ │ └── one.source.md │ │ ├── UrlSnippetMissing │ │ │ └── one.source.md │ │ ├── InPlaceOverwriteUrlInclude │ │ │ └── one.md │ │ └── InPlaceOverwriteUrlSnippet │ │ │ └── one.md │ ├── MarkdownProcessor │ │ ├── TocBuilderTests.SanitizeLink.verified.txt │ │ ├── TocBuilderTests.EmptyHeading.verified.txt │ │ ├── TocBuilderTests.WithSpaces.verified.txt │ │ ├── TocBuilderTests.Exclude.verified.txt │ │ ├── TocBuilderTests.Single.verified.txt │ │ ├── TocBuilderTests.IgnoreTop.verified.txt │ │ ├── TocBuilderTests.Duplicates.verified.txt │ │ ├── TocBuilderTests.StripMarkdown.verified.txt │ │ ├── TocBuilderTests.StopAtLevel.verified.txt │ │ ├── MarkdownProcessorTests.Empty_snippet_key.verified.txt │ │ ├── MarkdownProcessorTests.Whitespace_snippet_key.verified.txt │ │ ├── MarkdownProcessorTests.Toc1.verified.txt │ │ ├── MarkdownProcessorTests.MissingInclude.verified.txt │ │ ├── MarkdownProcessorTests.WithSingleInclude.verified.txt │ │ ├── TocBuilderTests.DuplicateNested.verified.txt │ │ ├── MarkdownProcessorTests.WithSingleInclude_Overwrite.verified.txt │ │ ├── MarkdownProcessorTests.Missing_endInclude.verified.txt │ │ ├── MarkdownProcessorTests.WithDoubleInclude.verified.txt │ │ ├── TocBuilderTests.Nested.verified.txt │ │ ├── MarkdownProcessorTests.WithEmptyMultiLineInclude_Overwrite.verified.txt │ │ ├── MarkdownProcessorTests.WithEmptyMultipleInclude.verified.txt │ │ ├── TocBuilderTests.Deep.verified.txt │ │ ├── MarkdownProcessorTests.WithMultiLineInclude_Overwrite.verified.txt │ │ ├── MarkdownProcessorTests.Missing_endToc.verified.txt │ │ ├── MarkdownProcessorTests.WithMultipleInclude.verified.txt │ │ ├── MarkdownProcessorTests.SkipHeadingBeforeToc.verified.txt │ │ ├── MarkdownProcessorTests.WithMixedCaseInclude.verified.txt │ │ ├── MarkdownProcessorTests.Toc.verified.txt │ │ ├── MarkdownProcessorTests.TocRetainedIfNoHeadingsInFile.verified.txt │ │ ├── MarkdownProcessorTests.Toc_Overwrite.verified.txt │ │ ├── MarkdownProcessorTests.WrongNewlineInSnippet.verified.txt │ │ ├── MarkdownProcessorTests.TableInInclude.verified.txt │ │ ├── MarkdownProcessorTests.WithSingleSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithTabIndentedSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithIndentedSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithTwoLineSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithIndentedCommentSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithIndentedWebSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithMultiLineSnippet.verified.txt │ │ ├── MarkdownProcessorTests.WithIndentedSnippetMultipleSpaces.verified.txt │ │ ├── MarkdownProcessorTests.MixedNewlinesInFile.verified.txt │ │ ├── MarkdownProcessorTests.WithIndentedMultiLineSnippet.verified.txt │ │ ├── MarkdownProcessorTests.SnippetInInclude.verified.txt │ │ ├── MarkdownProcessorTests.SnippetInIncludeLast.verified.txt │ │ ├── SnippetKey_ExtractStartCommentSnippet.cs │ │ ├── MarkdownProcessorTests.WithMixedCaseSnippet.verified.txt │ │ ├── SnippetKey_ExtractTransform.cs │ │ ├── MarkdownProcessorTests.Simple.verified.txt │ │ └── MarkdownProcessorTests.Simple_Overwrite.verified.txt │ ├── LoopState │ │ ├── LoopStateTests.ExcludeEmptyPaddingLines.verified.txt │ │ ├── LoopStateTests.TrimIndentation.verified.txt │ │ ├── LoopStateTests.TrimIndentation_with_mis_match.verified.txt │ │ ├── LoopStateTests.TrimIndentation_no_initial_padding.verified.txt │ │ └── LoopStateTests.cs │ ├── SnippetFileFinder │ │ ├── Simple │ │ │ ├── code1.txt │ │ │ ├── code2.txt │ │ │ ├── code3.txt │ │ │ └── code4.txt │ │ ├── Nested │ │ │ └── nested │ │ │ │ └── nested │ │ │ │ └── code.txt │ │ └── VerifyLambdasAreCalled │ │ │ └── subpath │ │ │ └── code4.txt │ ├── DirectorySnippetExtractor │ │ ├── Case │ │ │ ├── code1.txt │ │ │ └── code2.txt │ │ ├── Simple │ │ │ ├── code1.txt │ │ │ ├── code2.txt │ │ │ ├── code3.txt │ │ │ └── code4.txt │ │ ├── Nested │ │ │ └── nested │ │ │ │ └── nested │ │ │ │ └── code.txt │ │ └── VerifyLambdasAreCalled │ │ │ └── subpath │ │ │ └── code4.txt │ ├── HeaderWriterTests.WriteHeaderHeaderCustom.verified.txt │ ├── FileToUseAsSnippet.txt │ ├── SimpleSnippetMarkdownHandlingTests.Append.verified.txt │ ├── SnippetMarkdownHandlingTests.AppendOmitSnippetLinks.verified.txt │ ├── SnippetFileFinderTests.Nested.verified.txt │ ├── DirectoryMarkdownProcessorTests.MixedCaseInclude.verified.txt │ ├── ContentValidationTest.CheckInvalidWordSentenceStart.verified.txt │ ├── ContentValidationTest.CheckInvalidWord.verified.txt │ ├── GlobalUsings.cs │ ├── ContentValidationTest.CheckInvalidWordSentenceEnd.verified.txt │ ├── ContentValidationTest.CheckInvalidWordStringEnd.verified.txt │ ├── ContentValidationTest.CheckInvalidWordWithComma.verified.txt │ ├── SimpleSnippetMarkdownHandlingTests.ExpressiveCode.verified.txt │ ├── ContentValidationTest.CheckInvalidWordWithQuestionMark.verified.txt │ ├── badsnippets │ │ └── code.txt │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteWithFileSnippetMissing.verified.md │ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForNoKey.verified.txt │ ├── SnippetExtractor │ │ ├── SnippetExtractorTests.UnClosedRegion.verified.txt │ │ ├── SnippetExtractorTests.UnClosedSnippet.verified.txt │ │ ├── SnippetExtractorTests.MixedNewLines.verified.txt │ │ ├── SnippetExtractorTests.AppendFileAsSnippet.verified.txt │ │ ├── SnippetExtractorTests.CanExtractFromRegion.verified.txt │ │ ├── SnippetExtractorTests.CanExtractFromXml.verified.txt │ │ ├── SnippetExtractorTests.CanExtractWithMissingSpaces.verified.txt │ │ ├── SnippetExtractorTests.CanExtractWithNoTrailingCharacters.verified.txt │ │ ├── SnippetExtractorTests.CanExtractWithTrailingWhitespace.verified.txt │ │ ├── SnippetExtractorTests.TooWide.verified.txt │ │ ├── SnippetExtractorTests.CanExtractWithExpressiveCode.verified.txt │ │ ├── SnippetExtractorTests.CanExtractWithInnerWhiteSpace.verified.txt │ │ ├── SnippetExtractorTests.NestedBroken.verified.txt │ │ ├── SnippetExtractorTests.NestedMixed1.verified.txt │ │ ├── SnippetExtractorTests.NestedMixed2.verified.txt │ │ ├── SnippetExtractorTests.NestedRegion.verified.txt │ │ ├── SnippetExtractorTests.NestedStartCode.verified.txt │ │ ├── SnippetExtractorTests.RemoveDuplicateNewlines.verified.txt │ │ └── SnippetExtractorTests.AppendUrlAsSnippet.verified.txt │ ├── SnippetMarkdownHandlingTests.AppendOmitSourceLink.verified.txt │ ├── SnippetMarkdownHandlingTests.Append.verified.txt │ ├── GirRepoDirectoryFinderTests.cs │ ├── SnippetMarkdownHandlingTests.AppendHashed.verified.txt │ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyEndingWithSymbol.verified.txt │ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyStartingWithSymbol.verified.txt │ ├── SnippetMarkdownHandlingTests.AppendPrefixed.verified.txt │ ├── HeaderWriterTests.DefaultHeader.verified.txt │ ├── DirectoryMarkdownProcessorTests.BinaryFileSnippet.verified.txt │ ├── SnippetFileFinderTests.Simple.verified.txt │ ├── HeaderWriterTests.WriteHeaderDefaultHeader.verified.txt │ ├── DirectoryMarkdownProcessorTests.ConventionWithNestedDir.verified.txt │ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithSnippetAtEnd.verified.txt │ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithMergedSnippet.verified.txt │ ├── DirectoryMarkdownProcessorTests.FileSnippetMissing.verified.txt │ ├── HeaderWriterTests.cs │ ├── DirectoryMarkdownProcessorTests.UrlIncludeMissing.verified.txt │ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage.verified.txt │ ├── SnippetExtensionsTests.ToDictionary.verified.txt │ ├── DownloaderTests.Valid.verified.txt │ ├── DirectoryMarkdownProcessorTests.ExplicitFileInclude.verified.txt │ ├── SnippetFileFinderTests.VerifyLambdasAreCalled.verified.txt │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExists.verified.md │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteNotExists.verified.md │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExistsMdx.verified.mdx │ ├── DirectoryMarkdownProcessorTests.UrlSnippetMissing.verified.txt │ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase.verified.txt │ ├── ModuleInitializer.cs │ ├── DirectoryMarkdownProcessorTests.Convention.verified.txt │ ├── DownloaderTests.cs │ ├── SnippetExtensionsTests.ToDictionary_SameKey.verified.txt │ ├── WebSnippetTests.cs │ ├── ProcessResultConverter.cs │ ├── IndexReaderTests.cs │ ├── DirectoryMarkdownProcessorTests.Mdx.verified.txt │ ├── DirectoryMarkdownProcessorTests.FileSnippet.verified.txt │ ├── DirectoryMarkdownProcessorTests.FileSnippetWithWhiteSpace.verified.txt │ ├── SnippetExtensionsTests.cs │ ├── SimpleSnippetMarkdownHandlingTests.cs │ ├── DirectoryMarkdownProcessorTests.UrlInclude.verified.txt │ ├── Snippets │ │ └── Usage.cs │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlInclude.verified.txt │ ├── SnippetConverter.cs │ ├── DirectoryMarkdownProcessorTests.ValidationErrors.verified.txt │ ├── DirectoryMarkdownProcessorTests.UrlSnippet.verified.txt │ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlSnippet.verified.txt │ ├── StartEndTester_IsStartRegionTests.cs │ └── ContentValidationTest.cs ├── ConfigReader.Tests │ ├── ConfigReaderTests.Empty.verified.txt │ ├── GlobalUsings.cs │ ├── InPlaceOverwrite.json │ ├── SourceTransform.json │ ├── ModuleInitializer.cs │ ├── ConfigReaderTests.BadJson.verified.txt │ ├── ConfigReaderTests.Values.verified.txt │ ├── allConfig.json │ ├── ConfigReaderTests.cs │ └── ConfigReader.Tests.csproj ├── MarkdownSnippets │ ├── Reading │ │ ├── EndFunc.cs │ │ ├── IContent.cs │ │ ├── ShouldIncludeDirectory.cs │ │ ├── LineTooLongException.cs │ │ ├── LoopStack.cs │ │ ├── Exclusions │ │ │ └── DefaultDirectoryExclusions.cs │ │ └── ReadSnippets.cs │ ├── SnippetException.cs │ ├── SnippetReadingException.cs │ ├── Processing │ │ ├── DocumentConvention.cs │ │ ├── AppendSnippetsToMarkdown.cs │ │ ├── LinkFormat.cs │ │ ├── Markdown.cs │ │ ├── SimpleSnippetMarkdownHandling.cs │ │ ├── Lines.cs │ │ ├── HeaderWriter.cs │ │ ├── Line.cs │ │ ├── MissingInclude.cs │ │ ├── MissingSnippet.cs │ │ ├── ValidationError.cs │ │ └── RelativeFile.cs │ ├── GlobalUsings.cs │ ├── MissingIncludesException.cs │ ├── Paths.cs │ ├── KeyValidator.cs │ ├── SnippetExtensions.cs │ ├── Downloader │ │ ├── FileNameFromUrl.cs │ │ └── Timestamp.cs │ ├── MarkdownProcessingException.cs │ ├── MissingSnippetsException.cs │ ├── MarkdownSnippets.csproj │ ├── StringBuilderCache.cs │ ├── ContentValidationException.cs │ ├── AssemblyInfo.cs │ ├── FileEx.cs │ ├── InterpretErrors.cs │ ├── Guard.cs │ └── GitRepoDirectoryFinder.cs ├── MarkdownSnippets.Tool.Tests │ ├── CommandRunnerTests.VerifyContentLong.verified.txt │ ├── CommandRunnerTests.VerifyContentShort.verified.txt │ ├── GlobalUsings.cs │ ├── CommandRunnerTests.SingleUnNamedArg.verified.txt │ ├── CommandRunnerTests.Empty.verified.txt │ ├── CommandRunnerTests.TargetDirectoryLong.verified.txt │ ├── CommandRunnerTests.TargetDirectoryShort.verified.txt │ ├── CommandRunnerTests.Header.verified.txt │ ├── CommandRunnerTests.MaxWidthLong.verified.txt │ ├── CommandRunnerTests.TocLevelLong.verified.txt │ ├── CommandRunnerTests.LinkFormatLong.verified.txt │ ├── CommandRunnerTests.LinkFormatShort.verified.txt │ ├── CommandRunnerTests.ReadOnlyLong.verified.txt │ ├── CommandRunnerTests.ReadOnlyShort.verified.txt │ ├── CommandRunnerTests.UrlPrefix.verified.txt │ ├── CommandRunnerTests.WriteHeader.verified.txt │ ├── CommandRunnerTests.OmitSnippetLinks.verified.txt │ ├── CommandRunnerTests.ValidateContentLong.verified.txt │ ├── CommandRunnerTests.ConventionLong.verified.txt │ ├── CommandRunnerTests.ConventionShort.verified.txt │ ├── CommandRunnerTests.ValidateContentShort.verified.txt │ ├── CommandRunnerTests.ExcludeLong.verified.txt │ ├── CommandRunnerTests.ExcludeShort.verified.txt │ ├── CommandRunnerTests.UrlsAsSnippetsLong.verified.txt │ ├── CommandRunnerTests.UrlsAsSnippetsShort.verified.txt │ ├── CommandRunnerTests.ExcludeMultiple.verified.txt │ ├── CommandRunnerTests.ExcludeSnippetDirectoriesLong.verified.txt │ ├── CommandRunnerTests.UrlsAsSnippetsMultiple.verified.txt │ ├── CommandRunnerTests.ExcludeMarkdownDirectoriesLong.verified.txt │ ├── ModuleInitializer.cs │ ├── LogBuilderTests.BuildConfigLogMessageMinimal.DotNet9_0.verified.txt │ ├── LogBuilderTests.BuildConfigLogMessage.DotNet9_0.verified.txt │ ├── LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet9_0.verified.txt │ ├── MarkdownSnippets.Tool.Tests.csproj │ └── LogBuilderTests.cs ├── icon.png ├── key.snk ├── MarkdownSnippets.Tool │ ├── Invoke.cs │ ├── CommandLineException.cs │ ├── GlobalUsings.cs │ ├── AssemblyInfo.cs │ └── MarkdownSnippets.Tool.csproj ├── ConfigReader │ ├── ConfigurationException.cs │ ├── SharedGlobalUsings.cs │ ├── AssemblyInfo.cs │ ├── ConfigReader.csproj │ ├── ConfigResult.cs │ ├── ConfigSerialization.cs │ ├── ConfigInput.cs │ └── ExcludeToFilterBuilder.cs ├── global.json ├── mdsnippets.json ├── MarkdownSnippets.MsBuild │ ├── LoggingHelper.cs │ ├── MarkdownSnippets.MsBuild.targets │ └── MarkdownSnippets.MsBuild.csproj ├── context-menu.reg ├── MarkdownSnippets.slnx ├── MarkdownSnippets.slnx.DotSettings ├── Directory.Build.props ├── appveyor.yml ├── nuget.config └── Directory.Packages.props ├── docs ├── mdsource │ ├── readme.source.md │ ├── toc │ │ ├── tocBefore.txt │ │ └── tocAfter.txt │ ├── api.source.md │ ├── doc-index.include.md │ ├── header.source.md │ ├── toc.source.md │ ├── msbuild.source.md │ ├── github-action.source.md │ ├── indentation.source.md │ ├── exclusion.source.md │ ├── max-width.source.md │ ├── includes.source.md │ └── config-file.source.md ├── code-completion.png ├── code-completion-values.png ├── stop-regions-collapsing.png ├── on-push-do-docs.yml ├── readme.md ├── msbuild.md ├── api.md ├── indentation.md ├── header.md ├── includes.md ├── toc.md ├── github-action.md └── max-width.md ├── .gitignore ├── .gitattributes └── license.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: SimonCropp 2 | -------------------------------------------------------------------------------- /src/Tests/IncludeFinder/file.include.md: -------------------------------------------------------------------------------- 1 | Simple1 -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/Simple/other.txt: -------------------------------------------------------------------------------- 1 | Simple2 -------------------------------------------------------------------------------- /src/Tests/GitDirs/WithRef/HEAD: -------------------------------------------------------------------------------- 1 | ref: refs/heads/master 2 | -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/Simple/file1.include.md: -------------------------------------------------------------------------------- 1 | Simple1 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ConfigReaderTests.Empty.verified.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /docs/mdsource/readme.source.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | include: doc-index -------------------------------------------------------------------------------- /src/ConfigReader.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using VerifyTests.DiffPlex; -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/Mdx/sourceFile.txt: -------------------------------------------------------------------------------- 1 | From Source File -------------------------------------------------------------------------------- /src/Tests/GitDirs/NoRef/HEAD: -------------------------------------------------------------------------------- 1 | 061e8aee1b5174df8ed7b201e0d8dd5a0d155418 2 | -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/Nested/nested/nested/file.include.md: -------------------------------------------------------------------------------- 1 | Nested -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.SanitizeLink.verified.txt: -------------------------------------------------------------------------------- 1 | A_-b -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/EndFunc.cs: -------------------------------------------------------------------------------- 1 | delegate bool EndFunc(CharSpan line); -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/Convention/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: snippet1 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/NonMd/one.source.txt: -------------------------------------------------------------------------------- 1 | snippet: snippet1 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ReadOnly/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: snippet1 -------------------------------------------------------------------------------- /src/Tests/LoopState/LoopStateTests.ExcludeEmptyPaddingLines.verified.txt: -------------------------------------------------------------------------------- 1 | Line2 -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.VerifyContentLong.verified.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.VerifyContentShort.verified.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/FileSnippet/sourceFile.txt: -------------------------------------------------------------------------------- 1 | From Source File -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/MissingInclude/one.source.md: -------------------------------------------------------------------------------- 1 | include: include1 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/NonMd/mdsource/two.source.txt: -------------------------------------------------------------------------------- 1 | snippet: snippet2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/BinaryFileSnippet/sourceFile.dot: -------------------------------------------------------------------------------- 1 | From Source File -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/Convention/mdsource/two.source.md: -------------------------------------------------------------------------------- 1 | snippet: snippet2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/MixedCaseInclude/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ValidationErrors/one.source.md: -------------------------------------------------------------------------------- 1 | you 2 | we 3 | ! -------------------------------------------------------------------------------- /src/Tests/GitDirs/WithRef/refs/heads/master: -------------------------------------------------------------------------------- 1 | ad4901fc6e8bf4a253ee053327eba32fe31010ed 2 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/HEAD/src/icon.png -------------------------------------------------------------------------------- /src/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/HEAD/src/key.snk -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/BinaryFileSnippet/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: sourceFile.dot -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/fileToInclude1.txt: -------------------------------------------------------------------------------- 1 | The include text 1 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/fileToInclude2.txt: -------------------------------------------------------------------------------- 1 | The include text 2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/FileSnippetMissing/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: missing.txt -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExists/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/MixedCaseInclude/one.source.md: -------------------------------------------------------------------------------- 1 | include: fIletoinClude.txt -------------------------------------------------------------------------------- /src/Tests/LoopState/LoopStateTests.TrimIndentation.verified.txt: -------------------------------------------------------------------------------- 1 | Line1 2 | Line2 3 | Line2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExistsMdx/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteNotExists/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/VerifyLambdasAreCalled/subpath/file.include.md: -------------------------------------------------------------------------------- 1 | VerifyLambdasAreCalled -------------------------------------------------------------------------------- /src/Tests/LoopState/LoopStateTests.TrimIndentation_with_mis_match.verified.txt: -------------------------------------------------------------------------------- 1 | Line2 2 | 3 | Line4 -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/Simple/code1.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet2 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/Simple/code2.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/Simple/code3.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet2 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/Simple/code4.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool/Invoke.cs: -------------------------------------------------------------------------------- 1 | public delegate Task Invoke(string directory, ConfigInput config); -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude3.txt: -------------------------------------------------------------------------------- 1 | The include text 3 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude4.txt: -------------------------------------------------------------------------------- 1 | The include text 4 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude5.txt: -------------------------------------------------------------------------------- 1 | The include text 5 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude6.txt: -------------------------------------------------------------------------------- 1 | The include text 6 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExists/includeWithCode.txt: -------------------------------------------------------------------------------- 1 | ``` 2 | The Code 3 | ``` -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Case/code1.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snipPet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Case/code2.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: Snippet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/HeaderWriterTests.WriteHeaderHeaderCustom.verified.txt: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.EmptyHeading.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ConventionWithNestedDir/mdsource/Nested/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: snippet1 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExists/multiLineFileToInclude.txt: -------------------------------------------------------------------------------- 1 | Line 1 2 | 3 | Line 2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExistsMdx/includeWithCode.txt: -------------------------------------------------------------------------------- 1 | ``` 2 | The Code 3 | ``` -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteNotExists/includeWithCode.txt: -------------------------------------------------------------------------------- 1 | ``` 2 | The Code 3 | ``` -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Simple/code1.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet2 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Simple/code2.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Simple/code3.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet2 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Simple/code4.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/FileToUseAsSnippet.txt: -------------------------------------------------------------------------------- 1 | The 2 | Content 3 | #region aaa 4 | #region indented 5 | From 6 | File -------------------------------------------------------------------------------- /src/Tests/SimpleSnippetMarkdownHandlingTests.Append.verified.txt: -------------------------------------------------------------------------------- 1 | ```thelanguage 2 | theValue 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/code-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/HEAD/docs/code-completion.png -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileIncludeWithSnippetAtEnd/one.source.md: -------------------------------------------------------------------------------- 1 | include: fileToInclude.txt -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExistsMdx/multiLineFileToInclude.txt: -------------------------------------------------------------------------------- 1 | Line 1 2 | 3 | Line 2 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteNotExists/multiLineFileToInclude.txt: -------------------------------------------------------------------------------- 1 | Line 1 2 | 3 | Line 2 -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/Simple/file2.include.md: -------------------------------------------------------------------------------- 1 | begin-snippet: nestedsnippet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | bin/ 4 | obj/ 5 | .vs/ 6 | *.DotSettings.user 7 | .idea/ 8 | *.received.* 9 | nugets/ -------------------------------------------------------------------------------- /src/ConfigReader/ConfigurationException.cs: -------------------------------------------------------------------------------- 1 | class ConfigurationException(string message) : 2 | Exception(message); -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using MarkdownSnippets; 2 | global using VerifyTests.DiffPlex; -------------------------------------------------------------------------------- /src/Tests/IncludeFileFinder/Nested/nested/nested/other.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: nestedsnippet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/LoopState/LoopStateTests.TrimIndentation_no_initial_padding.verified.txt: -------------------------------------------------------------------------------- 1 | Line1 2 | Line2 3 | Line2 -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/Nested/nested/nested/code.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: nestedsnippet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetMarkdownHandlingTests.AppendOmitSnippetLinks.verified.txt: -------------------------------------------------------------------------------- 1 | ```thelanguage 2 | theValue 3 | ``` 4 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool/CommandLineException.cs: -------------------------------------------------------------------------------- 1 | class CommandLineException(string message) : 2 | Exception(message); -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/Nested/nested/nested/code.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: nestedsnippet 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinder/VerifyLambdasAreCalled/subpath/code4.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /docs/code-completion-values.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/HEAD/docs/code-completion-values.png -------------------------------------------------------------------------------- /docs/stop-regions-collapsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/HEAD/docs/stop-regions-collapsing.png -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/FileSnippetWithWhiteSpace/sourceFile.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | From Source File 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using CommandLine; 2 | global using MarkdownSnippets; 3 | global using Polyfills; -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileIncludeWithSnippetAtEnd/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text 2 | 3 | snippet: snippet1 -------------------------------------------------------------------------------- /src/Tests/DirectorySnippetExtractor/VerifyLambdasAreCalled/subpath/code4.txt: -------------------------------------------------------------------------------- 1 | begin-snippet: snippet1 2 | Some code 3 | end-snippet -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinderTests.Nested.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | {ProjectDirectory}SnippetFileFinder/Nested/nested/nested/code.txt 3 | ] -------------------------------------------------------------------------------- /docs/mdsource/toc/tocBefore.txt: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | toc 4 | 5 | ## Heading 1 6 | 7 | Text1 8 | 9 | ## Heading 1 10 | 11 | Text2 -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.SingleUnNamedArg.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: dir, 3 | configInput: {} 4 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileIncludeWithMergedSnippet/one.source.md: -------------------------------------------------------------------------------- 1 | some content 2 | 3 | include: fileToInclude.txt -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteWithFileSnippetMissing/file.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.MixedCaseInclude.verified.txt: -------------------------------------------------------------------------------- 1 | The include text 2 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.WithSpaces.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [A B](#a-b) -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.Empty.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: {} 4 | } -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordSentenceStart.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you' 4 | } 5 | ] -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.Exclude.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading1](#heading1) -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.Single.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading](#heading) -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/IContent.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public interface IContent 4 | { 5 | string? Path { get; } 6 | } -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWord.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you', 4 | Item2: 1 5 | } 6 | ] -------------------------------------------------------------------------------- /src/Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Argon; 2 | global using MarkdownSnippets; 3 | global using Polyfills; 4 | global using VerifyTests.DiffPlex; -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.IgnoreTop.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading2](#heading2) -------------------------------------------------------------------------------- /src/MarkdownSnippets/SnippetException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class SnippetException(string message) : 4 | Exception(message); -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/UrlInclude/one.source.md: -------------------------------------------------------------------------------- 1 | include: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/UrlSnippet/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/ShouldIncludeDirectory.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public delegate bool ShouldIncludeDirectory(string directoryPath); -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordSentenceEnd.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you', 4 | Item2: 1 5 | } 6 | ] -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordStringEnd.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you', 4 | Item2: 4 5 | } 6 | ] -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordWithComma.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you', 4 | Item2: 1 5 | } 6 | ] -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/UrlIncludeMissing/one.source.md: -------------------------------------------------------------------------------- 1 | include: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/UrlSnippetMissing/one.source.md: -------------------------------------------------------------------------------- 1 | snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt -------------------------------------------------------------------------------- /src/Tests/SimpleSnippetMarkdownHandlingTests.ExpressiveCode.verified.txt: -------------------------------------------------------------------------------- 1 | ```cs title="HelloWorld.cs" {1} 2 | Console.WriteLine("Hello World"); 3 | ``` 4 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TargetDirectoryLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {ProjectDirectory}bin/, 3 | configInput: {} 4 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TargetDirectoryShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {ProjectDirectory}bin/, 3 | configInput: {} 4 | } -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordWithQuestionMark.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: Invalid word detected: 'you', 4 | Item2: 1 5 | } 6 | ] -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/LineTooLongException.cs: -------------------------------------------------------------------------------- 1 | class LineTooLongException(string line) : 2 | Exception 3 | { 4 | public string Line { get; } = line; 5 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.Duplicates.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [A](#a) 5 | * [A](#a-1) 6 | * [a](#a-2) -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.StripMarkdown.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [bold italic Link](#bold-italic-link) -------------------------------------------------------------------------------- /src/MarkdownSnippets/SnippetReadingException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class SnippetReadingException(string message) : 4 | SnippetException(message); -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.Header.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | Header: the header 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.MaxWidthLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | MaxWidth: 5 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TocLevelLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | TocLevel: 5 5 | } 6 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.StopAtLevel.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading1](#heading1) 5 | * [Heading2](#heading2) -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/src" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.LinkFormatLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | LinkFormat: Tfs 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.LinkFormatShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | LinkFormat: Tfs 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ReadOnlyLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ReadOnly: false 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ReadOnlyShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ReadOnly: false 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlPrefix.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | UrlPrefix: the prefix 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.WriteHeader.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | WriteHeader: false 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/DocumentConvention.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public enum DocumentConvention 4 | { 5 | SourceTransform, 6 | InPlaceOverwrite 7 | } -------------------------------------------------------------------------------- /src/Tests/badsnippets/code.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | begin-snippet: snippet1 4 | this is some text to import 5 | end-snippet 6 | begin-snippet: snippet1 7 | this is some text to import 8 | end-snippet -------------------------------------------------------------------------------- /src/ConfigReader.Tests/InPlaceOverwrite.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", 3 | "Convention": "InPlaceOverwrite" 4 | } -------------------------------------------------------------------------------- /src/ConfigReader.Tests/SourceTransform.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", 3 | "Convention": "SourceTransform" 4 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.OmitSnippetLinks.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | OmitSnippetLinks: true 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ValidateContentLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ValidateContent: false 5 | } 6 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Empty_snippet_key.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetException, 3 | Message: Could not parse snippet from: snippet:. Path: . Line: 2 4 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ConventionLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | Convention: InPlaceOverwrite 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ConventionShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | Convention: InPlaceOverwrite 5 | } 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ValidateContentShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ValidateContent: false 5 | } 6 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Whitespace_snippet_key.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetException, 3 | Message: Could not parse snippet from: snippet:. Path: . Line: 2 4 | } -------------------------------------------------------------------------------- /src/ConfigReader/SharedGlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Polyfills; 2 | global using MarkdownSnippets; 3 | global using System.Runtime.Serialization; 4 | global using System.Runtime.Serialization.Json; 5 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Toc1.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | # Title 4 | 5 | toc1 6 | 7 | ## Heading 1 8 | 9 | Text1 10 | 11 | ## Heading 2 12 | 13 | Text2 14 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ExcludeDirectories: [ 5 | dir 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteNotExists/file.md: -------------------------------------------------------------------------------- 1 | snippet: snippet1 2 | 3 | include: fileToInclude.txt 4 | 5 | include: multiLineFileToInclude.txt 6 | 7 | include: includeWithCode.txt -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.MissingInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | ** Could not find include 'theKey' ** 6 | 7 | after 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ExcludeDirectories: [ 5 | dir 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | UrlsAsSnippets: [ 5 | url 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsShort.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | UrlsAsSnippets: [ 5 | url 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/AppendSnippetsToMarkdown.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public delegate void AppendSnippetsToMarkdown(string key, IEnumerable snippets, Action appendLine); -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteWithFileSnippetMissing.verified.md: -------------------------------------------------------------------------------- 1 | 2 | ``` 3 | ** Could not find snippet 'missing.txt' ** 4 | ``` 5 | 6 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteUrlInclude/one.md: -------------------------------------------------------------------------------- 1 | BAD 2 | BAD 3 | BAD -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithSingleInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | 7 | after 8 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.DuplicateNested.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading](#heading) 5 | * [Heading](#heading-1) 6 | * [Heading](#heading-2) -------------------------------------------------------------------------------- /src/Tests/StartEndTester_IsBeginSnippetTests.ShouldThrowForNoKey.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetReadingException, 3 | Message: 4 | No Key could be derived. 5 | Path: file 6 | Line: '' 7 | } -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | public static class ModuleInitializer 2 | { 3 | [ModuleInitializer] 4 | public static void Initialize() => 5 | VerifyDiffPlex.Initialize(OutputType.Compact); 6 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/LinkFormat.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public enum LinkFormat 4 | { 5 | GitHub, 6 | Tfs, 7 | Bitbucket, 8 | GitLab, 9 | DevOps, 10 | None 11 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.UnClosedRegion.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Error: Snippet was not closed, 5 | FileLocation: path.cs(3-3), 6 | IsInError: true 7 | } 8 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.UnClosedSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Error: Snippet was not closed, 5 | FileLocation: path.cs(2-2), 6 | IsInError: true 7 | } 8 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetMarkdownHandlingTests.AppendOmitSourceLink.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ```thelanguage 3 | theValue 4 | ``` 5 | anchor 6 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Diagnostics.CodeAnalysis; 2 | global using System.Net; 3 | global using System.Net.Http; 4 | global using System.Text.RegularExpressions; 5 | global using MarkdownSnippets; -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/Mdx/one.source.mdx: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | snippet: sourceFile.txt 4 | 5 | Relative Local 6 | 7 | snippet: ./sourceFile.txt 8 | 9 | Rooted 10 | 11 | snippet: /sourceFile.txt 12 | 13 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithSingleInclude_Overwrite.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | 7 | after 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeMultiple.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ExcludeDirectories: [ 5 | dir1, 6 | dir2 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeSnippetDirectoriesLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ExcludeSnippetDirectories: [ 5 | dir 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsMultiple.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | UrlsAsSnippets: [ 5 | url1, 6 | url2 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/FileSnippet/one.source.md: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | snippet: sourceFile.txt 4 | 5 | Relative Local 6 | 7 | snippet: ./sourceFile.txt 8 | 9 | Rooted 10 | 11 | snippet: /sourceFile.txt 12 | 13 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Missing_endInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: MarkdownProcessingException, 3 | LineNumber: 2, 4 | Message: Expected to find ``. File: . LineNumber: 2. 5 | } -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "MSBuild.Sdk.Extras": "3.0.44" 4 | }, 5 | "sdk": { 6 | "version": "10.0.101", 7 | "allowPrerelease": true, 8 | "rollForward": "latestFeature" 9 | } 10 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeMarkdownDirectoriesLong.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | targetDirectory: {CurrentDirectory}, 3 | configInput: { 4 | ExcludeMarkdownDirectories: [ 5 | dir 6 | ] 7 | } 8 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithDoubleInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | theValue2 7 | 8 | after 9 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.Nested.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading1](#heading1) 5 | * [Heading2](#heading2) 6 | * [Heading3](#heading3) 7 | * [Heading4](#heading4) -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithEmptyMultiLineInclude_Overwrite.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | one 6 | two 7 | 8 | after 9 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithEmptyMultipleInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | 6 | 7 | 8 | 9 | after 10 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/TocBuilderTests.Deep.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ## Contents 3 | 4 | * [Heading1](#heading1) 5 | * [Heading2](#heading2) 6 | * [Heading3](#heading3) 7 | * [Heading4](#heading4) -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/FileSnippetWithWhiteSpace/one.source.md: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | snippet: sourceFile.txt 4 | 5 | Relative Local 6 | 7 | snippet: ./sourceFile.txt 8 | 9 | Rooted 10 | 11 | snippet: /sourceFile.txt 12 | 13 | -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.MixedNewLines.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Key: CodeKey, 3 | Language: cs, 4 | Value: 5 | A 6 | B 7 | C 8 | D, 9 | Error: , 10 | FileLocation: path.cs(1-6), 11 | IsInError: false 12 | } -------------------------------------------------------------------------------- /docs/mdsource/toc/tocAfter.txt: -------------------------------------------------------------------------------- 1 | # Title 2 | 3 | 4 | ## Contents 5 | 6 | * [Heading 1](#heading-1) 7 | * [Heading 2](#heading-2) 8 | 9 | 10 | ## Heading 1 11 | 12 | Text1 13 | 14 | ## Heading 2 15 | 16 | Text2 -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithMultiLineInclude_Overwrite.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | theValue2 7 | 8 | after 9 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Missing_endToc.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: MarkdownProcessingException, 3 | File: sourceFile, 4 | LineNumber: 2, 5 | Message: Expected to find ``. File: sourceFile. LineNumber: 2. 6 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithMultipleInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | theValue2 7 | theValue3 8 | 9 | after 10 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.AppendFileAsSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: File.tmp, 4 | Language: tmp, 5 | Value: Foo, 6 | Error: , 7 | FileLocation: FilePath.txt(1-1), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractFromRegion.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: The Code, 6 | Error: , 7 | FileLocation: path.cs(2-4), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/mdsnippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", 3 | "TocLevel": 1, 4 | "TocExcludes": [ "Credits", "Release Notes", "Icon" ], 5 | "MaxWidth": 80, 6 | "ValidateContent": true 7 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractFromXml.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: , 6 | Error: , 7 | FileLocation: path.cs(1-3), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetMarkdownHandlingTests.Append.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ```thelanguage 3 | theValue 4 | ``` 5 | snippet source | anchor 6 | -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractWithMissingSpaces.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: , 6 | Error: , 7 | FileLocation: path.cs(2-4), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractWithNoTrailingCharacters.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: the code, 6 | Error: , 7 | FileLocation: path.cs(2-4), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractWithTrailingWhitespace.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: the code, 6 | Error: , 7 | FileLocation: path.cs(2-4), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/GirRepoDirectoryFinderTests.cs: -------------------------------------------------------------------------------- 1 | public class GirRepoDirectoryFinderTests 2 | { 3 | [Fact] 4 | public void CanFindGirRepoDir() 5 | { 6 | var path = GitRepoDirectoryFinder.FindForFilePath(); 7 | Assert.True(Directory.Exists(path)); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.SkipHeadingBeforeToc.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | ## Heading 1 4 | 5 | 6 | ## Contents 7 | 8 | * [Heading 2](#heading-2) 9 | 10 | Text1 11 | 12 | ## Heading 2 13 | 14 | Text2 15 | } -------------------------------------------------------------------------------- /src/Tests/SnippetMarkdownHandlingTests.AppendHashed.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ```thelanguage 3 | theValue 4 | ``` 5 | snippet source | anchor 6 | -------------------------------------------------------------------------------- /src/Tests/StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyEndingWithSymbol.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetReadingException, 3 | Message: 4 | Key cannot contain whitespace or start/end with symbols. 5 | Key: key_ 6 | Path: file 7 | Line: 8 | } -------------------------------------------------------------------------------- /src/Tests/StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyStartingWithSymbol.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetReadingException, 3 | Message: 4 | Key cannot contain whitespace or start/end with symbols. 5 | Key: _key 6 | Path: file 7 | Line: 8 | } -------------------------------------------------------------------------------- /src/Tests/SnippetMarkdownHandlingTests.AppendPrefixed.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | ```thelanguage 3 | theValue 4 | ``` 5 | snippet source | anchor 6 | -------------------------------------------------------------------------------- /src/Tests/HeaderWriterTests.DefaultHeader.verified.txt: -------------------------------------------------------------------------------- 1 | GENERATED FILE - DO NOT EDIT 2 | This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets). 3 | Source File: {relativePath} 4 | To change this file edit the source file and then run MarkdownSnippets. -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.TooWide.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Error: Line too long: caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab, 5 | FileLocation: path.cs(3-3), 6 | IsInError: true 7 | } 8 | ] -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.BinaryFileSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```dot 4 | From Source File 5 | ``` 6 | anchor 7 | 8 | -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractWithExpressiveCode.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: Console.WriteLine("Hello World");, 6 | Error: , 7 | FileLocation: path.cs(1-3), 8 | IsInError: false 9 | } 10 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinderTests.Simple.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | {ProjectDirectory}SnippetFileFinder/Simple/code1.txt, 3 | {ProjectDirectory}SnippetFileFinder/Simple/code2.txt, 4 | {ProjectDirectory}SnippetFileFinder/Simple/code3.txt, 5 | {ProjectDirectory}SnippetFileFinder/Simple/code4.txt 6 | ] -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteUrlSnippet/one.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | BAD 4 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/one.source.md: -------------------------------------------------------------------------------- 1 | include: fileToInclude1.txt 2 | 3 | include: /fileToInclude2.txt 4 | 5 | include: fileToInclude3.txt 6 | 7 | include: /fileToInclude4.txt 8 | 9 | include: Nested/fileToInclude5.txt 10 | 11 | include: /Nested/fileToInclude6.txt -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithMixedCaseInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | before 4 | 5 | theValue1 6 | 7 | theValue2 8 | 9 | after 10 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.CanExtractWithInnerWhiteSpace.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: CodeKey, 4 | Language: cs, 5 | Value: 6 | BeforeWhiteSpace 7 | 8 | AfterWhiteSpace, 9 | Error: , 10 | FileLocation: path.cs(2-8), 11 | IsInError: false 12 | } 13 | ] -------------------------------------------------------------------------------- /src/Tests/HeaderWriterTests.WriteHeaderDefaultHeader.verified.txt: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Toc.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | # Title 4 | 5 | 6 | ## Contents 7 | 8 | * [Heading 1](#heading-1) 9 | * [Heading 2](#heading-2) 10 | 11 | ## Heading 1 12 | 13 | Text1 14 | 15 | ## Heading 2 16 | 17 | Text2 18 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.TocRetainedIfNoHeadingsInFile.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | # Title 4 | 5 | 6 | 7 | 8 | This document has no headings. 9 | 10 | An empty toc section should be generated, in case 11 | any headings are added in future. 12 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Toc_Overwrite.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | # Title 4 | 5 | 6 | ## Contents 7 | 8 | * [Heading 1](#heading-1) 9 | * [Heading 2](#heading-2) 10 | 11 | ## Heading 1 12 | 13 | Text1 14 | 15 | ## Heading 2 16 | 17 | Text2 18 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WrongNewlineInSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | MissingSnippets: [ 3 | { 4 | Key: 'snippet1', 5 | LineNumber: 2 6 | } 7 | ], 8 | content: " 9 | 10 | ** Could not find snippet 'snippet1' ** 11 | 12 | some text 13 | " 14 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.MsBuild/LoggingHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Build.Utilities; 2 | 3 | static class LoggingHelper 4 | { 5 | public static void LogFileError(this TaskLoggingHelper loggingHelper, string message, string? file, int line, int column) => 6 | loggingHelper.LogError(null, null, null, file, line, column, 0, 0, message); 7 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/Markdown.cs: -------------------------------------------------------------------------------- 1 | static class Markdown 2 | { 3 | static Regex stripLinkRegex = new(@"\[(.*?)\][\[\(].*?[\]\)]", RegexOptions.Compiled); 4 | 5 | public static string StripMarkdown(string input) 6 | { 7 | var title = input.Replace("*",""); 8 | return stripLinkRegex.Replace(title, "$1"); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/ExplicitFileIncludeWithMergedSnippet/fileToInclude.txt: -------------------------------------------------------------------------------- 1 | The include text 2 | 3 | 4 | 5 | ```.cs 6 | the code from snippet1 7 | ``` 8 | anchor 9 | 10 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | public static class ModuleInitializer 2 | { 3 | [ModuleInitializer] 4 | public static void Initialize() 5 | { 6 | VerifyDiffPlex.Initialize(OutputType.Compact); 7 | VerifierSettings.IgnoreStackTrace(); 8 | VerifierSettings.AddScrubber(_ => _.Replace('\\', '/')); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.ConventionWithNestedDir.verified.txt: -------------------------------------------------------------------------------- 1 | /mdsource/Nested/one.source.md 2 | snippet: snippet1 3 | 4 | /Nested/one.md 5 | 6 | 7 | ```cs 8 | the code from snippet1 9 | ``` 10 | anchor 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/MissingIncludesException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class MissingIncludesException(IReadOnlyList missing) : 4 | SnippetException($"Missing includes: {string.Join(", ", missing.Select(_ => _.Key))}") 5 | { 6 | public IReadOnlyList Missing { get; } = missing; 7 | 8 | public override string ToString() => Message; 9 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithSnippetAtEnd.verified.txt: -------------------------------------------------------------------------------- 1 | The include text 2 | 3 | 4 | 5 | ```cs 6 | the code from snippet1 7 | ``` 8 | anchor 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.NestedBroken.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: 6 | b 7 | c, 8 | Error: , 9 | FileLocation: path.cs(4-7), 10 | IsInError: false 11 | }, 12 | { 13 | Key: KeyParent, 14 | Error: Snippet was not closed, 15 | FileLocation: path.cs(3-3), 16 | IsInError: true 17 | } 18 | ] -------------------------------------------------------------------------------- /src/ConfigReader/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: InternalsVisibleTo("ConfigReader.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text 2 | *.png binary 3 | *.snk binary 4 | 5 | *.verified.txt text eol=lf working-tree-encoding=UTF-8 6 | *.verified.xml text eol=lf working-tree-encoding=UTF-8 7 | *.verified.json text eol=lf working-tree-encoding=UTF-8 8 | 9 | .editorconfig text eol=lf working-tree-encoding=UTF-8 10 | *.sln.DotSettings text eol=lf working-tree-encoding=UTF-8 11 | *.slnx.DotSettings text eol=lf working-tree-encoding=UTF-8 -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.NestedMixed1.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: b, 6 | Error: , 7 | FileLocation: path.cs(3-5), 8 | IsInError: false 9 | }, 10 | { 11 | Key: KeyParent, 12 | Language: cs, 13 | Value: 14 | a 15 | b 16 | c, 17 | Error: , 18 | FileLocation: path.cs(1-7), 19 | IsInError: false 20 | } 21 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.NestedMixed2.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: b, 6 | Error: , 7 | FileLocation: path.cs(3-5), 8 | IsInError: false 9 | }, 10 | { 11 | Key: KeyParent, 12 | Language: cs, 13 | Value: 14 | a 15 | b 16 | c, 17 | Error: , 18 | FileLocation: path.cs(1-7), 19 | IsInError: false 20 | } 21 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.NestedRegion.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: b, 6 | Error: , 7 | FileLocation: path.cs(4-6), 8 | IsInError: false 9 | }, 10 | { 11 | Key: KeyParent, 12 | Language: cs, 13 | Value: 14 | a 15 | b 16 | c, 17 | Error: , 18 | FileLocation: path.cs(2-8), 19 | IsInError: false 20 | } 21 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.NestedStartCode.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: b, 6 | Error: , 7 | FileLocation: path.cs(3-5), 8 | IsInError: false 9 | }, 10 | { 11 | Key: KeyParent, 12 | Language: cs, 13 | Value: 14 | a 15 | b 16 | c, 17 | Error: , 18 | FileLocation: path.cs(1-7), 19 | IsInError: false 20 | } 21 | ] -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: InternalsVisibleTo("MarkdownSnippets.Tool.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] 2 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.TableInInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | result: 3 | some text 4 | 5 | 6 | | Number of Parameters | Variations per Parameter | Total Combinations | Pairwise Combinations | 7 | | -------------------- | ----------------------- | ------------------ | --------------------- | 8 | |2|5|25|25| 9 | 10 | 11 | some other text 12 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithMergedSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | some content 2 | 3 | The include text 4 | 5 | 6 | 7 | ```.cs 8 | the code from snippet1 9 | ``` 10 | anchor 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithSingleSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | before 14 | 15 | 16 | ```cs 17 | Snippet 18 | ``` 19 | 20 | 21 | after 22 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.FileSnippetMissing.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: MissingSnippetsException, 3 | Missing: [ 4 | { 5 | Key: missing.txt, 6 | LineNumber: 1, 7 | File: {CurrentDirectory}DirectoryMarkdownProcessor/FileSnippetMissing/one.source.md 8 | } 9 | ], 10 | Message: 11 | Missing snippets: 12 | {CurrentDirectory}DirectoryMarkdownProcessor/FileSnippetMissing/one.source.md: missing.txt 13 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithTabIndentedSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | before 14 | 15 | 16 | ```cs 17 | Snippet 18 | ``` 19 | 20 | 21 | after 22 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.RemoveDuplicateNewlines.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: KeyChild, 4 | Language: cs, 5 | Value: b, 6 | Error: , 7 | FileLocation: path.cs(8-14), 8 | IsInError: false 9 | }, 10 | { 11 | Key: KeyParent, 12 | Language: cs, 13 | Value: 14 | a 15 | 16 | b 17 | 18 | c, 19 | Error: , 20 | FileLocation: path.cs(2-20), 21 | IsInError: false 22 | } 23 | ] -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: merge-dependabot 2 | on: 3 | pull_request: 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: Dependabot Auto Merge 10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6 11 | with: 12 | target: minor 13 | github-token: ${{ secrets.dependabot }} 14 | command: squash and merge -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithIndentedSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | before 14 | 15 | 16 | ```cs 17 | Snippet 18 | ``` 19 | 20 | 21 | after 22 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Paths.cs: -------------------------------------------------------------------------------- 1 | static class Paths 2 | { 3 | public static bool IsMdFile(this string value) => 4 | value.EndsWith(".md") || 5 | value.EndsWith(".mdx"); 6 | 7 | public static bool IsSourceMdFile(this string value) => 8 | value.EndsWith(".source.md") || 9 | value.EndsWith(".source.mdx"); 10 | 11 | public static bool IsIncludeMdFile(this string value) => 12 | value.EndsWith(".include.md"); 13 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithTwoLineSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: 7 | the 8 | Snippet, 9 | Error: , 10 | FileLocation: thePath(1-2), 11 | IsInError: false 12 | } 13 | ], 14 | result: 15 | before 16 | 17 | 18 | ```cs 19 | the 20 | Snippet 21 | ``` 22 | 23 | 24 | after 25 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithIndentedCommentSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | before 14 | 15 | 16 | ```cs 17 | Snippet 18 | ``` 19 | 20 | 21 | after 22 | } -------------------------------------------------------------------------------- /src/Tests/HeaderWriterTests.cs: -------------------------------------------------------------------------------- 1 | public class HeaderWriterTests 2 | { 3 | [Fact] 4 | public Task DefaultHeader() => 5 | Verify(HeaderWriter.DefaultHeader); 6 | 7 | [Fact] 8 | public Task WriteHeaderDefaultHeader() => 9 | Verify(HeaderWriter.WriteHeader("thePath", null, "\r\n")); 10 | 11 | [Fact] 12 | public Task WriteHeaderHeaderCustom() => 13 | Verify(HeaderWriter.WriteHeader("thePath", @"line1\nline2", "\r\n")); 14 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithIndentedWebSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | MissingSnippets: [ 3 | { 4 | Key: http://example.com/file.cs#snippet1, 5 | LineNumber: 4 6 | } 7 | ], 8 | result: 9 | before 10 | 11 | 12 | ``` 13 | ** Could not fetch or parse web-snippet 'http://example.com/file.cs#snippet1' ** 14 | ``` 15 | 16 | 17 | after 18 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithMultiLineSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: 7 | the 8 | long 9 | Snippet, 10 | Error: , 11 | FileLocation: thePath(1-2), 12 | IsInError: false 13 | } 14 | ], 15 | result: 16 | before 17 | 18 | 19 | ```cs 20 | the 21 | long 22 | Snippet 23 | ``` 24 | 25 | 26 | after 27 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageMinimal.DotNet9_0.verified.txt: -------------------------------------------------------------------------------- 1 | Config: 2 | TargetDirectory: theRoot 3 | UrlPrefix: 4 | LinkFormat: GitHub 5 | Convention: SourceTransform 6 | TocLevel: 0 7 | ValidateContent: False 8 | OmitSnippetLinks: False 9 | TreatMissingAsWarning: False 10 | FileConfigPath: theConfigFilePath (exists:False) 11 | ReadOnly: 12 | WriteHeader: 13 | Header: 14 | TargetFramework: net9.0 -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.UrlIncludeMissing.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: MissingIncludesException, 3 | Missing: [ 4 | { 5 | Key: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt, 6 | LineNumber: 1, 7 | File: {CurrentDirectory}DirectoryMarkdownProcessor/UrlIncludeMissing/one.source.md 8 | } 9 | ], 10 | Message: Missing includes: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt 11 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithIndentedSnippetMultipleSpaces.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | before 14 | 15 | 16 | ```cs 17 | Snippet 18 | ``` 19 | 20 | 21 | after 22 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.MixedNewlinesInFile.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: FileWithMixedNewLines.txt, 5 | Language: txt, 6 | Value: 7 | a 8 | b 9 | c 10 | d, 11 | Error: , 12 | FileLocation: null, 13 | IsInError: false 14 | } 15 | ], 16 | result: 17 | some other text 18 | 19 | 20 | ```txt 21 | a 22 | b 23 | c 24 | d 25 | ``` 26 | 27 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/KeyValidator.cs: -------------------------------------------------------------------------------- 1 | static class KeyValidator 2 | { 3 | public static bool IsValidKey(CharSpan key) 4 | { 5 | if (key.Length == 0) 6 | { 7 | return false; 8 | } 9 | 10 | if (!char.IsLetterOrDigit(key[0])) 11 | { 12 | return false; 13 | } 14 | 15 | if (!char.IsLetterOrDigit(key[^1])) 16 | { 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcenter.com/2011/12/30/the-discipline-of-punctuation-in-technical-writing/. , 4 | Item2: 20 5 | }, 6 | { 7 | Item1: Invalid word detected: 'you', 8 | Item2: 1 9 | }, 10 | { 11 | Item1: Invalid word detected: 'yourself', 12 | Item2: 27 13 | } 14 | ] -------------------------------------------------------------------------------- /src/Tests/SnippetExtensionsTests.ToDictionary.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | snippet1: [ 3 | { 4 | Key: snippet1, 5 | Language: language, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | snippet2: [ 13 | { 14 | Key: snippet2, 15 | Language: language, 16 | Value: Snippet, 17 | Error: , 18 | FileLocation: thePath(1-2), 19 | IsInError: false 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /src/Tests/DownloaderTests.Valid.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | success: true, 3 | content: 4 | * text 5 | *.png binary 6 | *.snk binary 7 | 8 | *.verified.txt text eol=lf working-tree-encoding=UTF-8 9 | *.verified.xml text eol=lf working-tree-encoding=UTF-8 10 | *.verified.json text eol=lf working-tree-encoding=UTF-8 11 | 12 | .editorconfig text eol=lf working-tree-encoding=UTF-8 13 | *.sln.DotSettings text eol=lf working-tree-encoding=UTF-8 14 | *.slnx.DotSettings text eol=lf working-tree-encoding=UTF-8 15 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithIndentedMultiLineSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: 7 | the 8 | long 9 | Snippet, 10 | Error: , 11 | FileLocation: thePath(1-2), 12 | IsInError: false 13 | } 14 | ], 15 | result: 16 | before 17 | 18 | 19 | ```cs 20 | the 21 | long 22 | Snippet 23 | ``` 24 | 25 | 26 | after 27 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.SnippetInInclude.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: snippet1, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | some text 14 | 15 | 16 | 17 | ```cs 18 | Snippet 19 | ``` 20 | 21 | 22 | 23 | some other text 24 | } -------------------------------------------------------------------------------- /docs/mdsource/api.source.md: -------------------------------------------------------------------------------- 1 | # Library Usage 2 | 3 | 4 | ## NuGet package 5 | 6 | https://nuget.org/packages/MarkdownSnippets/ [![NuGet Status](https://img.shields.io/nuget/v/MarkdownSnippets.svg)](https://www.nuget.org/packages/MarkdownSnippets/) 7 | 8 | 9 | ## Reading snippets from files 10 | 11 | snippet: ReadingFilesSimple 12 | 13 | 14 | ## Ignored paths 15 | 16 | To change conventions manipulate lists `MarkdownSnippets.Exclusions.NoAcceptCommentsExtensions` and `MarkdownSnippets.Exclusions.BinaryFileExtensions`. -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.ExplicitFileInclude.verified.txt: -------------------------------------------------------------------------------- 1 | The include text 1 2 | 3 | The include text 2 4 | 5 | The include text 3 6 | 7 | The include text 4 8 | 9 | The include text 5 10 | 11 | The include text 6 12 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.SnippetInIncludeLast.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: snippet1, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | } 11 | ], 12 | result: 13 | some text 14 | 15 | line1 16 | 17 | ```cs 18 | Snippet 19 | ``` 20 | 21 | 22 | 23 | some other text 24 | } -------------------------------------------------------------------------------- /src/context-menu.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | [HKEY_CLASSES_ROOT\Directory\Shell] 3 | @="none" 4 | [HKEY_CLASSES_ROOT\Directory\shell\mdsnippets] 5 | "MUIVerb"="run mdsnippets" 6 | "Position"="bottom" 7 | [HKEY_CLASSES_ROOT\Directory\Background\shell\mdsnippets] 8 | "MUIVerb"="run mdsnippets" 9 | "Position"="bottom" 10 | [HKEY_CLASSES_ROOT\Directory\shell\mdsnippets\command] 11 | @="cmd.exe /c mdsnippets \"%V\"" 12 | [HKEY_CLASSES_ROOT\Directory\Background\shell\mdsnippets\command] 13 | @="cmd.exe /c mdsnippets \"%V\"" -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ConfigReaderTests.BadJson.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: SnippetException, 3 | Message: 4 | Failed to deserialize configuration. Error: There was an error deserializing the object of type ConfigSerialization. Encountered unexpected character '"'.. 5 | Content: 6 | { 7 | "ValidateContent": true 8 | "Convention": "InPlaceOverwrite" 9 | }, 10 | StackTrace: 11 | at ConfigReader.DeSerialize(String contents) 12 | at ConfigReader.Parse(String contents, String path) 13 | at ConfigReaderTests.<>c.b__1_0() 14 | } -------------------------------------------------------------------------------- /src/Tests/SnippetFileFinderTests.VerifyLambdasAreCalled.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | directories: [ 3 | {ProjectDirectory}SnippetFileFinder/VerifyLambdasAreCalled/subpath 4 | ], 5 | markdownDirectories: [ 6 | {ProjectDirectory}SnippetFileFinder/VerifyLambdasAreCalled, 7 | {ProjectDirectory}SnippetFileFinder/VerifyLambdasAreCalled/subpath 8 | ], 9 | snippetDirectories: [ 10 | {ProjectDirectory}SnippetFileFinder/VerifyLambdasAreCalled, 11 | {ProjectDirectory}SnippetFileFinder/VerifyLambdasAreCalled/subpath 12 | ] 13 | } -------------------------------------------------------------------------------- /docs/mdsource/doc-index.include.md: -------------------------------------------------------------------------------- 1 | * Developer Information 2 | * [.net API](/docs/api.md) 3 | * [MsBuild Task](/docs/msbuild.md) 4 | * [Github Action](/docs/github-action.md) 5 | * Customisation 6 | * [Config file convention](/docs/config-file.md) 7 | * [Max Width](/docs/max-width.md) 8 | * [Includes](/docs/includes.md) 9 | * [Directory Exclusion](/docs/exclusion.md) 10 | * [Header](/docs/header.md) 11 | * Writing Documentation 12 | * [Indentation](/docs/indentation.md) 13 | * [Table of contents](/docs/toc.md) -------------------------------------------------------------------------------- /src/MarkdownSnippets/SnippetExtensions.cs: -------------------------------------------------------------------------------- 1 | static class SnippetExtensions 2 | { 3 | public static Dictionary> ToDictionary(this IEnumerable value) => 4 | value 5 | .GroupBy(_ => _.Key) 6 | .ToDictionary( 7 | keySelector: _ => _.Key, 8 | elementSelector: _ => _.OrderBy(ScrubPath).ToReadonlyList()); 9 | 10 | static string? ScrubPath(Snippet snippet) => 11 | snippet.Path? 12 | .Replace("/", "") 13 | .Replace("\\", ""); 14 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteExists.verified.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```cs 4 | the code from snippet1 5 | ``` 6 | anchor 7 | 8 | 9 | The include text 10 | 11 | Line 1 12 | 13 | Line 2 14 | 15 | 16 | ``` 17 | The Code 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteNotExists.verified.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```cs 4 | the code from snippet1 5 | ``` 6 | anchor 7 | 8 | 9 | The include text 10 | 11 | Line 1 12 | 13 | Line 2 14 | 15 | 16 | ``` 17 | The Code 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /src/ConfigReader/ConfigReader.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1;net48;net8.0;net9.0 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteExistsMdx.verified.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```cs 4 | the code from snippet1 5 | ``` 6 | anchor 7 | 8 | 9 | The include text 10 | 11 | Line 1 12 | 13 | Line 2 14 | 15 | 16 | ``` 17 | The Code 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.UrlSnippetMissing.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: MissingSnippetsException, 3 | Missing: [ 4 | { 5 | Key: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt, 6 | LineNumber: 1, 7 | File: {CurrentDirectory}DirectoryMarkdownProcessor/UrlSnippetMissing/one.source.md 8 | } 9 | ], 10 | Message: 11 | Missing snippets: 12 | {CurrentDirectory}DirectoryMarkdownProcessor/UrlSnippetMissing/one.source.md: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt 13 | } -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Item1: No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcenter.com/2011/12/30/the-discipline-of-punctuation-in-technical-writing/. , 4 | Item2: 20 5 | }, 6 | { 7 | Item1: Invalid word detected: 'you', 8 | Item2: 1 9 | }, 10 | { 11 | Item1: Invalid word detected: 'us', 12 | Item2: 37 13 | }, 14 | { 15 | Item1: Invalid word detected: 'yourself', 16 | Item2: 27 17 | } 18 | ] -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/SnippetKey_ExtractStartCommentSnippet.cs: -------------------------------------------------------------------------------- 1 | public class SnippetKey_ExtractStartCommentSnippet 2 | { 3 | [Fact] 4 | public void WithDashes() 5 | { 6 | Assert.True(SnippetKey.ExtractStartCommentSnippet(new("", "path", 1), out var key)); 7 | Assert.Equal("my-code-snippet", key); 8 | } 9 | 10 | [Fact] 11 | public void Simple() 12 | { 13 | Assert.True(SnippetKey.ExtractStartCommentSnippet(new("", "path", 1), out var key)); 14 | Assert.Equal("snippet", key); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | public static class ModuleInitializer 2 | { 3 | [ModuleInitializer] 4 | public static void Initialize() 5 | { 6 | VerifyDiffPlex.Initialize(OutputType.Compact); 7 | VerifierSettings.IgnoreStackTrace(); 8 | VerifierSettings.AddExtraSettings(serializer => 9 | { 10 | var converters = serializer.Converters; 11 | converters.Add(new ProcessResultConverter()); 12 | converters.Add(new SnippetConverter()); 13 | }); 14 | VerifierSettings.AddScrubber(_ => _.Replace('\\', '/')); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.Convention.verified.txt: -------------------------------------------------------------------------------- 1 | /mdsource/two.source.md 2 | snippet: snippet2 3 | 4 | /one.md 5 | 6 | 7 | ```cs 8 | the code from snippet1 9 | ``` 10 | anchor 11 | 12 | 13 | /one.source.md 14 | snippet: snippet1 15 | 16 | /two.md 17 | 18 | 19 | ```cs 20 | the code from snippet2 21 | ``` 22 | anchor 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/Downloader/FileNameFromUrl.cs: -------------------------------------------------------------------------------- 1 | static class FileNameFromUrl 2 | { 3 | static List invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToList(); 4 | 5 | public static string ConvertToFileName(string url) 6 | { 7 | var builder = new StringBuilder(url.Length); 8 | foreach (var ch in url) 9 | { 10 | if (invalid.Contains(ch)) 11 | { 12 | builder.Append('_'); 13 | continue; 14 | } 15 | 16 | builder.Append(ch); 17 | } 18 | 19 | return builder.ToString(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Tests/DownloaderTests.cs: -------------------------------------------------------------------------------- 1 | public class DownloaderTests 2 | { 3 | [Fact] 4 | public async Task Valid() 5 | { 6 | var content = await Downloader.DownloadContent("https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/.gitattributes"); 7 | await Verify(new {content.success, content.content}); 8 | } 9 | 10 | [Fact] 11 | public async Task Missing() 12 | { 13 | var content = await Downloader.DownloadContent("https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/missing.txt"); 14 | Assert.False(content.success); 15 | Assert.Null(content.content); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtensionsTests.ToDictionary_SameKey.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | snippet1: [ 3 | { 4 | Key: snippet1, 5 | Language: language, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: null, 9 | IsInError: false 10 | }, 11 | { 12 | Key: snippet1, 13 | Language: language, 14 | Value: Snippet, 15 | Error: , 16 | FileLocation: thePath1(1-2), 17 | IsInError: false 18 | }, 19 | { 20 | Key: snippet1, 21 | Language: language, 22 | Value: Snippet, 23 | Error: , 24 | FileLocation: thePath2(1-2), 25 | IsInError: false 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExists/file.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```.cs 4 | the code from snippet1 5 | ``` 6 | anchor 7 | 8 | 9 | Bad text 10 | 11 | Bad Line 1 12 | 13 | Bad Line 2 14 | 15 | 16 | ``` 17 | Bad Code 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessor/InPlaceOverwriteExistsMdx/file.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```.cs 4 | the code from snippet1 5 | ``` 6 | anchor 7 | 8 | 9 | Bad text 10 | 11 | Bad Line 1 12 | 13 | Bad Line 2 14 | 15 | 16 | ``` 17 | Bad Code 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/MarkdownProcessingException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class MarkdownProcessingException : 4 | SnippetException 5 | { 6 | public string? File { get; } 7 | public int LineNumber { get; } 8 | 9 | public MarkdownProcessingException(string message, string? file, int lineNumber) : 10 | base($"{message} File: {file}. LineNumber: {lineNumber}.") 11 | { 12 | Guard.AgainstNegativeAndZero(lineNumber, nameof(lineNumber)); 13 | Guard.AgainstEmpty(file, nameof(file)); 14 | File = file; 15 | LineNumber = lineNumber; 16 | } 17 | 18 | public override string ToString() => Message; 19 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/MissingSnippetsException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class MissingSnippetsException(IReadOnlyList missing) : 4 | SnippetException($"Missing snippets:{Environment.NewLine} {Report(missing)}") 5 | { 6 | public IReadOnlyList Missing { get; } = missing; 7 | 8 | static string Report(IReadOnlyList missing) => 9 | string.Join( 10 | $"{Environment.NewLine} ", 11 | missing.GroupBy(_ => _.File ?? "file-unknown") 12 | .Select(_ => $"{_.Key}: {string.Join(',', _.Select(_ => _.Key))}")); 13 | 14 | public override string ToString() => Message; 15 | } 16 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessage.DotNet9_0.verified.txt: -------------------------------------------------------------------------------- 1 | Config: 2 | TargetDirectory: theRoot 3 | UrlPrefix: 4 | LinkFormat: Tfs 5 | Convention: InPlaceOverwrite 6 | TocLevel: 5 7 | ValidateContent: False 8 | OmitSnippetLinks: False 9 | TreatMissingAsWarning: False 10 | FileConfigPath: theConfigFilePath (exists:False) 11 | MaxWidth: 80 12 | ExcludeDirectories: 13 | Dir1 14 | Dir2 15 | ExcludeMarkdownDirectories: 16 | Dir3 17 | Dir4 18 | ExcludeSnippetDirectories: 19 | Dir5 20 | Dir6 21 | UrlsAsSnippets: 22 | Url1 23 | Url2 24 | TargetFramework: net9.0 -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithMixedCaseSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: theKey, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | }, 11 | { 12 | Key: TheKey, 13 | Language: cs, 14 | Value: Snippet, 15 | Error: , 16 | FileLocation: thePath(1-2), 17 | IsInError: false 18 | } 19 | ], 20 | result: 21 | before 22 | 23 | 24 | ```cs 25 | Snippet 26 | ``` 27 | 28 | 29 | 30 | ```cs 31 | Snippet 32 | ``` 33 | 34 | 35 | after 36 | } -------------------------------------------------------------------------------- /src/Tests/WebSnippetTests.cs: -------------------------------------------------------------------------------- 1 | public class WebSnippetTests 2 | { 3 | [Fact] 4 | public void ExtractWebSnippet_ParsesCorrectly() 5 | { 6 | var line = new Line("web-snippet:https://example.com/file.cs#mysnippet", "", 1); 7 | Assert.True(SnippetKey.ExtractWebSnippet(line, out var url, out var key)); 8 | Assert.Equal("https://example.com/file.cs", url); 9 | Assert.Equal("mysnippet", key); 10 | } 11 | 12 | [Fact] 13 | public void ExtractWebSnippet_FailsWithoutHash() 14 | { 15 | var line = new Line("web-snippet:https://example.com/file.cs", "", 1); 16 | Assert.False(SnippetKey.ExtractWebSnippet(line, out var _, out var _)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ConfigReaderTests.Values.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ReadOnly: false, 3 | ValidateContent: true, 4 | OmitSnippetLinks: true, 5 | LinkFormat: Tfs, 6 | Convention: InPlaceOverwrite, 7 | TocLevel: 3, 8 | MaxWidth: 80, 9 | UrlsAsSnippets: [ 10 | Url1, 11 | Url2 12 | ], 13 | ExcludeDirectories: [ 14 | Dir1, 15 | Dir2 16 | ], 17 | ExcludeMarkdownDirectories: [ 18 | Dir2, 19 | Dir3 20 | ], 21 | ExcludeSnippetDirectories: [ 22 | Dir4, 23 | Dir5 24 | ], 25 | WriteHeader: true, 26 | Header: GENERATED FILE - Source File: {relativePath}, 27 | UrlPrefix: TheUrlPrefix, 28 | TocExcludes: [ 29 | Exclude Heading1, 30 | Exclude Heading2 31 | ], 32 | TreatMissingAsWarning: true 33 | } -------------------------------------------------------------------------------- /docs/mdsource/header.source.md: -------------------------------------------------------------------------------- 1 | # Header 2 | 3 | When a .md file is written, a header is include. The default header is: 4 | 5 | snippet: HeaderWriterTests.DefaultHeader.verified.txt 6 | 7 | 8 | ## Disable Header 9 | 10 | To disable the header use `--write-header` 11 | 12 | ```ps 13 | mdsnippets --write-header false 14 | ``` 15 | 16 | 17 | ## Custom Header 18 | 19 | To apply a custom header use `--header`. `{relativePath}` will be replaced with the relative path of the `.source.md` file. 20 | 21 | ```ps 22 | mdsnippets --header "GENERATED FILE - Source File: {relativePath}" 23 | ``` 24 | 25 | 26 | ## Newlines in Header 27 | 28 | To insert a newline use `\n` 29 | 30 | ```ps 31 | mdsnippets --header "GENERATED FILE\nSource File: {relativePath}" 32 | ``` -------------------------------------------------------------------------------- /src/ConfigReader.Tests/allConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", 3 | "ReadOnly": false, 4 | "LinkFormat": "Tfs", 5 | "TocLevel": 3, 6 | "ExcludeDirectories": [ "Dir1", "Dir2" ], 7 | "ExcludeMarkdownDirectories": [ "Dir2", "Dir3" ], 8 | "ExcludeSnippetDirectories": [ "Dir4", "Dir5" ], 9 | "UrlsAsSnippets": [ "Url1", "Url2" ], 10 | "TocExcludes": [ "Exclude Heading1", "Exclude Heading2" ], 11 | "Convention": "InPlaceOverwrite", 12 | "WriteHeader": true, 13 | "MaxWidth": 80, 14 | "Header": "GENERATED FILE - Source File: {relativePath}", 15 | "UrlPrefix": "TheUrlPrefix", 16 | "TreatMissingAsWarning": true, 17 | "ValidateContent": true, 18 | "OmitSnippetLinks": true 19 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/MarkdownSnippets.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1;net48;net8.0;net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/on-push-do-docs.yml: -------------------------------------------------------------------------------- 1 | name: on-push-do-docs 2 | on: 3 | push: 4 | jobs: 5 | docs: 6 | runs-on: windows-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Run MarkdownSnippets 10 | run: | 11 | dotnet tool install --global MarkdownSnippets.Tool 12 | mdsnippets ${GITHUB_WORKSPACE} 13 | shell: bash 14 | - name: Push changes 15 | run: | 16 | git config --local user.email "action@github.com" 17 | git config --local user.name "GitHub Action" 18 | git commit -m "Docs changes" -a || echo "nothing to commit" 19 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" 20 | branch="${GITHUB_REF:11}" 21 | git push "${remote}" ${branch} || echo "nothing to push" 22 | shell: bash -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet9_0.verified.txt: -------------------------------------------------------------------------------- 1 | Config: 2 | TargetDirectory: theRoot 3 | UrlPrefix: 4 | LinkFormat: Tfs 5 | Convention: SourceTransform 6 | TocLevel: 5 7 | ValidateContent: False 8 | OmitSnippetLinks: False 9 | TreatMissingAsWarning: False 10 | FileConfigPath: theConfigFilePath (exists:False) 11 | ReadOnly: True 12 | WriteHeader: True 13 | Header: 14 | line1 15 | line2 16 | MaxWidth: 80 17 | ExcludeDirectories: 18 | Dir1 19 | Dir2 20 | ExcludeMarkdownDirectories: 21 | Dir3 22 | Dir4 23 | ExcludeSnippetDirectories: 24 | Dir5 25 | Dir6 26 | UrlsAsSnippets: 27 | Url1 28 | Url2 29 | TargetFramework: net9.0 -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ConfigReaderTests.cs: -------------------------------------------------------------------------------- 1 | public class ConfigReaderTests 2 | { 3 | [Fact] 4 | public Task Empty() 5 | { 6 | var config = ConfigReader.Parse("{}", "filePath"); 7 | 8 | return Verify(config); 9 | } 10 | 11 | [Fact] 12 | public Task BadJson() => 13 | Throws( 14 | () => ConfigReader.Parse( 15 | """ 16 | { 17 | "ValidateContent": true 18 | "Convention": "InPlaceOverwrite" 19 | } 20 | """, 21 | "filePath")); 22 | 23 | [Fact] 24 | public Task Values() 25 | { 26 | var stream = File.ReadAllText("allConfig.json"); 27 | var config = ConfigReader.Parse(stream, "filePath"); 28 | return Verify(config); 29 | } 30 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/mdsource/toc.source.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | If a line is `toc` it will be replaced with a table of contents 4 | 5 | So if a markdown document contains the following: 6 | 7 | snippet: tocBefore.txt 8 | 9 | The result will be rendered: 10 | 11 | snippet: tocAfter.txt 12 | 13 | 14 | ## Heading Level 15 | 16 | Headings with level 2 (`##`) or greater can be rendered. By default all level 2 and level 3 headings are included. 17 | 18 | To include more levels use the `--toc-level` argument. So for example to include headings levels 2 though level 6 use: 19 | 20 | ```ps 21 | mdsnippets --toc-level 5 22 | ``` 23 | 24 | 25 | ## Ignore Headings 26 | 27 | To exclude headings use the `--toc-excludes` argument. So for example to exclude `heading1` and `heading2` use: 28 | 29 | ```ps 30 | mdsnippets --toc-excludes heading1:heading2 31 | ``` -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 7 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Set to true to ignore issues in a milestone (defaults to false) 6 | exemptMilestones: true 7 | # Comment to post when marking an issue as stale. Set to `false` to disable 8 | markComment: > 9 | This issue has been automatically marked as stale because it has not had 10 | recent activity. It will be closed if no further activity occurs. Thank you 11 | for your contributions. 12 | # Comment to post when closing a stale issue. Set to `false` to disable 13 | closeComment: false 14 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 15 | pulls: 16 | daysUntilStale: 30 17 | exemptLabels: 18 | - Question 19 | - Bug 20 | - Feature 21 | - Improvement -------------------------------------------------------------------------------- /src/Tests/ProcessResultConverter.cs: -------------------------------------------------------------------------------- 1 | class ProcessResultConverter : 2 | JsonConverter 3 | { 4 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 5 | { 6 | var processResult = (ProcessResult)value; 7 | writer.WriteStartObject(); 8 | writer.WritePropertyName("missing"); 9 | serializer.Serialize(writer, processResult.MissingSnippets); 10 | writer.WritePropertyName("usedSnippets"); 11 | serializer.Serialize(writer, processResult.UsedSnippets); 12 | writer.WriteEndObject(); 13 | } 14 | 15 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => 16 | throw new NotImplementedException(); 17 | 18 | public override bool CanConvert(Type objectType) => 19 | objectType == typeof(ProcessResult); 20 | } -------------------------------------------------------------------------------- /docs/mdsource/msbuild.source.md: -------------------------------------------------------------------------------- 1 | # MsBuild 2 | 3 | An [MsBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) for merging snippets into markdown documents. 4 | 5 | MsBuild has a [convention](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#from-a-convention-based-working-directory) to automatically run build tasks from included NuGet packages. This package takes advantage of that hook to run markdownsnippets on build. 6 | 7 | This package only need to be included in one project of the solution. A logical choice is the test project. 8 | 9 | 10 | ## NuGet package 11 | 12 | https://nuget.org/packages/MarkdownSnippets.MsBuild/ [![NuGet Status](https://img.shields.io/nuget/v/MarkdownSnippets.MsBuild.svg)](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/) 13 | 14 | 15 | ## More Info 16 | 17 | * [Config file convention](/docs/config-file.md). -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/LoopStack.cs: -------------------------------------------------------------------------------- 1 | [DebuggerDisplay("Depth={stack.Count}, IsInSnippet={IsInSnippet}")] 2 | class LoopStack 3 | { 4 | public bool IsInSnippet => stack.Count > 0; 5 | 6 | public LoopState Current => stack.Peek(); 7 | 8 | public void AppendLine(string line) 9 | { 10 | foreach (var state in stack) 11 | { 12 | state.AppendLine(line); 13 | } 14 | } 15 | 16 | public void Pop() => stack.Pop(); 17 | 18 | public void Push(EndFunc endFunc, CharSpan key, int startLine, int maxWidth, string newLine, CharSpan expressiveCode) 19 | { 20 | var expressiveCodeString = expressiveCode.Length == 0 ? null : expressiveCode.ToString(); 21 | 22 | var state = new LoopState(key.ToString(), endFunc, startLine, maxWidth, newLine, expressiveCodeString); 23 | stack.Push(state); 24 | } 25 | 26 | Stack stack = []; 27 | } -------------------------------------------------------------------------------- /src/ConfigReader/ConfigResult.cs: -------------------------------------------------------------------------------- 1 | public class ConfigResult 2 | { 3 | public bool? ReadOnly { get; init; } 4 | public bool ValidateContent { get; init; } 5 | public bool OmitSnippetLinks { get; init; } 6 | public LinkFormat LinkFormat { get; init; } 7 | public DocumentConvention Convention { get; init; } 8 | public int TocLevel { get; init; } 9 | public int MaxWidth { get; init; } 10 | public List? UrlsAsSnippets { get; init; } 11 | public List? ExcludeDirectories { get; init; } 12 | public List? ExcludeMarkdownDirectories { get; init; } 13 | public List? ExcludeSnippetDirectories { get; init; } 14 | public bool? WriteHeader { get; init; } 15 | public string? Header { get; init; } 16 | public string? UrlPrefix { get; init; } 17 | public List? TocExcludes { get; init; } 18 | public bool TreatMissingAsWarning { get; init; } 19 | } -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Documentation 9 | 10 | * Developer Information 11 | * [.net API](/docs/api.md) 12 | * [MsBuild Task](/docs/msbuild.md) 13 | * [Github Action](/docs/github-action.md) 14 | * Customisation 15 | * [Config file convention](/docs/config-file.md) 16 | * [Max Width](/docs/max-width.md) 17 | * [Includes](/docs/includes.md) 18 | * [Directory Exclusion](/docs/exclusion.md) 19 | * [Header](/docs/header.md) 20 | * Writing Documentation 21 | * [Indentation](/docs/indentation.md) 22 | * [Table of contents](/docs/toc.md) 23 | -------------------------------------------------------------------------------- /src/ConfigReader/ConfigSerialization.cs: -------------------------------------------------------------------------------- 1 | public class ConfigSerialization 2 | { 3 | public bool? ReadOnly { get; set; } 4 | public bool? ValidateContent { get; set; } 5 | public bool? OmitSnippetLinks { get; set; } 6 | public string? LinkFormat { get; set; } 7 | public string? Convention { get; set; } 8 | public bool? WriteHeader { get; set; } 9 | public string? Header { get; set; } 10 | public string? UrlPrefix { get; set; } 11 | public int? TocLevel { get; set; } 12 | public int? MaxWidth { get; set; } 13 | public List UrlsAsSnippets { get; set; } = []; 14 | public List ExcludeDirectories { get; set; } = []; 15 | public List ExcludeMarkdownDirectories { get; set; } = []; 16 | public List ExcludeSnippetDirectories { get; set; } = []; 17 | public List TocExcludes { get; set; } = []; 18 | public bool? TreatMissingAsWarning { get; set; } 19 | } -------------------------------------------------------------------------------- /src/Tests/IndexReaderTests.cs: -------------------------------------------------------------------------------- 1 | public class IndexReaderTests 2 | { 3 | [Theory] 4 | [InlineData("a\r", "\r")] 5 | [InlineData("a\n", "\n")] 6 | [InlineData("a\r\n", "\r\n")] 7 | [InlineData("", null)] 8 | [InlineData("a", null)] 9 | [InlineData("a\rb", "\r")] 10 | [InlineData("a\nb", "\n")] 11 | [InlineData("a\r\nb", "\r\n")] 12 | [InlineData("a\r\r", "\r")] 13 | [InlineData("a\r\r\nb", "\r")] 14 | public void NewLineDetection(string input, string? expected) 15 | { 16 | var fileName = Path.GetTempFileName(); 17 | try 18 | { 19 | File.WriteAllText(fileName, input); 20 | using var streamReader = File.OpenText(fileName); 21 | streamReader.TryFindNewline(out var newline); 22 | Assert.Equal(expected, newline); 23 | } 24 | finally 25 | { 26 | File.Delete(fileName); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/ConfigReader/ConfigInput.cs: -------------------------------------------------------------------------------- 1 | public class ConfigInput 2 | { 3 | public bool? ReadOnly { get; init; } 4 | public bool? ValidateContent { get; init; } 5 | public bool? OmitSnippetLinks { get; init; } 6 | public LinkFormat? LinkFormat { get; init; } 7 | public DocumentConvention? Convention { get; init; } 8 | public int? TocLevel { get; init; } 9 | public int? MaxWidth { get; init; } 10 | public List UrlsAsSnippets { get; init; } = []; 11 | public List ExcludeDirectories { get; init; } = []; 12 | public List ExcludeMarkdownDirectories { get; init; } = []; 13 | public List ExcludeSnippetDirectories { get; init; } = []; 14 | public bool? WriteHeader { get; init; } 15 | public string? Header { get; init; } 16 | public string? UrlPrefix { get; init; } 17 | public List TocExcludes { get; init; } = []; 18 | public bool? TreatMissingAsWarning { get; init; } 19 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.slnx.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | ..\Shared.sln.DotSettings 3 | True 4 | True 5 | 1 6 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/SimpleSnippetMarkdownHandling.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | /// 4 | /// Simple markdown handling to be passed to . 5 | /// 6 | public static class SimpleSnippetMarkdownHandling 7 | { 8 | public static void Append(string key, IEnumerable snippets, Action appendLine) 9 | { 10 | foreach (var snippet in snippets) 11 | { 12 | WriteSnippet(appendLine, snippet); 13 | } 14 | } 15 | 16 | static void WriteSnippet(Action appendLine, Snippet snippet) 17 | { 18 | if (snippet.ExpressiveCode is null) 19 | { 20 | appendLine($"```{snippet.Language}"); 21 | } 22 | else 23 | { 24 | appendLine($"```{snippet.Language} {snippet.ExpressiveCode}"); 25 | } 26 | 27 | appendLine(snippet.Value); 28 | appendLine("```"); 29 | } 30 | } -------------------------------------------------------------------------------- /src/ConfigReader/ExcludeToFilterBuilder.cs: -------------------------------------------------------------------------------- 1 | static class ExcludeToFilterBuilder 2 | { 3 | public static ShouldIncludeDirectory ExcludesToFilter(List? excludes) => 4 | path => 5 | { 6 | if (DefaultDirectoryExclusions.ShouldExcludeDirectory(path)) 7 | { 8 | return false; 9 | } 10 | 11 | if(excludes == null || 12 | excludes.Count == 0) 13 | { 14 | return true; 15 | } 16 | 17 | if (!excludes.Any(path.Contains)) 18 | { 19 | return true; 20 | } 21 | 22 | if (!excludes.Any(path.Replace('\\', '/').Contains)) 23 | { 24 | return true; 25 | } 26 | 27 | if (!excludes.Any(path.Replace('/', '\\').Contains)) 28 | { 29 | return true; 30 | } 31 | 32 | return false; 33 | }; 34 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/MarkdownSnippets.Tool.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | Exe 6 | $(NoWarn);xUnit1051 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.Mdx.verified.txt: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | 4 | 5 | ```txt 6 | From Source File 7 | ``` 8 | snippet source | anchor 9 | 10 | 11 | Relative Local 12 | 13 | 14 | 15 | ```txt 16 | From Source File 17 | ``` 18 | anchor 19 | 20 | 21 | Rooted 22 | 23 | 24 | 25 | ```txt 26 | From Source File 27 | ``` 28 | snippet source | anchor 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CS1591;NU1608;NU1109 5 | 28.0.0-beta.8 6 | preview 7 | 1.0.0 8 | Markdown, Snippets, mdsnippets, documentation, MarkdownSnippets 9 | Extracts snippets from code files and merges them into markdown documents. 10 | true 11 | true 12 | true 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.FileSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | 4 | 5 | ```txt 6 | From Source File 7 | ``` 8 | snippet source | anchor 9 | 10 | 11 | Relative Local 12 | 13 | 14 | 15 | ```txt 16 | From Source File 17 | ``` 18 | anchor 19 | 20 | 21 | Rooted 22 | 23 | 24 | 25 | ```txt 26 | From Source File 27 | ``` 28 | snippet source | anchor 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/SnippetKey_ExtractTransform.cs: -------------------------------------------------------------------------------- 1 | public class SnippetKey_ExtractTransform 2 | { 3 | [Fact] 4 | public void MissingSpaces() 5 | { 6 | Assert.True( SnippetKey.ExtractSnippet(new("snippet:snippet", "path", 1), out var key)); 7 | Assert.Equal("snippet", key); 8 | } 9 | 10 | [Fact] 11 | public void WithDashes() 12 | { 13 | Assert.True(SnippetKey.ExtractSnippet(new("snippet: my-code-snippet", "path", 1), out var key)); 14 | Assert.Equal("my-code-snippet", key); 15 | } 16 | 17 | [Fact] 18 | public void Simple() 19 | { 20 | Assert.True(SnippetKey.ExtractSnippet(new("snippet: snippet", "path", 1), out var key)); 21 | Assert.Equal("snippet", key); 22 | } 23 | 24 | [Fact] 25 | public void ExtraSpace() 26 | { 27 | Assert.True(SnippetKey.ExtractSnippet(new("snippet: snippet ", "path", 1), out var key)); 28 | Assert.Equal("snippet", key); 29 | } 30 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/Exclusions/DefaultDirectoryExclusions.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public static class DefaultDirectoryExclusions 4 | { 5 | public static bool ShouldExcludeDirectory(string path) 6 | { 7 | var suffix = Path 8 | .GetFileName(path) 9 | .ToLowerInvariant(); 10 | if (suffix is 11 | // source control 12 | ".git" or 13 | 14 | // ide temp files 15 | ".vs" or 16 | ".vscode" or 17 | ".idea" or 18 | 19 | // package cache 20 | "packages" or 21 | "node_modules" or 22 | 23 | // build output 24 | "dist" or 25 | ".angular" or 26 | "bin" or 27 | "obj") 28 | { 29 | return true; 30 | } 31 | 32 | var directory = new DirectoryInfo(path); 33 | return directory.Attributes.HasFlag(FileAttributes.Hidden); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.FileSnippetWithWhiteSpace.verified.txt: -------------------------------------------------------------------------------- 1 | Local 2 | 3 | 4 | 5 | ```txt 6 | From Source File 7 | ``` 8 | snippet source | anchor 9 | 10 | 11 | Relative Local 12 | 13 | 14 | 15 | ```txt 16 | From Source File 17 | ``` 18 | anchor 19 | 20 | 21 | Rooted 22 | 23 | 24 | 25 | ```txt 26 | From Source File 27 | ``` 28 | snippet source | anchor 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/mdsource/github-action.source.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions 2 | 3 | Markdown snippets can be run inside a [GitHub Action](https://help.github.com/en/actions) by installing and using [MarkdownSnippets.Tool](/readme.md#installation). This can be useful to ensure md docs are in sync when .source files are edited online, and without needing to re-generate the docs locally. 4 | 5 | Add the following to `.github\workflows\on-push-do-doco.yml` in the target repository. 6 | 7 | snippet: on-push-do-docs.yml 8 | 9 | This action performs the following tasks: 10 | 11 | * Use the [Checkout Action](https://github.com/marketplace/actions/checkout) to pull down the source 12 | * Install the MarkdownSnippets dotnet tool 13 | * Run MarkdownSnippets against the current directory 14 | * Push any changes back to GitHub 15 | 16 | 17 | ## More Info 18 | 19 | * [Software installed on GitHub-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners) -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool/MarkdownSnippets.Tool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0;net9.0 5 | mdsnippets 6 | mdsnippets 7 | MarkdownSnippets.Tool 8 | True 9 | .NET Core Global Tool for merging code snippets with markdown documents 10 | true 11 | LatestMajor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tests/SnippetExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | public class SnippetExtensionsTests 2 | { 3 | [Fact] 4 | public Task ToDictionary() 5 | { 6 | var snippets = new List 7 | { 8 | SnippetBuild("snippet1", "thePath"), 9 | SnippetBuild("snippet2", "thePath") 10 | }; 11 | return Verify(snippets.ToDictionary()); 12 | } 13 | 14 | [Fact] 15 | public Task ToDictionary_SameKey() 16 | { 17 | var snippets = new List 18 | { 19 | SnippetBuild("snippet1", null), 20 | SnippetBuild("snippet1", "thePath2"), 21 | SnippetBuild("snippet1", "thePath1") 22 | }; 23 | return Verify(snippets.ToDictionary()); 24 | } 25 | 26 | static Snippet SnippetBuild(string key, string? path) => 27 | Snippet.Build( 28 | language: "language", 29 | startLine: 1, 30 | endLine: 2, 31 | value: "Snippet", 32 | key: key, 33 | path: path, 34 | expressiveCode: null); 35 | } -------------------------------------------------------------------------------- /src/Tests/SimpleSnippetMarkdownHandlingTests.cs: -------------------------------------------------------------------------------- 1 | public class SimpleSnippetMarkdownHandlingTests 2 | { 3 | [Fact] 4 | public Task Append() 5 | { 6 | var builder = new StringBuilder(); 7 | var snippets = new List {Snippet.Build(1, 2, "theValue", "thekey", "thelanguage", "thePath", null)}; 8 | using (var writer = new StringWriter(builder)) 9 | { 10 | SimpleSnippetMarkdownHandling.Append("key1", snippets, writer.WriteLine); 11 | } 12 | 13 | return Verify(builder.ToString()); 14 | } 15 | 16 | [Fact] 17 | public Task ExpressiveCode() 18 | { 19 | var builder = new StringBuilder(); 20 | var snippets = new List {Snippet.Build(1, 2, """Console.WriteLine("Hello World");""", "thekey", "cs", "thePath", """title="HelloWorld.cs" {1}""")}; 21 | using (var writer = new StringWriter(builder)) 22 | { 23 | SimpleSnippetMarkdownHandling.Append("key1", snippets, writer.WriteLine); 24 | } 25 | 26 | return Verify(builder.ToString()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/Lines.cs: -------------------------------------------------------------------------------- 1 | static class Lines 2 | { 3 | public static void RemoveUntil( 4 | this List lines, 5 | int index, 6 | string match, 7 | string? path, 8 | Line startLine) 9 | { 10 | while (true) 11 | { 12 | if (index == lines.Count) 13 | { 14 | throw new MarkdownProcessingException($"Expected to find `{match}`.", path, startLine.LineNumber); 15 | } 16 | 17 | var lineCurrent = lines[index].Current; 18 | var shouldExit = lineCurrent.Contains(match); 19 | if (shouldExit) 20 | { 21 | lines.RemoveAt(index); 22 | break; 23 | } 24 | 25 | lines.RemoveAt(index); 26 | } 27 | } 28 | 29 | public static IEnumerable ReadAllLines(TextReader textReader, string? path) 30 | { 31 | var index = 1; 32 | while (textReader.ReadLine() is { } line) 33 | { 34 | yield return new(line, path, index); 35 | index++; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Simon Cropp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2022 3 | - macOS 4 | environment: 5 | DOTNET_NOLOGO: true 6 | DOTNET_CLI_TELEMETRY_OPTOUT: true 7 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 8 | build_script: 9 | - pwsh: | 10 | if ($isWindows) { 11 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" 12 | ./dotnet-install.ps1 -JSonFile src/global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet' 13 | } 14 | else { 15 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh" 16 | sudo chmod u+x dotnet-install.sh 17 | sudo ./dotnet-install.sh --jsonfile src/global.json --architecture x64 --install-dir '/usr/local/share/dotnet' 18 | sudo ./dotnet-install.sh --version 9.0.306 --architecture x64 --install-dir '/usr/local/share/dotnet' 19 | } 20 | - dotnet build src --configuration Release 21 | - dotnet test src --configuration Release --no-build --no-restore 22 | test: off 23 | on_failure: 24 | - ps: Get-ChildItem *.received.* -recurse | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 25 | artifacts: 26 | - path: nugets\*.nupkg -------------------------------------------------------------------------------- /docs/msbuild.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # MsBuild 9 | 10 | An [MsBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) for merging snippets into markdown documents. 11 | 12 | MsBuild has a [convention](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package#from-a-convention-based-working-directory) to automatically run build tasks from included NuGet packages. This package takes advantage of that hook to run markdownsnippets on build. 13 | 14 | This package only need to be included in one project of the solution. A logical choice is the test project. 15 | 16 | 17 | ## NuGet package 18 | 19 | https://nuget.org/packages/MarkdownSnippets.MsBuild/ [![NuGet Status](https://img.shields.io/nuget/v/MarkdownSnippets.MsBuild.svg)](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/) 20 | 21 | 22 | ## More Info 23 | 24 | * [Config file convention](/docs/config-file.md). 25 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Library Usage 9 | 10 | 11 | ## NuGet package 12 | 13 | https://nuget.org/packages/MarkdownSnippets/ [![NuGet Status](https://img.shields.io/nuget/v/MarkdownSnippets.svg)](https://www.nuget.org/packages/MarkdownSnippets/) 14 | 15 | 16 | ## Reading snippets from files 17 | 18 | 19 | 20 | ```cs 21 | var files = Directory.EnumerateFiles(@"C:\path", "*.cs", SearchOption.AllDirectories); 22 | 23 | var snippets = FileSnippetExtractor.Read(files); 24 | ``` 25 | snippet source | anchor 26 | 27 | 28 | 29 | ## Ignored paths 30 | 31 | To change conventions manipulate lists `MarkdownSnippets.Exclusions.NoAcceptCommentsExtensions` and `MarkdownSnippets.Exclusions.BinaryFileExtensions`. 32 | -------------------------------------------------------------------------------- /docs/mdsource/indentation.source.md: -------------------------------------------------------------------------------- 1 | # Code indentation 2 | 3 | The code snippets will do smart trimming of snippet indentation. 4 | 5 | For example given this snippet: 6 | 7 |
 8 | ••// begin-snippet MySnippetName
 9 | ••Line one of the snippet
10 | ••••Line two of the snippet
11 | ••// end-snippet
12 | 
13 | 14 | The leading two spaces (••) will be trimmed and the result will be: 15 | 16 | ``` 17 | Line one of the snippet 18 | ••Line two of the snippet 19 | ``` 20 | 21 | The same behavior will apply to leading tabs. 22 | 23 | 24 | ## Do not mix tabs and spaces 25 | 26 | If tabs and spaces are mixed there is no way for the snippets to work out what to trim. 27 | 28 | So given this snippet: 29 | 30 |
31 | ••// begin-snippet MySnippetNamea
32 | ••Line one of the snippet
33 | ➙➙Line one of the snippet
34 | ••// end-snippet
35 | 
36 | 37 | Where ➙ is a tab. 38 | 39 | The resulting markdown will be will be 40 | 41 |
42 | Line one of the snippet
43 | ➙➙Line one of the snippet
44 | 
45 | 46 | Note that none of the tabs have been trimmed. -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/StringBuilderCache.cs: -------------------------------------------------------------------------------- 1 | static class StringBuilderCache 2 | { 3 | const int MAX_BUILDER_SIZE = 360; 4 | 5 | [ThreadStatic] 6 | static StringBuilder? CachedInstance; 7 | 8 | public static StringBuilder Acquire(int capacity = 16) 9 | { 10 | if (capacity <= MAX_BUILDER_SIZE) 11 | { 12 | var builder = CachedInstance; 13 | // Avoid StringBuilder block fragmentation by getting a new StringBuilder 14 | // when the requested size is larger than the current capacity 15 | if (capacity <= builder?.Capacity) 16 | { 17 | CachedInstance = null; 18 | builder.Clear(); 19 | return builder; 20 | } 21 | } 22 | return new(capacity); 23 | } 24 | 25 | public static void Release(StringBuilder builder) 26 | { 27 | if (builder.Capacity <= MAX_BUILDER_SIZE) 28 | { 29 | CachedInstance = builder; 30 | } 31 | } 32 | 33 | public static string GetStringAndRelease(StringBuilder builder) 34 | { 35 | var result = builder.ToString(); 36 | Release(builder); 37 | return result; 38 | } 39 | } -------------------------------------------------------------------------------- /src/ConfigReader.Tests/ConfigReader.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | Exe 6 | $(NoWarn);xUnit1051 7 | testing 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/MarkdownSnippets.MsBuild/MarkdownSnippets.MsBuild.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory)..\task\net9.0\MarkdownSnippets.MsBuild.dll 4 | $(MSBuildThisFileDirectory)..\task\netstandard2.0\MarkdownSnippets.MsBuild.dll 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/ContentValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public class ContentValidationException(IReadOnlyList errors) : 4 | SnippetException(BuildMessage(errors)) 5 | { 6 | public IReadOnlyList Errors { get; } = errors; 7 | 8 | static string BuildMessage(IReadOnlyList errors) 9 | { 10 | var builder = new StringBuilder("Content validation errors:"); 11 | builder.AppendLine(); 12 | foreach (var error in errors) 13 | { 14 | if (error.File == null) 15 | { 16 | Polyfill.AppendLine( 17 | builder, 18 | $""" 19 | {error.Error} 20 | Line: {error.Line} 21 | Column: {error.Column} 22 | """); 23 | } 24 | 25 | Polyfill.AppendLine( 26 | builder, 27 | $""" 28 | {error.Error} 29 | File: {error.File} 30 | Line: {error.Line} 31 | Column: {error.Column} 32 | """); 33 | } 34 | 35 | return builder.ToString(); 36 | } 37 | 38 | public override string ToString() => Message; 39 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.UrlInclude.verified.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Simon Cropp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/Tests/Snippets/Usage.cs: -------------------------------------------------------------------------------- 1 | 2 | // ReSharper disable UnusedVariable 3 | 4 | class Usage 5 | { 6 | static void ReadingFiles() 7 | { 8 | #region ReadingFilesSimple 9 | 10 | var files = Directory.EnumerateFiles(@"C:\path", "*.cs", SearchOption.AllDirectories); 11 | 12 | var snippets = FileSnippetExtractor.Read(files); 13 | 14 | #endregion 15 | } 16 | 17 | static void DirectoryMarkdownProcessorRun() 18 | { 19 | #region DirectoryMarkdownProcessorRun 20 | 21 | var processor = new DirectoryMarkdownProcessor( 22 | "targetDirectory", 23 | directoryIncludes: _ => true, 24 | markdownDirectoryIncludes: _ => true, 25 | snippetDirectoryIncludes: _ => true); 26 | processor.Run(); 27 | 28 | #endregion 29 | } 30 | 31 | static void DirectoryMarkdownProcessorRunMaxWidth() 32 | { 33 | #region DirectoryMarkdownProcessorRunMaxWidth 34 | 35 | var processor = new DirectoryMarkdownProcessor( 36 | "targetDirectory", 37 | maxWidth: 80, 38 | directoryIncludes: _ => true, 39 | markdownDirectoryIncludes: _ => true, 40 | snippetDirectoryIncludes: _ => true); 41 | processor.Run(); 42 | 43 | #endregion 44 | } 45 | } -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlInclude.verified.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Simon Cropp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/Tests/SnippetConverter.cs: -------------------------------------------------------------------------------- 1 | class SnippetConverter : 2 | JsonConverter 3 | { 4 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 5 | { 6 | var snippet = (Snippet)value; 7 | writer.WriteStartObject(); 8 | writer.WritePropertyName("Key"); 9 | serializer.Serialize(writer, snippet.Key); 10 | if (!snippet.IsInError) 11 | { 12 | writer.WritePropertyName("Language"); 13 | serializer.Serialize(writer, snippet.Language); 14 | writer.WritePropertyName("Value"); 15 | serializer.Serialize(writer, snippet.Value); 16 | } 17 | writer.WritePropertyName("Error"); 18 | serializer.Serialize(writer, snippet.Error); 19 | writer.WritePropertyName("FileLocation"); 20 | serializer.Serialize(writer, snippet.FileLocation); 21 | writer.WritePropertyName("IsInError"); 22 | serializer.Serialize(writer, snippet.IsInError); 23 | writer.WriteEndObject(); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) => 27 | throw new NotImplementedException(); 28 | 29 | public override bool CanConvert(Type objectType) => 30 | objectType == typeof(Snippet); 31 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Reading/ReadSnippets.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | [DebuggerDisplay("Count={Snippets.Count}")] 4 | public class ReadSnippets : 5 | IEnumerable 6 | { 7 | public IReadOnlyList Snippets { get; } 8 | public IReadOnlyList Files { get; } 9 | public IReadOnlyDictionary> Lookup { get; } 10 | public IReadOnlyList SnippetsInError { get; } 11 | 12 | public ReadSnippets(IReadOnlyList snippets, IReadOnlyList files) 13 | { 14 | Snippets = snippets; 15 | Files = files; 16 | SnippetsInError = Snippets.Where(_ => _.IsInError).Distinct().ToList(); 17 | Lookup = Snippets.ToDictionary(); 18 | } 19 | 20 | /// 21 | /// Enumerates through the but will first throw an exception if there are any . 22 | /// 23 | public virtual IEnumerator GetEnumerator() 24 | { 25 | if (SnippetsInError.Any()) 26 | { 27 | throw new SnippetReadingException($"SnippetsInError: {string.Join(", ", SnippetsInError.Select(_ => _.Key))}"); 28 | } 29 | 30 | return Snippets.GetEnumerator(); 31 | } 32 | 33 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 34 | } -------------------------------------------------------------------------------- /docs/mdsource/exclusion.source.md: -------------------------------------------------------------------------------- 1 | # Exclusions 2 | 3 | 4 | ## Exclude directories from snippet and markdown discovery 5 | 6 | To exclude directories use `-e` or `--exclude-directories`. 7 | 8 | For example the following will exclude any directory containing 'foo' or 'bar' 9 | 10 | ```ps 11 | mdsnippets -e foo:bar 12 | ``` 13 | 14 | 15 | ## Exclude snippets from directories 16 | 17 | To exclude directories from snippet discovery use `--exclude-snippet-directories`. 18 | 19 | For example the following will exclude any directory containing 'foo' or 'bar' 20 | 21 | ```ps 22 | mdsnippets --exclude-snippet-directories foo:bar 23 | ``` 24 | 25 | ## Exclude markdown from directories 26 | 27 | To exclude directories from markdown discovery use `--exclude-markdown-directories`. 28 | 29 | For example the following will exclude any directory containing 'foo' or 'bar' 30 | 31 | ```ps 32 | mdsnippets --exclude-markdown-directories foo:bar 33 | ``` 34 | 35 | 36 | ## Ignored paths 37 | 38 | ### Directory exclusion rules: 39 | 40 | snippet: DefaultDirectoryExclusions.cs 41 | 42 | 43 | ### File exclusion rules 44 | 45 | All binary files as defined by https://github.com/sindresorhus/binary-extensions/: 46 | 47 | snippet: BinaryFileExtensions 48 | 49 | 50 | ### No comment files 51 | 52 | Files that cannot contain comments are excluded. 53 | 54 | snippet: NoAcceptCommentsExtensions -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/HeaderWriter.cs: -------------------------------------------------------------------------------- 1 | static class HeaderWriter 2 | { 3 | static string[] defaultHeaderLines; 4 | 5 | internal const string DefaultHeader = 6 | """ 7 | GENERATED FILE - DO NOT EDIT 8 | This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets). 9 | Source File: {relativePath} 10 | To change this file edit the source file and then run MarkdownSnippets. 11 | """; 12 | 13 | static HeaderWriter() => 14 | defaultHeaderLines = DefaultHeader.Lines(); 15 | 16 | public static string WriteHeader(string relativePath, string? header, string newline) 17 | { 18 | var lines = Header(header); 19 | var inner = string.Join(newline, lines) 20 | .Replace("{relativePath}", relativePath) 21 | .Replace(@"\n", newline); 22 | return $"{newline}"; 23 | } 24 | 25 | static string[] separator = ["\r\n", "\r", "\n", @"\n"]; 26 | 27 | static string[] Header(string? header) 28 | { 29 | if (header == null) 30 | { 31 | return defaultHeaderLines; 32 | } 33 | 34 | if (header.Contains("")) 36 | { 37 | throw new SnippetException("Header cannot contain ``."); 38 | } 39 | 40 | return header.Split(separator, StringSplitOptions.None); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Tests/SnippetExtractor/SnippetExtractorTests.AppendUrlAsSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Key: appveyor.yml, 4 | Language: yml, 5 | Value: 6 | image: 7 | - Visual Studio 2022 8 | - macOS 9 | environment: 10 | DOTNET_NOLOGO: true 11 | DOTNET_CLI_TELEMETRY_OPTOUT: true 12 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 13 | build_script: 14 | - pwsh: | 15 | if ($isWindows) { 16 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" 17 | ./dotnet-install.ps1 -JSonFile src/global.json -Architecture x64 -InstallDir 'C:/Program Files/dotnet' 18 | } 19 | else { 20 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh" 21 | sudo chmod u+x dotnet-install.sh 22 | sudo ./dotnet-install.sh --jsonfile src/global.json --architecture x64 --install-dir '/usr/local/share/dotnet' 23 | sudo ./dotnet-install.sh --version 9.0.306 --architecture x64 --install-dir '/usr/local/share/dotnet' 24 | } 25 | - dotnet build src --configuration Release 26 | - dotnet test src --configuration Release --no-build --no-restore 27 | test: off 28 | on_failure: 29 | - ps: Get-ChildItem *.received.* -recurse | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 30 | artifacts: 31 | - path: nugets/*.nupkg, 32 | Error: , 33 | FileLocation: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/src/appveyor.yml(1-26), 34 | IsInError: false 35 | } 36 | ] -------------------------------------------------------------------------------- /docs/indentation.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Code indentation 9 | 10 | The code snippets will do smart trimming of snippet indentation. 11 | 12 | For example given this snippet: 13 | 14 |
15 | ••// begin-snippet MySnippetName
16 | ••Line one of the snippet
17 | ••••Line two of the snippet
18 | ••// end-snippet
19 | 
20 | 21 | The leading two spaces (••) will be trimmed and the result will be: 22 | 23 | ``` 24 | Line one of the snippet 25 | ••Line two of the snippet 26 | ``` 27 | 28 | The same behavior will apply to leading tabs. 29 | 30 | 31 | ## Do not mix tabs and spaces 32 | 33 | If tabs and spaces are mixed there is no way for the snippets to work out what to trim. 34 | 35 | So given this snippet: 36 | 37 |
38 | ••// begin-snippet MySnippetNamea
39 | ••Line one of the snippet
40 | ➙➙Line one of the snippet
41 | ••// end-snippet
42 | 
43 | 44 | Where ➙ is a tab. 45 | 46 | The resulting markdown will be will be 47 | 48 |
49 | Line one of the snippet
50 | ➙➙Line one of the snippet
51 | 
52 | 53 | Note that none of the tabs have been trimmed. 54 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.ValidationErrors.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: ContentValidationException, 3 | Errors: [ 4 | { 5 | Error: Invalid word detected: 'you', 6 | Line: 1, 7 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 8 | }, 9 | { 10 | Error: Invalid word detected: 'we', 11 | Line: 2, 12 | Column: 1, 13 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 14 | }, 15 | { 16 | Error: No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcenter.com/2011/12/30/the-discipline-of-punctuation-in-technical-writing/. , 17 | Line: 3, 18 | Column: 2, 19 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 20 | } 21 | ], 22 | Message: 23 | Content validation errors: 24 | Invalid word detected: 'you' 25 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 26 | Line: 1 27 | Column: 0 28 | Invalid word detected: 'we' 29 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 30 | Line: 2 31 | Column: 1 32 | No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcenter.com/2011/12/30/the-discipline-of-punctuation-in-technical-writing/. 33 | File: {CurrentDirectory}DirectoryMarkdownProcessor/ValidationErrors/one.source.md 34 | Line: 3 35 | Column: 2 36 | 37 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: InternalsVisibleTo("Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] 2 | [assembly: InternalsVisibleTo("mdsnippets, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] 3 | [assembly: InternalsVisibleTo("MarkdownSnippets.MsBuild, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] 4 | [assembly: InternalsVisibleTo("ConfigReader, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")] -------------------------------------------------------------------------------- /src/MarkdownSnippets.MsBuild/MarkdownSnippets.MsBuild.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net9.0 5 | Extract code snippets from any language to be used when building documentation. 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | true 14 | task\$(TargetFramework) 15 | 16 | 17 | true 18 | task\$(TargetFramework) 19 | 20 | 21 | true 22 | build 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/mdsource/max-width.source.md: -------------------------------------------------------------------------------- 1 | # Max Width 2 | 3 | The Max Width setting is used to control the maximum characters per line of a snippet. If any snippet has a line that exceeds the maximum an error will be thrown. 4 | 5 | 6 | ## Usage 7 | 8 | 9 | ### Config File 10 | 11 | ``` 12 | { 13 | "MaxWidth": 80 14 | } 15 | ``` 16 | 17 | 18 | ### Command Line 19 | 20 | ``` 21 | --max-width 80 22 | ``` 23 | 24 | 25 | ### Code Api 26 | 27 | snippet: DirectoryMarkdownProcessorRunMaxWidth 28 | 29 | 30 | ## References 31 | 32 | https://en.wikipedia.org/wiki/Line_length#Electronic_text 33 | 34 | > Legibility research specific to digital text has shown that, like with printed text, line length can affect reading speed. If lines are too long it is difficult for the reader to quickly return to the start of the next line (saccade) whereas if lines are too short more scrolling or paging will be required. Researchers have suggested that longer lines are better for quick scanning, while shorter lines are better for accuracy. Longer lines would then be better suited for cases when the information will likely be scanned, while shorter lines would be appropriate when the information is meant to be read thoroughly. One proposal advanced that, in order for on-screen text to have the best compromise between reading speed and comprehension, about 55 cpl should be used. On the other hand, there have been studies indicating that digital text at 100 cpl can be read faster than text with lines of 25 characters, while retaining the same level of comprehension. -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Simple.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: snippet1, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | }, 11 | { 12 | Key: snippet2, 13 | Language: cs, 14 | Value: Snippet, 15 | Error: , 16 | FileLocation: thePath(1-2), 17 | IsInError: false 18 | }, 19 | { 20 | Key: FileToUseAsSnippet.txt, 21 | Language: txt, 22 | Value: 23 | The 24 | Content 25 | From 26 | File, 27 | Error: , 28 | FileLocation: {ProjectDirectory}FileToUseAsSnippet.txt(1-4), 29 | IsInError: false 30 | }, 31 | { 32 | Key: /FileToUseAsSnippet.txt, 33 | Language: txt, 34 | Value: 35 | The 36 | Content 37 | From 38 | File, 39 | Error: , 40 | FileLocation: {ProjectDirectory}FileToUseAsSnippet.txt(1-4), 41 | IsInError: false 42 | } 43 | ], 44 | result: 45 | 46 | ```cs 47 | Snippet 48 | ``` 49 | 50 | 51 | some text 52 | 53 | 54 | ```cs 55 | Snippet 56 | ``` 57 | 58 | 59 | some other text 60 | 61 | 62 | ```txt 63 | The 64 | Content 65 | From 66 | File 67 | ``` 68 | 69 | 70 | some other text 71 | 72 | 73 | ```txt 74 | The 75 | Content 76 | From 77 | File 78 | ``` 79 | 80 | } -------------------------------------------------------------------------------- /src/Tests/MarkdownProcessor/MarkdownProcessorTests.Simple_Overwrite.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | UsedSnippets: [ 3 | { 4 | Key: snippet1, 5 | Language: cs, 6 | Value: Snippet, 7 | Error: , 8 | FileLocation: thePath(1-2), 9 | IsInError: false 10 | }, 11 | { 12 | Key: snippet2, 13 | Language: cs, 14 | Value: Snippet, 15 | Error: , 16 | FileLocation: thePath(1-2), 17 | IsInError: false 18 | }, 19 | { 20 | Key: FileToUseAsSnippet.txt, 21 | Language: txt, 22 | Value: 23 | The 24 | Content 25 | From 26 | File, 27 | Error: , 28 | FileLocation: {ProjectDirectory}FileToUseAsSnippet.txt(1-4), 29 | IsInError: false 30 | }, 31 | { 32 | Key: /FileToUseAsSnippet.txt, 33 | Language: txt, 34 | Value: 35 | The 36 | Content 37 | From 38 | File, 39 | Error: , 40 | FileLocation: {ProjectDirectory}FileToUseAsSnippet.txt(1-4), 41 | IsInError: false 42 | } 43 | ], 44 | result: 45 | 46 | ```cs 47 | Snippet 48 | ``` 49 | 50 | 51 | some text 52 | 53 | 54 | ```cs 55 | Snippet 56 | ``` 57 | 58 | 59 | some other text 60 | 61 | 62 | ```txt 63 | The 64 | Content 65 | From 66 | File 67 | ``` 68 | 69 | 70 | some other text 71 | 72 | 73 | ```txt 74 | The 75 | Content 76 | From 77 | File 78 | ``` 79 | 80 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/FileEx.cs: -------------------------------------------------------------------------------- 1 | static class FileEx 2 | { 3 | public static string FixFileCapitalization(string file) 4 | { 5 | var fileName = Path.GetFileName(file); 6 | var directory = Path.GetDirectoryName(file); 7 | var filePaths = Directory.GetFiles(directory!, fileName, SearchOption.TopDirectoryOnly); 8 | return filePaths[0]; 9 | } 10 | 11 | public static string GetRelativePath(string file, string directory) 12 | { 13 | var fileUri = new Uri(file); 14 | // Folders must end in a slash 15 | if (!directory.EndsWith(Path.DirectorySeparatorChar)) 16 | { 17 | directory += Path.DirectorySeparatorChar; 18 | } 19 | 20 | var directoryUri = new Uri(directory); 21 | return Uri.UnescapeDataString(directoryUri.MakeRelativeUri(fileUri).ToString().Replace('/', Path.DirectorySeparatorChar)); 22 | } 23 | 24 | public static string PrependSlash(string path) 25 | { 26 | if (path.StartsWith('/')) 27 | { 28 | return path; 29 | } 30 | 31 | return $"/{path}"; 32 | } 33 | 34 | public static void ClearReadOnly(string path) 35 | { 36 | if (!File.Exists(path)) 37 | { 38 | return; 39 | } 40 | 41 | new FileInfo(path) 42 | { 43 | IsReadOnly = false 44 | }; 45 | } 46 | 47 | public static void MakeReadOnly(string path) => 48 | new FileInfo(path) 49 | { 50 | IsReadOnly = true 51 | }; 52 | } -------------------------------------------------------------------------------- /docs/mdsource/includes.source.md: -------------------------------------------------------------------------------- 1 | # Includes 2 | 3 | 4 | ## Including full code files 5 | 6 | When snippets are read all source files are stored in a list. When searching for a snippet with a specified key, and that key is not found, the list of files are used as a secondary lookup. The lookup is done by finding all files that have a suffix matching the key. This results in the ability to include full files as snippets using the following syntax: 7 | 8 |
 9 | snippet: directory/FileToInclude.txt
10 | 
11 | 12 | The path syntax uses forward slashes `/`. 13 | 14 | 15 | ## Including urls 16 | 17 | 18 |
19 | snippet: http://myurl
20 | 
21 | 22 | ## Including a snippet from an external URL 23 | 24 | To include a specific named snippet from a file using an external URL, use the `web-snippet:` keyword followed by the URL and the snippet key separated by a `#`: 25 | 26 |
27 | web-snippet:https://raw.githubusercontent.com/owner/repo/branch/path/to/file.cs#snippetKey
28 | 
29 | 30 | This will fetch the file from the URL, extract the snippet with the given key, and embed it in your Markdown. 31 | 32 | 33 | ## Markdown includes 34 | 35 | Markdown includes are pulled into the document before passing the content through the snippet insertion. 36 | 37 | 38 | ### Defining an include 39 | 40 | Add a file anywhere in the target directory that is suffixed with `.include.md`. For example, the file might be named `theKey.include.md`. 41 | 42 | 43 | ### Using an include 44 | 45 | Add the following to the markdown: 46 | 47 | ``` 48 | include: theKey 49 | ``` -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.UrlSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```txt 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2013 Simon Cropp 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | ``` 25 | anchor 26 | 27 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/Downloader/Timestamp.cs: -------------------------------------------------------------------------------- 1 | class Timestamp 2 | { 3 | static DateTime minFileDate = DateTime.FromFileTimeUtc(0); 4 | public DateTime? Expiry; 5 | public DateTime? LastModified; 6 | 7 | public static Timestamp GetTimestamp(HttpResponseMessage headResponse) 8 | { 9 | var timestamp = new Timestamp(); 10 | var headers = headResponse.Content.Headers; 11 | if (headers.LastModified != null) 12 | { 13 | timestamp.LastModified = headers.LastModified.Value.UtcDateTime; 14 | } 15 | 16 | if (headers.Expires != null) 17 | { 18 | timestamp.Expiry = headers.Expires.Value.UtcDateTime; 19 | } 20 | 21 | return timestamp; 22 | } 23 | 24 | public static void SetTimestamp(string path, Timestamp timestamp) 25 | { 26 | File.SetCreationTimeUtc(path, timestamp.LastModified.GetValueOrDefault(DateTime.UtcNow)); 27 | File.SetLastWriteTimeUtc(path, timestamp.Expiry.GetValueOrDefault(minFileDate)); 28 | } 29 | 30 | public static Timestamp GetTimestamp(string path) 31 | { 32 | var timestamp = new Timestamp(); 33 | var creationTimeUtc = File.GetCreationTimeUtc(path); 34 | if (creationTimeUtc != minFileDate) 35 | { 36 | timestamp.LastModified = creationTimeUtc; 37 | } 38 | 39 | var lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); 40 | if (lastWriteTimeUtc != minFileDate) 41 | { 42 | timestamp.Expiry = lastWriteTimeUtc; 43 | } 44 | 45 | return timestamp; 46 | } 47 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/Line.cs: -------------------------------------------------------------------------------- 1 | [DebuggerDisplay("Line={LineNumber}, Original={Original}, Current={Current}")] 2 | class Line 3 | { 4 | public Line(string original, string? path, int lineNumber) 5 | { 6 | Original = original; 7 | Current = original; 8 | Path = path; 9 | LineNumber = lineNumber; 10 | LeadingWhitespace = GetLeadingWhitespace(original); 11 | } 12 | 13 | public Line WithCurrent(string current) => 14 | new(Original, Path, LineNumber) 15 | { 16 | Current = current 17 | }; 18 | 19 | public readonly string Original; 20 | 21 | public override string ToString() => 22 | throw new(); 23 | 24 | public string Current 25 | { 26 | get; 27 | set 28 | { 29 | IsWhiteSpace = value.IsWhiteSpace(); 30 | Length = value.Length; 31 | field = value; 32 | } 33 | } = null!; 34 | 35 | public string? Path { get; } 36 | public int LineNumber { get; } 37 | 38 | public int Length { get; private set; } 39 | 40 | public bool IsWhiteSpace { get; private set; } 41 | 42 | public string LeadingWhitespace { get; } 43 | 44 | static string GetLeadingWhitespace(string text) 45 | { 46 | var length = 0; 47 | foreach (var c in text) 48 | { 49 | if (c is ' ' or '\t') 50 | { 51 | length++; 52 | } 53 | else 54 | { 55 | break; 56 | } 57 | } 58 | return text[..length]; 59 | } 60 | } -------------------------------------------------------------------------------- /docs/header.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Header 9 | 10 | When a .md file is written, a header is include. The default header is: 11 | 12 | 13 | 14 | ```txt 15 | GENERATED FILE - DO NOT EDIT 16 | This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets). 17 | Source File: {relativePath} 18 | To change this file edit the source file and then run MarkdownSnippets. 19 | ``` 20 | snippet source | anchor 21 | 22 | 23 | 24 | ## Disable Header 25 | 26 | To disable the header use `--write-header` 27 | 28 | ```ps 29 | mdsnippets --write-header false 30 | ``` 31 | 32 | 33 | ## Custom Header 34 | 35 | To apply a custom header use `--header`. `{relativePath}` will be replaced with the relative path of the `.source.md` file. 36 | 37 | ```ps 38 | mdsnippets --header "GENERATED FILE - Source File: {relativePath}" 39 | ``` 40 | 41 | 42 | ## Newlines in Header 43 | 44 | To insert a newline use `\n` 45 | 46 | ```ps 47 | mdsnippets --header "GENERATED FILE\nSource File: {relativePath}" 48 | ``` 49 | -------------------------------------------------------------------------------- /src/Tests/DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlSnippet.verified.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```txt 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2013 Simon Cropp 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | ``` 25 | anchor 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: How to raise feature requests 4 | --- 5 | 6 | 7 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 8 | 9 | If you are certain the feature will be accepted, it is better to raise a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/). 10 | 11 | If you are uncertain if the feature will be accepted, outline the proposal below to confirm it is viable, prior to raising a PR that implements the feature. 12 | 13 | Note that even if the feature is a good idea and viable, it may not be accepted since the ongoing effort in maintaining the feature may outweigh the benefit it delivers. 14 | 15 | 16 | #### Is the feature request related to a problem 17 | 18 | A clear and concise description of what the problem is. 19 | 20 | 21 | #### Describe the solution 22 | 23 | A clear and concise proposal of how you intend to implement the feature. 24 | 25 | 26 | #### Describe alternatives considered 27 | 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | 31 | #### Additional context 32 | 33 | Add any other context about the feature request here. 34 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/MissingInclude.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | /// 4 | /// Part of . 5 | /// 6 | [DebuggerDisplay("Key={Key}, Line={LineNumber}")] 7 | public class MissingInclude 8 | { 9 | /// 10 | /// Initialise a new instance of . 11 | /// 12 | public MissingInclude(string key, int lineNumber, string? file) 13 | { 14 | Guard.AgainstNullAndEmpty(key, nameof(key)); 15 | Guard.AgainstNegativeAndZero(lineNumber, nameof(lineNumber)); 16 | Guard.AgainstEmpty(file, nameof(file)); 17 | Key = key; 18 | LineNumber = lineNumber; 19 | File = file; 20 | } 21 | 22 | /// 23 | /// The key of the missing include. 24 | /// 25 | public string Key { get; } 26 | 27 | /// 28 | /// The line number in the input text where the include was expected to be injected. 29 | /// 30 | public int LineNumber { get; } 31 | 32 | /// 33 | /// The File of the missing include. 34 | /// 35 | public string? File { get; } 36 | 37 | public override string ToString() 38 | { 39 | if (File == null) 40 | { 41 | return $""" 42 | MissingInclude. 43 | LineNumber: {LineNumber} 44 | Key: {Key} 45 | """; 46 | } 47 | 48 | return $""" 49 | MissingInclude. 50 | File: {File} 51 | LineNumber: {LineNumber} 52 | Key: {Key} 53 | """; 54 | } 55 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/MissingSnippet.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | /// 4 | /// Part of . 5 | /// 6 | [DebuggerDisplay("Key={Key}, Line={LineNumber}")] 7 | public class MissingSnippet 8 | { 9 | /// 10 | /// Initialise a new instance of . 11 | /// 12 | public MissingSnippet(string key, int lineNumber, string? file) 13 | { 14 | Guard.AgainstNullAndEmpty(key, nameof(key)); 15 | Guard.AgainstNegativeAndZero(lineNumber, nameof(lineNumber)); 16 | Guard.AgainstEmpty(file, nameof(file)); 17 | Key = key; 18 | LineNumber = lineNumber; 19 | File = file; 20 | } 21 | 22 | /// 23 | /// The key of the missing snippet. 24 | /// 25 | public string Key { get; } 26 | 27 | /// 28 | /// The line number in the input text where the snippet was expected to be injected. 29 | /// 30 | public int LineNumber { get; } 31 | 32 | /// 33 | /// The File of the missing snippet. 34 | /// 35 | public string? File { get; } 36 | 37 | public override string ToString() 38 | { 39 | if (File == null) 40 | { 41 | return $""" 42 | MissingSnippet. 43 | LineNumber: {LineNumber} 44 | Key: {Key} 45 | """; 46 | } 47 | 48 | return $""" 49 | MissingSnippet. 50 | File: {File} 51 | LineNumber: {LineNumber} 52 | Key: {Key} 53 | """; 54 | } 55 | } -------------------------------------------------------------------------------- /src/Tests/StartEndTester_IsStartRegionTests.cs: -------------------------------------------------------------------------------- 1 | public class StartEndTester_IsStartRegionTests 2 | { 3 | [Fact] 4 | public void CanExtractFromXml() 5 | { 6 | StartEndTester.IsStartRegion("#region CodeKey", out var key); 7 | Assert.Equal("CodeKey", key); 8 | } 9 | 10 | [Fact] 11 | public void ShouldThrowForKeyStartingWithSymbol() => 12 | Assert.False(StartEndTester.IsStartRegion("#region _key", out _)); 13 | 14 | [Fact] 15 | public void WithSpaces() => 16 | Assert.False(StartEndTester.IsStartRegion("#region the text", out _)); 17 | 18 | [Fact] 19 | public void ShouldThrowForKeyEndingWithSymbol() => 20 | Assert.False(StartEndTester.IsStartRegion("#region key_ ", out _)); 21 | 22 | [Fact] 23 | public void ShouldIgnoreForNoKey() => 24 | Assert.False(StartEndTester.IsStartRegion("#region ", out _)); 25 | 26 | [Fact] 27 | public void CanExtractFromXmlWithExtraSpaces() 28 | { 29 | StartEndTester.IsStartRegion("#region CodeKey ", out var key); 30 | Assert.Equal("CodeKey", key); 31 | } 32 | 33 | [Fact] 34 | public void CanExtractWithNoTrailingCharacters() 35 | { 36 | StartEndTester.IsStartRegion("#region CodeKey", out var key); 37 | Assert.Equal("CodeKey", key); 38 | } 39 | 40 | [Fact] 41 | public void CanExtractWithUnderScores() 42 | { 43 | StartEndTester.IsStartRegion("#region Code_Key", out var key); 44 | Assert.Equal("Code_Key", key); 45 | } 46 | 47 | [Fact] 48 | public void CanExtractWithDashes() 49 | { 50 | StartEndTester.IsStartRegion("#region Code-Key", out var key); 51 | Assert.Equal("Code-Key", key); 52 | } 53 | } -------------------------------------------------------------------------------- /docs/includes.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Includes 9 | 10 | 11 | ## Including full code files 12 | 13 | When snippets are read all source files are stored in a list. When searching for a snippet with a specified key, and that key is not found, the list of files are used as a secondary lookup. The lookup is done by finding all files that have a suffix matching the key. This results in the ability to include full files as snippets using the following syntax: 14 | 15 |
16 | snippet: directory/FileToInclude.txt
17 | 
18 | 19 | The path syntax uses forward slashes `/`. 20 | 21 | 22 | ## Including urls 23 | 24 | 25 |
26 | snippet: http://myurl
27 | 
28 | 29 | ## Including a snippet from an external URL 30 | 31 | To include a specific named snippet from a file using an external URL, use the `web-snippet:` keyword followed by the URL and the snippet key separated by a `#`: 32 | 33 |
34 | web-snippet:https://raw.githubusercontent.com/owner/repo/branch/path/to/file.cs#snippetKey
35 | 
36 | 37 | This will fetch the file from the URL, extract the snippet with the given key, and embed it in your Markdown. 38 | 39 | 40 | ## Markdown includes 41 | 42 | Markdown includes are pulled into the document before passing the content through the snippet insertion. 43 | 44 | 45 | ### Defining an include 46 | 47 | Add a file anywhere in the target directory that is suffixed with `.include.md`. For example, the file might be named `theKey.include.md`. 48 | 49 | 50 | ### Using an include 51 | 52 | Add the following to the markdown: 53 | 54 | ``` 55 | include: theKey 56 | ``` 57 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/InterpretErrors.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | /// 4 | /// Extension method to convert various error cases. 5 | /// 6 | public static class InterpretErrors 7 | { 8 | /// 9 | /// Converts to a markdown string. 10 | /// 11 | public static string ErrorsAsMarkdown(this IReadOnlyList snippets) 12 | { 13 | if (!snippets.Any()) 14 | { 15 | return ""; 16 | } 17 | 18 | var builder = StringBuilderCache.Acquire(); 19 | builder.AppendLine("## Snippet errors\r\n"); 20 | foreach (var error in snippets) 21 | { 22 | Polyfill.AppendLine( 23 | builder, 24 | $" * {error}"); 25 | } 26 | 27 | builder.AppendLine(); 28 | return StringBuilderCache.GetStringAndRelease(builder); 29 | } 30 | 31 | /// 32 | /// Converts to a markdown string. 33 | /// 34 | public static string ErrorsAsMarkdown(this ProcessResult processResult) 35 | { 36 | var builder = StringBuilderCache.Acquire(); 37 | var missingSnippets = processResult.MissingSnippets.ToList(); 38 | if (missingSnippets.Count != 0) 39 | { 40 | builder.Append( 41 | """ 42 | ## Missing snippets 43 | 44 | """); 45 | foreach (var error in missingSnippets) 46 | { 47 | Polyfill.AppendLine( 48 | builder, 49 | $" * Key:'{error.Key}' Line:'{error.LineNumber}'"); 50 | } 51 | } 52 | //TODO: handle other errors 53 | 54 | builder.AppendLine(); 55 | return StringBuilderCache.GetStringAndRelease(builder); 56 | } 57 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/ValidationError.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | /// 4 | /// Part of . 5 | /// 6 | [DebuggerDisplay("Error={Error}, Line={Line}:{Column}")] 7 | public class ValidationError 8 | { 9 | /// 10 | /// Initialise a new instance of . 11 | /// 12 | public ValidationError(string error, int line, int column, string? file) 13 | { 14 | Guard.AgainstNullAndEmpty(error, nameof(error)); 15 | Guard.AgainstNegativeAndZero(line, nameof(line)); 16 | Guard.AgainstNegative(column, nameof(column)); 17 | Guard.AgainstEmpty(file, nameof(file)); 18 | Error = error; 19 | Line = line; 20 | Column = column; 21 | File = file; 22 | } 23 | 24 | /// 25 | /// The error. 26 | /// 27 | public string Error { get; } 28 | 29 | /// 30 | /// The line number in the input text. 31 | /// 32 | public int Line { get; } 33 | 34 | /// 35 | /// The column number in the line. 36 | /// 37 | public int Column { get; } 38 | 39 | /// 40 | /// The File. 41 | /// 42 | public string? File { get; } 43 | 44 | public override string ToString() 45 | { 46 | if (File == null) 47 | { 48 | return $""" 49 | ContentError. 50 | Line: {Line} 51 | Column: {Column} 52 | Error: {Error} 53 | """; 54 | } 55 | 56 | return $""" 57 | ContentError. 58 | File: {File} 59 | Line: {Line} 60 | Column: {Column} 61 | Error: {Error} 62 | """; 63 | } 64 | } -------------------------------------------------------------------------------- /docs/toc.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Table of contents 9 | 10 | If a line is `toc` it will be replaced with a table of contents 11 | 12 | So if a markdown document contains the following: 13 | 14 | 15 | 16 | ```txt 17 | # Title 18 | 19 | toc 20 | 21 | ## Heading 1 22 | 23 | Text1 24 | 25 | ## Heading 1 26 | 27 | Text2 28 | ``` 29 | snippet source | anchor 30 | 31 | 32 | The result will be rendered: 33 | 34 | 35 | 36 | ```txt 37 | # Title 38 | 39 | 40 | ## Contents 41 | 42 | * [Heading 1](#heading-1) 43 | * [Heading 2](#heading-2) 44 | 45 | 46 | ## Heading 1 47 | 48 | Text1 49 | 50 | ## Heading 2 51 | 52 | Text2 53 | ``` 54 | snippet source | anchor 55 | 56 | 57 | 58 | ## Heading Level 59 | 60 | Headings with level 2 (`##`) or greater can be rendered. By default all level 2 and level 3 headings are included. 61 | 62 | To include more levels use the `--toc-level` argument. So for example to include headings levels 2 though level 6 use: 63 | 64 | ```ps 65 | mdsnippets --toc-level 5 66 | ``` 67 | 68 | 69 | ## Ignore Headings 70 | 71 | To exclude headings use the `--toc-excludes` argument. So for example to exclude `heading1` and `heading2` use: 72 | 73 | ```ps 74 | mdsnippets --toc-excludes heading1:heading2 75 | ``` 76 | -------------------------------------------------------------------------------- /src/Tests/ContentValidationTest.cs: -------------------------------------------------------------------------------- 1 | public class ContentValidationTest 2 | { 3 | [Fact] 4 | public Task CheckInvalidWord() => Verify(ContentValidation.Verify(" you ")); 5 | 6 | [Fact] 7 | public Task CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage() => 8 | Verify(ContentValidation.Verify(" you, and you again! Still yourself? ")); 9 | 10 | [Fact] 11 | public Task CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase() => 12 | Verify(ContentValidation.Verify(" you, and you again! Still Yourself? Us")); 13 | 14 | [Fact] 15 | public Task CheckInvalidWordWithQuestionMark() => 16 | Verify(ContentValidation.Verify(" you? ")); 17 | 18 | [Fact] 19 | public Task CheckInvalidWordWithComma() => 20 | Verify(ContentValidation.Verify(" you, ")); 21 | 22 | [Fact] 23 | public Task CheckInvalidWordSentenceEnd() => 24 | Verify(ContentValidation.Verify(" you. ")); 25 | 26 | [Fact] 27 | public Task CheckInvalidWordSentenceStart() => 28 | Verify(ContentValidation.Verify("you ")); 29 | 30 | [Fact] 31 | public Task CheckInvalidWordStringEnd() => 32 | Verify(ContentValidation.Verify("the you")); 33 | 34 | [Fact] 35 | public void CheckInvalidWordDoesNotThrowWhenNoMatch() => 36 | Assert.Empty(ContentValidation.Verify(" some random content which doesn't contain invalid words. ")); 37 | 38 | [Fact] 39 | public void CheckInvalidWordDoesNotThrowWhenIsQuote() => 40 | Assert.Empty(ContentValidation.Verify("> you ")); 41 | 42 | [Fact] 43 | public void CheckInvalidWordInUrl() 44 | { 45 | Assert.Empty(ContentValidation.Verify("some random content containing links /us/allowed/")); 46 | Assert.Empty(ContentValidation.Verify("some random content containing links /yourself/us/")); 47 | Assert.Empty(ContentValidation.Verify(" /us/ ")); 48 | Assert.Empty(ContentValidation.Verify("/us-")); 49 | } 50 | } -------------------------------------------------------------------------------- /docs/mdsource/config-file.source.md: -------------------------------------------------------------------------------- 1 | # Config File 2 | 3 | The [dotnet tool](/readme.md#installation) and the [MSBuild Task](msbuild.md) support a config file convention. 4 | 5 | Add a file named `mdsnippets.json` at the target directory with the following content: 6 | 7 | 8 | ## For [InPlaceOverwrite](https://github.com/SimonCropp/MarkdownSnippets#inplaceoverwrite) 9 | 10 | snippet: InPlaceOverwrite.json 11 | 12 | 13 | ## For [SourceTransform](https://github.com/SimonCropp/MarkdownSnippets#sourcetransform) 14 | 15 | snippet: SourceTransform.json 16 | 17 | 18 | ## All Settings 19 | 20 | snippet: allConfig.json 21 | 22 | 23 | ## JSON Schema 24 | 25 | Editor help is available by adding the `$schema` field to the `mdsnippets.json` file. 26 | 27 | ```json 28 | { 29 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json" 30 | } 31 | ``` 32 | 33 | In the screenshot, [JetBrains Rider](https://jetbrains.com/rider), is able to offer code completion support. 34 | 35 | ![IDE schema code completion](/docs/code-completion.png) 36 | 37 | The schema also includes `enum` values for constrained value types. 38 | 39 | ![IDE schema code completion](/docs/code-completion-values.png) 40 | 41 | 42 | ## More Info 43 | 44 | * [ReadOnly: Mark resulting files as read only](/readme.md#mark-resulting-files-as-read-only) 45 | * [LinkFormat](/readme.md#linkformat). 46 | * [Convention](/readme.md#document-convention). 47 | * [TocLevel: Heading level](/docs/toc.md#heading-level). 48 | * [TocExcludes: Ignore headings](/docs/toc.md#ignore-headings). 49 | * [Exclude: Exclude directories from discovery](/docs/exclusion.md). 50 | * [WriteHeader: Disable Header](/docs/header.md#disable-header). 51 | * [UrlPrefix](/readme.md#urlprefix). 52 | * [UrlsAsSnippets: Urls to files to be included as snippets](/readme.md#urlsassnippets). 53 | * TreatMissingAsWarning: The default behavior for a missing snippet/include is to log an error (or throw an exception). To change that behavior to a warning set TreatMissingAsWarning to true. -------------------------------------------------------------------------------- /src/MarkdownSnippets/Guard.cs: -------------------------------------------------------------------------------- 1 | static class Guard 2 | { 3 | public static void AgainstUpperCase(string value, string argumentName) 4 | { 5 | if (value.Any(char.IsUpper)) 6 | { 7 | throw new ArgumentException($"Cannot contain upper case. Value: {value}", argumentName); 8 | } 9 | } 10 | 11 | public static void AgainstNegativeAndZero(int value, string argumentName) 12 | { 13 | if (value <= 0) 14 | { 15 | throw new ArgumentOutOfRangeException(argumentName,value, "Zero or less"); 16 | } 17 | } 18 | 19 | public static void AgainstNegative(int value, string argumentName) 20 | { 21 | if (value < 0) 22 | { 23 | throw new ArgumentOutOfRangeException(argumentName, value, "negative"); 24 | } 25 | } 26 | 27 | public static void AgainstNullAndEmpty(string? value, string argumentName) 28 | { 29 | if (string.IsNullOrWhiteSpace(value)) 30 | { 31 | throw new ArgumentNullException(argumentName); 32 | } 33 | } 34 | 35 | public static void DirectoryExists(string path, string argumentName) 36 | { 37 | AgainstNullAndEmpty(path, argumentName); 38 | if (!Directory.Exists(path)) 39 | { 40 | throw new ArgumentException($"Directory does not exist: {path}", argumentName); 41 | } 42 | } 43 | 44 | public static void FileExists(string? path, string argumentName) 45 | { 46 | AgainstNullAndEmpty(path, argumentName); 47 | if (!File.Exists(path)) 48 | { 49 | throw new ArgumentException($"File does not exist: {path}", argumentName); 50 | } 51 | } 52 | 53 | public static void AgainstEmpty(string? value, string argumentName) 54 | { 55 | if (value == null) 56 | { 57 | return; 58 | } 59 | 60 | if (string.IsNullOrWhiteSpace(value)) 61 | { 62 | throw new ArgumentException("Cannot be only whitespace.", argumentName); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Tests/LoopState/LoopStateTests.cs: -------------------------------------------------------------------------------- 1 | public class LoopStateTests 2 | { 3 | [Fact] 4 | public Task TrimIndentation() 5 | { 6 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 7 | loopState.AppendLine(" Line1"); 8 | loopState.AppendLine(" Line2"); 9 | loopState.AppendLine(" Line2"); 10 | return Verify(loopState.GetLines()); 11 | } 12 | 13 | [Fact] 14 | public Task ExcludeEmptyPaddingLines() 15 | { 16 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 17 | loopState.AppendLine(" "); 18 | loopState.AppendLine(" Line2"); 19 | loopState.AppendLine(" "); 20 | return Verify(loopState.GetLines()); 21 | } 22 | 23 | [Fact] 24 | public Task TrimIndentation_with_mis_match() 25 | { 26 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 27 | loopState.AppendLine(" Line2"); 28 | loopState.AppendLine(" "); 29 | loopState.AppendLine(" Line4"); 30 | return Verify(loopState.GetLines()); 31 | } 32 | 33 | [Fact] 34 | public void ExcludeEmptyPaddingLines_empty_list() 35 | { 36 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 37 | Assert.Empty(loopState.GetLines()); 38 | } 39 | 40 | [Fact] 41 | public void ExcludeEmptyPaddingLines_whitespace_list() 42 | { 43 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 44 | loopState.AppendLine(""); 45 | loopState.AppendLine(" "); 46 | Assert.Empty(loopState.GetLines()); 47 | } 48 | 49 | [Fact] 50 | public Task TrimIndentation_no_initial_padding() 51 | { 52 | var loopState = new LoopState("key", _ => throw new(), 1, int.MaxValue, "\n"); 53 | loopState.AppendLine("Line1"); 54 | loopState.AppendLine(" Line2"); 55 | loopState.AppendLine(" Line2"); 56 | return Verify(loopState.GetLines()); 57 | } 58 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug fix 3 | about: Create a bug fix to help us improve 4 | --- 5 | 6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 7 | 8 | 9 | #### Preamble 10 | 11 | General questions may be better placed [StackOveflow](https://stackoverflow.com/). 12 | 13 | Where relevant, ensure you are using the current stable versions on your development stack. For example: 14 | 15 | * Visual Studio 16 | * [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download) 17 | * Any related NuGet packages 18 | 19 | Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/). 20 | 21 | 22 | #### Describe the bug 23 | 24 | A clear and concise description of what the bug is. Include any relevant version information. 25 | 26 | A clear and concise description of what you expected to happen. 27 | 28 | Add any other context about the problem here. 29 | 30 | 31 | #### Minimal Repro 32 | 33 | Ensure you have replicated the bug in a minimal solution with the fewest moving parts. Often this will help point to the true cause of the problem. Upload this repro as part of the issue, preferably a public GitHub repository or a downloadable zip. The repro will allow the maintainers of this project to smoke test the any fix. 34 | 35 | #### Submit a PR that fixes the bug 36 | 37 | Submit a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) that fixes the bug. Include in this PR a test that verifies the fix. If you were not able to fix the bug, a PR that illustrates your partial progress will suffice. -------------------------------------------------------------------------------- /src/MarkdownSnippets/GitRepoDirectoryFinder.cs: -------------------------------------------------------------------------------- 1 | namespace MarkdownSnippets; 2 | 3 | public static class GitRepoDirectoryFinder 4 | { 5 | public static string FindForFilePath([CallerFilePath] string sourceFilePath = "") 6 | { 7 | Guard.FileExists(sourceFilePath, nameof(sourceFilePath)); 8 | var directory = Path.GetDirectoryName(sourceFilePath)!; 9 | return FindForDirectory(directory); 10 | } 11 | 12 | public static string FindForDirectory(string directory) 13 | { 14 | Guard.DirectoryExists(directory, nameof(directory)); 15 | if (TryFind(directory, out var path)) 16 | { 17 | return path; 18 | } 19 | 20 | throw new("Could not find git repository directory"); 21 | } 22 | 23 | static bool TryFind(string directory, [NotNullWhen(true)] out string? path) 24 | { 25 | if (TryFind(directory, ".git", out var targetDirectory)) 26 | { 27 | path = targetDirectory; 28 | return true; 29 | } 30 | 31 | if (TryFind(directory, ".gitignore", out targetDirectory)) 32 | { 33 | path = targetDirectory; 34 | return true; 35 | } 36 | 37 | path = null; 38 | return false; 39 | } 40 | 41 | public static bool IsInGitRepository(string directory) 42 | { 43 | Guard.DirectoryExists(directory, nameof(directory)); 44 | return TryFind(directory, out _); 45 | } 46 | 47 | static bool TryFind(string directory, string suffix, [NotNullWhen(true)] out string? path) 48 | { 49 | Guard.DirectoryExists(directory, nameof(directory)); 50 | 51 | do 52 | { 53 | var combine = Path.Combine(directory, suffix); 54 | if (Directory.Exists(combine) || 55 | File.Exists(combine)) 56 | { 57 | path = directory; 58 | return true; 59 | } 60 | 61 | var parent = Directory.GetParent(directory); 62 | if (parent == null) 63 | { 64 | path = null; 65 | return false; 66 | } 67 | 68 | directory = parent.FullName; 69 | } while (true); 70 | } 71 | } -------------------------------------------------------------------------------- /src/MarkdownSnippets.Tool.Tests/LogBuilderTests.cs: -------------------------------------------------------------------------------- 1 | public class LogBuilderTests 2 | { 3 | [Fact] 4 | public Task BuildConfigLogMessage() 5 | { 6 | var config = new ConfigResult 7 | { 8 | WriteHeader = true, 9 | Header = """ 10 | line1 11 | line2 12 | """, 13 | ExcludeDirectories = ["Dir1", "Dir2"], 14 | ExcludeMarkdownDirectories = ["Dir3", "Dir4"], 15 | ExcludeSnippetDirectories = ["Dir5", "Dir6"], 16 | ReadOnly = true, 17 | LinkFormat = LinkFormat.Tfs, 18 | UrlsAsSnippets = ["Url1", "Url2"], 19 | TocLevel = 5, 20 | MaxWidth = 80, 21 | Convention = DocumentConvention.InPlaceOverwrite, 22 | }; 23 | var message = LogBuilder.BuildConfigLogMessage("theRoot", config, "theConfigFilePath"); 24 | return Verify(message) 25 | .UniqueForTargetFrameworkAndVersion(); 26 | } 27 | 28 | [Fact] 29 | public Task BuildConfigLogMessageSourceTransform() 30 | { 31 | var config = new ConfigResult 32 | { 33 | WriteHeader = true, 34 | Header = """ 35 | line1 36 | line2 37 | """, 38 | ExcludeDirectories = ["Dir1", "Dir2"], 39 | ExcludeMarkdownDirectories = ["Dir3", "Dir4"], 40 | ExcludeSnippetDirectories = ["Dir5", "Dir6"], 41 | ReadOnly = true, 42 | LinkFormat = LinkFormat.Tfs, 43 | UrlsAsSnippets = ["Url1", "Url2"], 44 | TocLevel = 5, 45 | MaxWidth = 80, 46 | Convention = DocumentConvention.SourceTransform, 47 | }; 48 | var message = LogBuilder.BuildConfigLogMessage("theRoot", config, "theConfigFilePath"); 49 | return Verify(message) 50 | .UniqueForTargetFrameworkAndVersion(); 51 | } 52 | 53 | [Fact] 54 | public Task BuildConfigLogMessageMinimal() 55 | { 56 | var config = new ConfigResult(); 57 | var message = LogBuilder.BuildConfigLogMessage("theRoot", config, "theConfigFilePath"); 58 | return Verify(message) 59 | .UniqueForTargetFrameworkAndVersion(); 60 | } 61 | } -------------------------------------------------------------------------------- /docs/github-action.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # GitHub Actions 9 | 10 | Markdown snippets can be run inside a [GitHub Action](https://help.github.com/en/actions) by installing and using [MarkdownSnippets.Tool](/readme.md#installation). This can be useful to ensure md docs are in sync when .source files are edited online, and without needing to re-generate the docs locally. 11 | 12 | Add the following to `.github\workflows\on-push-do-doco.yml` in the target repository. 13 | 14 | 15 | 16 | ```yml 17 | name: on-push-do-docs 18 | on: 19 | push: 20 | jobs: 21 | docs: 22 | runs-on: windows-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Run MarkdownSnippets 26 | run: | 27 | dotnet tool install --global MarkdownSnippets.Tool 28 | mdsnippets ${GITHUB_WORKSPACE} 29 | shell: bash 30 | - name: Push changes 31 | run: | 32 | git config --local user.email "action@github.com" 33 | git config --local user.name "GitHub Action" 34 | git commit -m "Docs changes" -a || echo "nothing to commit" 35 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" 36 | branch="${GITHUB_REF:11}" 37 | git push "${remote}" ${branch} || echo "nothing to push" 38 | shell: bash 39 | ``` 40 | snippet source | anchor 41 | 42 | 43 | This action performs the following tasks: 44 | 45 | * Use the [Checkout Action](https://github.com/marketplace/actions/checkout) to pull down the source 46 | * Install the MarkdownSnippets dotnet tool 47 | * Run MarkdownSnippets against the current directory 48 | * Push any changes back to GitHub 49 | 50 | 51 | ## More Info 52 | 53 | * [Software installed on GitHub-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners) 54 | -------------------------------------------------------------------------------- /docs/max-width.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | # Max Width 9 | 10 | The Max Width setting is used to control the maximum characters per line of a snippet. If any snippet has a line that exceeds the maximum an error will be thrown. 11 | 12 | 13 | ## Usage 14 | 15 | 16 | ### Config File 17 | 18 | ``` 19 | { 20 | "MaxWidth": 80 21 | } 22 | ``` 23 | 24 | 25 | ### Command Line 26 | 27 | ``` 28 | --max-width 80 29 | ``` 30 | 31 | 32 | ### Code Api 33 | 34 | 35 | 36 | ```cs 37 | var processor = new DirectoryMarkdownProcessor( 38 | "targetDirectory", 39 | maxWidth: 80, 40 | directoryIncludes: _ => true, 41 | markdownDirectoryIncludes: _ => true, 42 | snippetDirectoryIncludes: _ => true); 43 | processor.Run(); 44 | ``` 45 | snippet source | anchor 46 | 47 | 48 | 49 | ## References 50 | 51 | https://en.wikipedia.org/wiki/Line_length#Electronic_text 52 | 53 | > Legibility research specific to digital text has shown that, like with printed text, line length can affect reading speed. If lines are too long it is difficult for the reader to quickly return to the start of the next line (saccade) whereas if lines are too short more scrolling or paging will be required. Researchers have suggested that longer lines are better for quick scanning, while shorter lines are better for accuracy. Longer lines would then be better suited for cases when the information will likely be scanned, while shorter lines would be appropriate when the information is meant to be read thoroughly. One proposal advanced that, in order for on-screen text to have the best compromise between reading speed and comprehension, about 55 cpl should be used. On the other hand, there have been studies indicating that digital text at 100 cpl can be read faster than text with lines of 25 characters, while retaining the same level of comprehension. 54 | -------------------------------------------------------------------------------- /src/MarkdownSnippets/Processing/RelativeFile.cs: -------------------------------------------------------------------------------- 1 | static class RelativeFile 2 | { 3 | static bool InnerFind(IReadOnlyList allFiles, string targetDirectory, string key, string? relativePath, string? linePath, out string path) 4 | { 5 | if (!key.Contains('.')) 6 | { 7 | path = null!; 8 | return false; 9 | } 10 | 11 | var relativeToRoot = Path.Combine(targetDirectory, key); 12 | if (File.Exists(relativeToRoot)) 13 | { 14 | path = relativeToRoot; 15 | return true; 16 | } 17 | 18 | var documentDirectory = Path.GetDirectoryName(relativePath); 19 | if (documentDirectory != null) 20 | { 21 | var relativeToDocument = Path.Combine(targetDirectory, documentDirectory.Trim('/', '\\'), key); 22 | if (File.Exists(relativeToDocument)) 23 | { 24 | path = relativeToDocument; 25 | return true; 26 | } 27 | } 28 | 29 | var lineDirectory = Path.GetDirectoryName(linePath); 30 | if (lineDirectory != null) 31 | { 32 | var relativeToLine = Path.Combine(lineDirectory, key); 33 | if (File.Exists(relativeToLine)) 34 | { 35 | path = relativeToLine; 36 | return true; 37 | } 38 | } 39 | 40 | if (File.Exists(key)) 41 | { 42 | path = Path.GetFullPath(key); 43 | return true; 44 | } 45 | 46 | var suffix = FileEx.PrependSlash(key); 47 | var endWith = allFiles 48 | .FirstOrDefault(_ => _.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); 49 | if (endWith != null) 50 | { 51 | path = endWith; 52 | return true; 53 | } 54 | 55 | path = null!; 56 | return false; 57 | } 58 | 59 | public static bool Find( 60 | IReadOnlyList allFiles, 61 | string targetDirectory, 62 | string key, 63 | string? relativePath, 64 | string? linePath, 65 | [NotNullWhen(true)] out string? path) 66 | { 67 | if (!InnerFind(allFiles, targetDirectory, key, relativePath, linePath, out path)) 68 | { 69 | return false; 70 | } 71 | 72 | path = FileEx.FixFileCapitalization(path); 73 | return true; 74 | } 75 | } --------------------------------------------------------------------------------