Showing preview only (607K chars total). Download the full file or copy to clipboard to get everything.
Repository: SimonCropp/MarkdownSnippets
Branch: main
Commit: 7151bf7e45fa
Files: 431
Total size: 495.7 KB
Directory structure:
gitextract_lnivcw9c/
├── .claude/
│ └── settings.local.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ ├── merge-dependabot.yml
│ └── milestone-release.yml
├── .gitignore
├── claude.md
├── code_of_conduct.md
├── docs/
│ ├── api.md
│ ├── config-file.md
│ ├── exclusion.md
│ ├── github-action.md
│ ├── header.md
│ ├── includes.md
│ ├── indentation.md
│ ├── max-width.md
│ ├── mdsource/
│ │ ├── api.source.md
│ │ ├── config-file.source.md
│ │ ├── doc-index.include.md
│ │ ├── exclusion.source.md
│ │ ├── github-action.source.md
│ │ ├── header.source.md
│ │ ├── includes.source.md
│ │ ├── indentation.source.md
│ │ ├── max-width.source.md
│ │ ├── msbuild.source.md
│ │ ├── readme.source.md
│ │ ├── toc/
│ │ │ ├── tocAfter.txt
│ │ │ └── tocBefore.txt
│ │ └── toc.source.md
│ ├── msbuild.md
│ ├── on-push-do-docs.yml
│ ├── readme.md
│ └── toc.md
├── license.txt
├── readme.md
├── readme.source.md
├── schema.json
└── src/
├── Benchmarks/
│ ├── Benchmarks.csproj
│ ├── FullRenderBenchmarks.cs
│ └── Program.cs
├── ConfigReader/
│ ├── AssemblyInfo.cs
│ ├── ConfigDefaults.cs
│ ├── ConfigInput.cs
│ ├── ConfigReader.cs
│ ├── ConfigReader.csproj
│ ├── ConfigResult.cs
│ ├── ConfigSerialization.cs
│ ├── ConfigurationException.cs
│ ├── ExcludeToFilterBuilder.cs
│ ├── LogBuilder.cs
│ └── SharedGlobalUsings.cs
├── ConfigReader.Tests/
│ ├── ConfigReader.Tests.csproj
│ ├── ConfigReaderTests.BadJson.verified.txt
│ ├── ConfigReaderTests.Empty.verified.txt
│ ├── ConfigReaderTests.Values.verified.txt
│ ├── ConfigReaderTests.cs
│ ├── GlobalUsings.cs
│ ├── InPlaceOverwrite.json
│ ├── ModuleInitializer.cs
│ ├── SourceTransform.json
│ └── allConfig.json
├── Directory.Build.props
├── Directory.Packages.props
├── MarkdownSnippets/
│ ├── AssemblyInfo.cs
│ ├── ContentValidation.cs
│ ├── ContentValidationException.cs
│ ├── Downloader/
│ │ ├── Downloader.cs
│ │ ├── FileNameFromUrl.cs
│ │ └── Timestamp.cs
│ ├── Extensions.cs
│ ├── FileEx.cs
│ ├── GitRepoDirectoryFinder.cs
│ ├── GlobalUsings.cs
│ ├── Guard.cs
│ ├── InterpretErrors.cs
│ ├── KeyValidator.cs
│ ├── MarkdownProcessingException.cs
│ ├── MarkdownSnippets.csproj
│ ├── MissingIncludesException.cs
│ ├── MissingSnippetsException.cs
│ ├── NewLineConfigReader.cs
│ ├── Paths.cs
│ ├── Processing/
│ │ ├── AppendSnippetsToMarkdown.cs
│ │ ├── DirectoryMarkdownProcessor.cs
│ │ ├── DocumentConvention.cs
│ │ ├── HeaderWriter.cs
│ │ ├── IncludeProcessor.cs
│ │ ├── Line.cs
│ │ ├── Lines.cs
│ │ ├── LinkFormat.cs
│ │ ├── Markdown.cs
│ │ ├── MarkdownProcessor.cs
│ │ ├── MissingInclude.cs
│ │ ├── MissingSnippet.cs
│ │ ├── ProcessResult.cs
│ │ ├── RelativeFile.cs
│ │ ├── SimpleSnippetMarkdownHandling.cs
│ │ ├── SnippetKey.cs
│ │ ├── SnippetMarkdownHandling.cs
│ │ ├── TocBuilder.cs
│ │ └── ValidationError.cs
│ ├── Reading/
│ │ ├── EndFunc.cs
│ │ ├── Exclusions/
│ │ │ ├── DefaultDirectoryExclusions.cs
│ │ │ └── SnippetFileExclusions.cs
│ │ ├── FileFinder.cs
│ │ ├── FileSnippetExtractor.cs
│ │ ├── IContent.cs
│ │ ├── Include.cs
│ │ ├── LineTooLongException.cs
│ │ ├── LoopStack.cs
│ │ ├── LoopState.cs
│ │ ├── ReadSnippets.cs
│ │ ├── ShouldIncludeDirectory.cs
│ │ ├── ShouldIncludeFile.cs
│ │ ├── Snippet.cs
│ │ └── StartEndTester.cs
│ ├── SnippetException.cs
│ ├── SnippetExtensions.cs
│ ├── SnippetReadingException.cs
│ └── StringBuilderCache.cs
├── MarkdownSnippets.MsBuild/
│ ├── DocoTask.cs
│ ├── LoggingHelper.cs
│ ├── MarkdownSnippets.MsBuild.csproj
│ └── MarkdownSnippets.MsBuild.targets
├── MarkdownSnippets.Tool/
│ ├── AssemblyInfo.cs
│ ├── CommandLineException.cs
│ ├── CommandRunner.cs
│ ├── GlobalUsings.cs
│ ├── Invoke.cs
│ ├── MarkdownSnippets.Tool.csproj
│ ├── Options.cs
│ └── Program.cs
├── MarkdownSnippets.Tool.Tests/
│ ├── CommandRunnerTests.ConventionLong.verified.txt
│ ├── CommandRunnerTests.ConventionShort.verified.txt
│ ├── CommandRunnerTests.Empty.verified.txt
│ ├── CommandRunnerTests.ExcludeLong.verified.txt
│ ├── CommandRunnerTests.ExcludeMarkdownDirectoriesLong.verified.txt
│ ├── CommandRunnerTests.ExcludeMultiple.verified.txt
│ ├── CommandRunnerTests.ExcludeShort.verified.txt
│ ├── CommandRunnerTests.ExcludeSnippetDirectoriesLong.verified.txt
│ ├── CommandRunnerTests.Header.verified.txt
│ ├── CommandRunnerTests.LinkFormatLong.verified.txt
│ ├── CommandRunnerTests.LinkFormatShort.verified.txt
│ ├── CommandRunnerTests.MaxWidthLong.verified.txt
│ ├── CommandRunnerTests.OmitSnippetLinks.verified.txt
│ ├── CommandRunnerTests.ReadOnlyLong.verified.txt
│ ├── CommandRunnerTests.ReadOnlyShort.verified.txt
│ ├── CommandRunnerTests.SingleUnNamedArg.verified.txt
│ ├── CommandRunnerTests.TargetDirectoryLong.verified.txt
│ ├── CommandRunnerTests.TargetDirectoryShort.verified.txt
│ ├── CommandRunnerTests.TocLevelLong.verified.txt
│ ├── CommandRunnerTests.UrlPrefix.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsLong.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsMultiple.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsShort.verified.txt
│ ├── CommandRunnerTests.ValidateContentLong.verified.txt
│ ├── CommandRunnerTests.ValidateContentShort.verified.txt
│ ├── CommandRunnerTests.VerifyContentLong.verified.txt
│ ├── CommandRunnerTests.VerifyContentShort.verified.txt
│ ├── CommandRunnerTests.WriteHeader.verified.txt
│ ├── CommandRunnerTests.cs
│ ├── GlobalUsings.cs
│ ├── LogBuilderTests.BuildConfigLogMessage.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessage.DotNet9_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageMinimal.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageMinimal.DotNet9_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet9_0.verified.txt
│ ├── LogBuilderTests.cs
│ ├── MarkdownSnippets.Tool.Tests.csproj
│ └── ModuleInitializer.cs
├── MarkdownSnippets.slnx
├── MarkdownSnippets.slnx.DotSettings
├── Shared.sln.DotSettings
├── Tests/
│ ├── ContentValidationTest.CheckInvalidWord.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordSentenceEnd.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordSentenceStart.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordStringEnd.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordWithComma.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordWithQuestionMark.verified.txt
│ ├── ContentValidationTest.cs
│ ├── DirectoryMarkdownProcessor/
│ │ ├── BinaryFileSnippet/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.dot
│ │ ├── Convention/
│ │ │ ├── mdsource/
│ │ │ │ └── two.source.md
│ │ │ └── one.source.md
│ │ ├── ConventionWithNestedDir/
│ │ │ └── mdsource/
│ │ │ └── Nested/
│ │ │ └── one.source.md
│ │ ├── ExplicitFileInclude/
│ │ │ ├── Nested/
│ │ │ │ ├── fileToInclude3.txt
│ │ │ │ ├── fileToInclude4.txt
│ │ │ │ ├── fileToInclude5.txt
│ │ │ │ └── fileToInclude6.txt
│ │ │ ├── fileToInclude1.txt
│ │ │ ├── fileToInclude2.txt
│ │ │ └── one.source.md
│ │ ├── ExplicitFileIncludeWithMergedSnippet/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── ExplicitFileIncludeWithSnippetAtEnd/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── FileSnippet/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.txt
│ │ ├── FileSnippetMissing/
│ │ │ └── one.source.md
│ │ ├── FileSnippetWithHash/
│ │ │ ├── one.source.md
│ │ │ └── source#File.txt
│ │ ├── FileSnippetWithWhiteSpace/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.txt
│ │ ├── InPlaceOverwriteExists/
│ │ │ ├── file.md
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteExistsMdx/
│ │ │ ├── file.mdx
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteNotExists/
│ │ │ ├── file.md
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteUrlInclude/
│ │ │ └── one.md
│ │ ├── InPlaceOverwriteUrlSnippet/
│ │ │ └── one.md
│ │ ├── InPlaceOverwriteWithFileSnippetMissing/
│ │ │ └── file.md
│ │ ├── Mdx/
│ │ │ ├── one.source.mdx
│ │ │ └── sourceFile.txt
│ │ ├── MissingInclude/
│ │ │ ├── one.md
│ │ │ └── one.source.md
│ │ ├── MixedCaseInclude/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── NonMd/
│ │ │ ├── mdsource/
│ │ │ │ └── two.source.txt
│ │ │ └── one.source.txt
│ │ ├── ReadOnly/
│ │ │ └── one.source.md
│ │ ├── UrlInclude/
│ │ │ └── one.source.md
│ │ ├── UrlIncludeMissing/
│ │ │ └── one.source.md
│ │ ├── UrlSnippet/
│ │ │ └── one.source.md
│ │ ├── UrlSnippetMissing/
│ │ │ └── one.source.md
│ │ └── ValidationErrors/
│ │ ├── one.md
│ │ └── one.source.md
│ ├── DirectoryMarkdownProcessorTests.BinaryFileSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.Convention.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ConventionWithNestedDir.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithMergedSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithSnippetAtEnd.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetExplicitIncludeBypassesExcludeSnippetFiles.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetWithHash.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetWithWhiteSpace.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExists.verified.md
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExistsMdx.verified.mdx
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteNotExists.verified.md
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteWithFileSnippetMissing.verified.md
│ ├── DirectoryMarkdownProcessorTests.Mdx.verified.txt
│ ├── DirectoryMarkdownProcessorTests.MixedCaseInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlIncludeMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlSnippetMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ValidationErrors.verified.txt
│ ├── DirectoryMarkdownProcessorTests.cs
│ ├── DirectorySnippetExtractor/
│ │ ├── Case/
│ │ │ ├── code1.txt
│ │ │ └── code2.txt
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ └── code.txt
│ │ ├── Simple/
│ │ │ ├── code1.txt
│ │ │ ├── code2.txt
│ │ │ ├── code3.txt
│ │ │ └── code4.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── code4.txt
│ ├── DownloaderTests.Valid.verified.txt
│ ├── DownloaderTests.cs
│ ├── FileExTests.cs
│ ├── FileToUseAsSnippet.txt
│ ├── GirRepoDirectoryFinderTests.cs
│ ├── GitDirs/
│ │ ├── NoRef/
│ │ │ └── HEAD
│ │ └── WithRef/
│ │ ├── HEAD
│ │ └── refs/
│ │ └── heads/
│ │ └── master
│ ├── GlobalUsings.cs
│ ├── HeaderWriterTests.DefaultHeader.verified.txt
│ ├── HeaderWriterTests.WriteHeaderDefaultHeader.verified.txt
│ ├── HeaderWriterTests.WriteHeaderHeaderCustom.verified.txt
│ ├── HeaderWriterTests.cs
│ ├── IncludeFileFinder/
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ ├── file.include.md
│ │ │ └── other.txt
│ │ ├── Simple/
│ │ │ ├── file1.include.md
│ │ │ ├── file2.include.md
│ │ │ └── other.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── file.include.md
│ ├── IncludeFinder/
│ │ └── file.include.md
│ ├── IndexReaderTests.cs
│ ├── LoopState/
│ │ ├── LoopStateTests.ExcludeEmptyPaddingLines.verified.txt
│ │ ├── LoopStateTests.TrimIndentation.verified.txt
│ │ ├── LoopStateTests.TrimIndentation_no_initial_padding.verified.txt
│ │ ├── LoopStateTests.TrimIndentation_with_mis_match.verified.txt
│ │ └── LoopStateTests.cs
│ ├── MarkdownProcessor/
│ │ ├── MarkdownProcessorTests.Empty_snippet_key.verified.txt
│ │ ├── MarkdownProcessorTests.MissingInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Missing_endInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Missing_endToc.verified.txt
│ │ ├── MarkdownProcessorTests.MixedNewlinesInFile.verified.txt
│ │ ├── MarkdownProcessorTests.Simple.verified.txt
│ │ ├── MarkdownProcessorTests.Simple_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.SkipHeadingBeforeToc.verified.txt
│ │ ├── MarkdownProcessorTests.SnippetInInclude.verified.txt
│ │ ├── MarkdownProcessorTests.SnippetInIncludeLast.verified.txt
│ │ ├── MarkdownProcessorTests.TableInInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Toc.verified.txt
│ │ ├── MarkdownProcessorTests.Toc1.verified.txt
│ │ ├── MarkdownProcessorTests.TocRetainedIfNoHeadingsInFile.verified.txt
│ │ ├── MarkdownProcessorTests.Toc_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.Whitespace_snippet_key.verified.txt
│ │ ├── MarkdownProcessorTests.WithCommentWebSnippetUpdate.verified.txt
│ │ ├── MarkdownProcessorTests.WithCommentWebSnippetWithViewUrl.verified.txt
│ │ ├── MarkdownProcessorTests.WithDoubleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithEmptyMultiLineInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithEmptyMultipleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedCommentSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedMultiLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedSnippetMultipleSpaces.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedWebSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithInlineWebSnippetWithViewUrl.verified.txt
│ │ ├── MarkdownProcessorTests.WithMixedCaseInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithMixedCaseSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultiLineInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultiLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultipleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithTabIndentedSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithTwoLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WrongNewlineInSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.cs
│ │ ├── SnippetKey_ExtractStartCommentSnippet.cs
│ │ ├── SnippetKey_ExtractStartCommentWebSnippet.cs
│ │ ├── SnippetKey_ExtractTransform.cs
│ │ ├── TocBuilderTests.Deep.verified.txt
│ │ ├── TocBuilderTests.DuplicateNested.verified.txt
│ │ ├── TocBuilderTests.Duplicates.verified.txt
│ │ ├── TocBuilderTests.EmptyHeading.verified.txt
│ │ ├── TocBuilderTests.Exclude.verified.txt
│ │ ├── TocBuilderTests.IgnoreTop.verified.txt
│ │ ├── TocBuilderTests.Nested.verified.txt
│ │ ├── TocBuilderTests.SanitizeLink.verified.txt
│ │ ├── TocBuilderTests.Single.verified.txt
│ │ ├── TocBuilderTests.StopAtLevel.verified.txt
│ │ ├── TocBuilderTests.StripMarkdown.verified.txt
│ │ ├── TocBuilderTests.WithSpaces.verified.txt
│ │ └── TocBuilderTests.cs
│ ├── ModuleInitializer.cs
│ ├── MsBuildIntegrationTests.cs
│ ├── NewLineConfigReaderTests.cs
│ ├── PathsTests.cs
│ ├── ProcessResultConverter.cs
│ ├── SimpleSnippetMarkdownHandlingTests.Append.verified.txt
│ ├── SimpleSnippetMarkdownHandlingTests.ExpressiveCode.verified.txt
│ ├── SimpleSnippetMarkdownHandlingTests.cs
│ ├── SnippetConverter.cs
│ ├── SnippetExtensionsTests.ToDictionary.verified.txt
│ ├── SnippetExtensionsTests.ToDictionary_SameKey.verified.txt
│ ├── SnippetExtensionsTests.cs
│ ├── SnippetExtractor/
│ │ ├── SnippetExtractorTests.AppendFileAsSnippet.verified.txt
│ │ ├── SnippetExtractorTests.AppendUrlAsSnippet.verified.txt
│ │ ├── SnippetExtractorTests.AppendUrlAsSnippetInline.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractFromRegion.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractFromXml.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithExpressiveCode.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithInnerWhiteSpace.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithMissingSpaces.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithNoTrailingCharacters.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithTrailingWhitespace.verified.txt
│ │ ├── SnippetExtractorTests.CanReadFileWhileLockedByAnotherProcess.verified.txt
│ │ ├── SnippetExtractorTests.LanguageOverride.verified.txt
│ │ ├── SnippetExtractorTests.LanguageOverrideWithExpressiveCode.verified.txt
│ │ ├── SnippetExtractorTests.MixedNewLines.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.TooWide.verified.txt
│ │ ├── SnippetExtractorTests.UnClosedRegion.verified.txt
│ │ ├── SnippetExtractorTests.UnClosedSnippet.verified.txt
│ │ └── SnippetExtractorTests.cs
│ ├── SnippetFileFinder/
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ └── code.txt
│ │ ├── Simple/
│ │ │ ├── code1.txt
│ │ │ ├── code2.txt
│ │ │ ├── code3.txt
│ │ │ └── code4.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── code4.txt
│ ├── SnippetFileFinderTests.ExcludeSnippetFiles.verified.txt
│ ├── SnippetFileFinderTests.Nested.verified.txt
│ ├── SnippetFileFinderTests.Simple.verified.txt
│ ├── SnippetFileFinderTests.VerifyLambdasAreCalled.verified.txt
│ ├── SnippetFileFinderTests.cs
│ ├── SnippetMarkdownHandlingTests.Append.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendHashed.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendOmitSnippetLinks.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendOmitSourceLink.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendPrefixed.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendWebSnippet.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendWebSnippetWithViewUrl.verified.txt
│ ├── SnippetMarkdownHandlingTests.cs
│ ├── SnippetVerifier.cs
│ ├── Snippets/
│ │ └── Usage.cs
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForEmptyLanguageValue.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForInvalidLanguageValue.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyEndingWithSymbol.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyStartingWithSymbol.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForNoKey.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.cs
│ ├── StartEndTester_IsStartRegionTests.cs
│ ├── Tests.csproj
│ ├── WebSnippetTests.cs
│ └── badsnippets/
│ └── code.txt
├── appveyor.yml
├── context-menu.reg
├── global.json
├── key.snk
├── mdsnippets.json
├── nuget-readme.md
└── nuget.config
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/settings.local.json
================================================
{
"permissions": {
"allow": [
"Bash(dotnet build:*)",
"Bash(dotnet test:*)"
]
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
end_of_line = lf
insert_final_newline = true
[*.cs]
indent_size = 4
charset = utf-8
# Redundant accessor body
resharper_redundant_accessor_body_highlighting = error
# Replace with field keyword
resharper_replace_with_field_keyword_highlighting = error
# Replace with single call to Single(..)
resharper_replace_with_single_call_to_single_highlighting = error
# Replace with single call to SingleOrDefault(..)
resharper_replace_with_single_call_to_single_or_default_highlighting = error
# Replace with single call to LastOrDefault(..)
resharper_replace_with_single_call_to_last_or_default_highlighting = error
# Element is localizable
resharper_localizable_element_highlighting = none
# Replace with single call to Last(..)
resharper_replace_with_single_call_to_last_highlighting = error
# Replace with single call to First(..)
resharper_replace_with_single_call_to_first_highlighting = error
# Replace with single call to FirstOrDefault(..)
resharper_replace_with_single_call_to_first_or_default_highlighting = error
# Replace with single call to Any(..)
resharper_replace_with_single_call_to_any_highlighting = error
# Simplify negative equality expression
resharper_negative_equality_expression_highlighting = error
# Replace with single call to Count(..)
resharper_replace_with_single_call_to_count_highlighting = error
# Declare types in namespaces
dotnet_diagnostic.CA1050.severity = none
# Use Literals Where Appropriate
dotnet_diagnostic.CA1802.severity = error
# Template should be a static expression
dotnet_diagnostic.CA2254.severity = error
# Potentially misleading parameter name in lambda or local function
resharper_all_underscore_local_parameter_name_highlighting = none
# Redundant explicit collection creation in argument of 'params' parameter
resharper_redundant_explicit_params_array_creation_highlighting = error
# Do not initialize unnecessarily
dotnet_diagnostic.CA1805.severity = error
# Avoid unsealed attributes
dotnet_diagnostic.CA1813.severity = error
# Test for empty strings using string length
dotnet_diagnostic.CA1820.severity = none
# Remove empty finalizers
dotnet_diagnostic.CA1821.severity = error
# Mark members as static
dotnet_diagnostic.CA1822.severity = error
# Avoid unused private fields
dotnet_diagnostic.CA1823.severity = error
# Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = error
# Use property instead of Linq Enumerable method
dotnet_diagnostic.CA1826.severity = error
# Do not use Count()/LongCount() when Any() can be used
dotnet_diagnostic.CA1827.severity = error
dotnet_diagnostic.CA1828.severity = error
# Use Length/Count property instead of Enumerable.Count method
dotnet_diagnostic.CA1829.severity = error
# Prefer strongly-typed Append and Insert method overloads on StringBuilder
dotnet_diagnostic.CA1830.severity = error
# Use AsSpan instead of Range-based indexers for string when appropriate
dotnet_diagnostic.CA1831.severity = error
# Use AsSpan instead of Range-based indexers for string when appropriate
dotnet_diagnostic.CA1831.severity = error
dotnet_diagnostic.CA1832.severity = error
dotnet_diagnostic.CA1833.severity = error
# Use StringBuilder.Append(char) for single character strings
dotnet_diagnostic.CA1834.severity = error
# Prefer IsEmpty over Count when available
dotnet_diagnostic.CA1836.severity = error
# Prefer IsEmpty over Count when available
dotnet_diagnostic.CA1836.severity = error
# Use Environment.ProcessId instead of Process.GetCurrentProcess().Id
dotnet_diagnostic.CA1837.severity = error
# Use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName
dotnet_diagnostic.CA1839.severity = error
# Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId
dotnet_diagnostic.CA1840.severity = error
# Prefer Dictionary Contains methods
dotnet_diagnostic.CA1841.severity = error
# Do not use WhenAll with a single task
dotnet_diagnostic.CA1842.severity = error
# Do not use WhenAll/WaitAll with a single task
dotnet_diagnostic.CA1842.severity = error
dotnet_diagnostic.CA1843.severity = error
# Use span-based 'string.Concat'
dotnet_diagnostic.CA1845.severity = error
# Prefer AsSpan over Substring
dotnet_diagnostic.CA1846.severity = error
# Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = error
# Prefer static HashData method over ComputeHash
dotnet_diagnostic.CA1850.severity = error
# Possible multiple enumerations of IEnumerable collection
dotnet_diagnostic.CA1851.severity = error
# Unnecessary call to Dictionary.ContainsKey(key)
dotnet_diagnostic.CA1853.severity = error
# Prefer the IDictionary.TryGetValue(TKey, out TValue) method
dotnet_diagnostic.CA1854.severity = error
# Use Span<T>.Clear() instead of Span<T>.Fill()
dotnet_diagnostic.CA1855.severity = error
# Incorrect usage of ConstantExpected attribute
dotnet_diagnostic.CA1856.severity = error
# The parameter expects a constant for optimal performance
dotnet_diagnostic.CA1857.severity = error
# Use StartsWith instead of IndexOf
dotnet_diagnostic.CA1858.severity = error
# Avoid using Enumerable.Any() extension method
dotnet_diagnostic.CA1860.severity = error
# Avoid constant arrays as arguments
dotnet_diagnostic.CA1861.severity = error
# Use the StringComparison method overloads to perform case-insensitive string comparisons
dotnet_diagnostic.CA1862.severity = error
# Prefer the IDictionary.TryAdd(TKey, TValue) method
dotnet_diagnostic.CA1864.severity = error
# Use string.Method(char) instead of string.Method(string) for string with single char
dotnet_diagnostic.CA1865.severity = error
dotnet_diagnostic.CA1866.severity = error
dotnet_diagnostic.CA1867.severity = error
# Unnecessary call to 'Contains' for sets
dotnet_diagnostic.CA1868.severity = error
# Cache and reuse 'JsonSerializerOptions' instances
dotnet_diagnostic.CA1869.severity = error
# Use a cached 'SearchValues' instance
dotnet_diagnostic.CA1870.severity = error
# Microsoft .NET properties
trim_trailing_whitespace = true
csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion
resharper_namespace_body = file_scoped
dotnet_naming_rule.private_constants_rule.severity = warning
dotnet_naming_rule.private_constants_rule.style = lower_camel_case_style
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
dotnet_naming_rule.private_instance_fields_rule.severity = warning
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
dotnet_naming_rule.private_static_fields_rule.severity = warning
dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
dotnet_naming_rule.private_static_readonly_rule.severity = warning
dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
# ReSharper properties
resharper_object_creation_when_type_not_evident = target_typed
# ReSharper inspection severities
resharper_arrange_object_creation_when_type_evident_highlighting = error
resharper_arrange_object_creation_when_type_not_evident_highlighting = error
resharper_arrange_redundant_parentheses_highlighting = error
resharper_arrange_static_member_qualifier_highlighting = error
resharper_arrange_this_qualifier_highlighting = error
resharper_arrange_type_member_modifiers_highlighting = none
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
resharper_built_in_type_reference_style_highlighting = hint
resharper_check_namespace_highlighting = none
resharper_convert_to_using_declaration_highlighting = error
resharper_field_can_be_made_read_only_local_highlighting = none
resharper_merge_into_logical_pattern_highlighting = warning
resharper_merge_into_pattern_highlighting = error
resharper_method_has_async_overload_highlighting = warning
# because stop rider giving errors before source generators have run
resharper_partial_type_with_single_part_highlighting = warning
resharper_redundant_base_qualifier_highlighting = warning
resharper_redundant_cast_highlighting = error
resharper_redundant_empty_object_creation_argument_list_highlighting = error
resharper_redundant_empty_object_or_collection_initializer_highlighting = error
resharper_redundant_name_qualifier_highlighting = error
resharper_redundant_suppress_nullable_warning_expression_highlighting = error
resharper_redundant_using_directive_highlighting = error
resharper_redundant_verbatim_string_prefix_highlighting = error
resharper_redundant_lambda_signature_parentheses_highlighting = error
resharper_replace_substring_with_range_indexer_highlighting = warning
resharper_suggest_var_or_type_built_in_types_highlighting = error
resharper_suggest_var_or_type_elsewhere_highlighting = error
resharper_suggest_var_or_type_simple_types_highlighting = error
resharper_unnecessary_whitespace_highlighting = error
resharper_use_await_using_highlighting = warning
resharper_use_deconstruction_highlighting = warning
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_qualification_for_method = false:error
dotnet_style_qualification_for_event = false:error
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:error
dotnet_style_predefined_type_for_member_access = true:error
# Suggest more modern language features when available
dotnet_style_object_initializer = true:error
dotnet_style_collection_initializer = true:error
dotnet_style_coalesce_expression = false:error
dotnet_style_null_propagation = true:error
dotnet_style_explicit_tuple_names = true:error
# Use collection expression syntax
resharper_use_collection_expression_highlighting = error
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:error
csharp_style_var_when_type_is_apparent = true:error
csharp_style_var_elsewhere = true:error
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = true:error
csharp_style_expression_bodied_local_functions = true:error
csharp_style_expression_bodied_constructors = true:error
csharp_style_expression_bodied_operators = true:error
resharper_place_expr_method_on_single_line = false
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:error
csharp_style_expression_bodied_indexers = true:error
csharp_style_expression_bodied_accessors = true:error
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:error
csharp_style_pattern_matching_over_as_with_null_check = true:error
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
#csharp_new_line_before_open_brace = all:error
resharper_max_array_initializer_elements_on_line = 1
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
resharper_wrap_before_first_type_parameter_constraint = true
resharper_wrap_extends_list_style = chop_always
resharper_wrap_after_dot_in_method_calls = false
resharper_wrap_before_binary_pattern_op = false
resharper_wrap_object_and_collection_initializer_style = chop_always
resharper_place_simple_initializer_on_single_line = false
# space
resharper_space_around_lambda_arrow = true
dotnet_style_require_accessibility_modifiers = never:error
resharper_place_type_constraints_on_same_line = false
resharper_blank_lines_inside_namespace = 0
resharper_blank_lines_after_file_scoped_namespace_directive = 1
resharper_blank_lines_inside_type = 0
resharper_place_attribute_on_same_line = false
#braces https://www.jetbrains.com/help/resharper/EditorConfig_CSHARP_CSharpCodeStylePageImplSchema.html#Braces
resharper_braces_for_ifelse = required
resharper_braces_for_foreach = required
resharper_braces_for_while = required
resharper_braces_for_dowhile = required
resharper_braces_for_lock = required
resharper_braces_for_fixed = required
resharper_braces_for_for = required
resharper_return_value_of_pure_method_is_not_used_highlighting = error
resharper_member_hides_interface_member_with_default_implementation_highlighting = error
resharper_misleading_body_like_statement_highlighting = error
resharper_redundant_record_class_keyword_highlighting = error
resharper_redundant_extends_list_entry_highlighting = error
resharper_redundant_type_arguments_inside_nameof_highlighting = error
# Xml files
[*.{xml,config,nuspec,resx,vsixmanifest,csproj,targets,props,fsproj}]
indent_size = 2
# https://www.jetbrains.com/help/resharper/EditorConfig_XML_XmlCodeStylePageSchema.html#resharper_xml_blank_line_after_pi
resharper_blank_line_after_pi = false
resharper_space_before_self_closing = true
ij_xml_space_inside_empty_tag = true
[*.json]
indent_size = 2
# Verify settings
[*.{received,verified}.{txt,xml,json,md,sql,csv,html,htm,nuspec,rels}]
charset = utf-8-bom
end_of_line = lf
indent_size = unset
indent_style = unset
insert_final_newline = false
tab_width = unset
trim_trailing_whitespace = false
================================================
FILE: .gitattributes
================================================
# Auto detect text files and normalize line endings to LF
* text=auto eol=lf
*.png binary
*.snk binary
*.verified.txt text eol=lf working-tree-encoding=UTF-8
*.verified.xml text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8
.editorconfig text eol=lf working-tree-encoding=UTF-8
*.sln.DotSettings text eol=lf working-tree-encoding=UTF-8
*.slnx.DotSettings text eol=lf working-tree-encoding=UTF-8
================================================
FILE: .github/FUNDING.yml
================================================
github: SimonCropp
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug fix
about: Create a bug fix to help us improve
---
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.
#### Preamble
Where relevant, ensure you are using the current stable versions on your development stack. For example:
* Visual Studio
* [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download)
* Any related NuGet packages
Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/).
#### Describe the bug
A clear and concise description of what the bug is. Include any relevant version information.
A clear and concise description of what you expected to happen.
Add any other context about the problem here.
#### Minimal Repro
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.
#### Submit a PR that fixes the bug
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.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: How to raise feature requests
---
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.
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/).
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.
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.
#### Is the feature request related to a problem
A clear and concise description of what the problem is.
#### Describe the solution
A clear and concise proposal of how you intend to implement the feature.
#### Describe alternatives considered
A clear and concise description of any alternative solutions or features you've considered.
#### Additional context
Add any other context about the feature request here.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: nuget
directory: "/src"
schedule:
interval: daily
open-pull-requests-limit: 10
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 7
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
pulls:
daysUntilStale: 30
exemptLabels:
- Question
- Bug
- Feature
- Improvement
================================================
FILE: .github/workflows/merge-dependabot.yml
================================================
name: merge-dependabot
on:
pull_request:
jobs:
automerge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Dependabot Auto Merge
uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6
with:
target: minor
github-token: ${{ secrets.dependabot }}
command: squash and merge
================================================
FILE: .github/workflows/milestone-release.yml
================================================
name: milestone-release
on:
push:
tags:
- '*'
milestone:
types: [created, edited, closed, opened]
issues:
types: [opened, edited, closed, reopened, deleted, milestoned, demilestoned]
pull_request:
types: [opened, edited, closed, reopened, milestoned, demilestoned]
workflow_dispatch:
inputs:
milestone:
description: 'Milestone title to rebuild (leave empty to rebuild all)'
required: false
type: string
permissions:
contents: write
jobs:
sync-release:
runs-on: ubuntu-latest
steps:
- name: Sync Release with Milestone
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
// Helper: Find release by tag name
async function findRelease(tagName) {
for await (const response of github.paginate.iterator(
github.rest.repos.listReleases,
{ owner, repo, per_page: 100 }
)) {
const release = response.data.find(r => r.tag_name === tagName);
if (release) return release;
}
return null;
}
// Helper: Find milestone by title
async function findMilestone(title) {
for await (const response of github.paginate.iterator(
github.rest.issues.listMilestones,
{ owner, repo, state: 'all', per_page: 100 }
)) {
const milestone = response.data.find(m => m.title === title);
if (milestone) return milestone;
}
return null;
}
// Helper: Generate release body from milestone
async function generateBody(milestoneNumber) {
const items = [];
for await (const response of github.paginate.iterator(
github.rest.issues.listForRepo,
{ owner, repo, milestone: milestoneNumber, state: 'all', per_page: 100 }
)) {
items.push(...response.data);
}
items.sort((a, b) => a.number - b.number);
return items.map(item => {
const checkbox = item.state === 'closed' ? '[x]' : '[ ]';
return `- ${checkbox} [#${item.number}](${item.html_url}) ${item.title}`;
}).join('\n') || 'No issues in this milestone yet.';
}
// Helper: Update existing release only
async function updateReleaseIfExists(milestone) {
const release = await findRelease(milestone.title);
if (!release) {
console.log(`No release found for ${milestone.title}, skipping`);
return;
}
const body = await generateBody(milestone.number);
await github.rest.repos.updateRelease({
owner, repo,
release_id: release.id,
body: body
});
console.log(`Updated release: ${milestone.title}`);
}
// Handle tag push events
if (context.eventName === 'push' && context.ref.startsWith('refs/tags/')) {
const tagName = context.ref.replace('refs/tags/', '');
// Tag deleted
if (context.payload.deleted) {
const release = await findRelease(tagName);
if (release) {
await github.rest.repos.deleteRelease({
owner, repo, release_id: release.id
});
console.log(`Deleted release for tag: ${tagName}`);
}
return;
}
// Tag created - create release
const milestone = await findMilestone(tagName);
const body = milestone
? await generateBody(milestone.number)
: '';
await github.rest.repos.createRelease({
owner, repo,
tag_name: tagName,
name: tagName,
body: body,
draft: false
});
console.log(`Created release for tag: ${tagName}`);
return;
}
// Handle workflow_dispatch - update only
if (context.eventName === 'workflow_dispatch') {
const inputMilestone = context.payload.inputs?.milestone;
const milestones = [];
for await (const response of github.paginate.iterator(
github.rest.issues.listMilestones,
{ owner, repo, state: 'all', per_page: 100 }
)) {
milestones.push(...response.data);
}
for (const ms of milestones) {
if (!inputMilestone || ms.title === inputMilestone) {
await updateReleaseIfExists(ms);
}
}
return;
}
// Handle milestone/issue/PR events - update only
let milestone = context.payload.milestone;
if (!milestone && context.payload.issue?.milestone) {
milestone = context.payload.issue.milestone;
}
if (!milestone && context.payload.pull_request?.milestone) {
milestone = context.payload.pull_request.milestone;
}
if (!milestone) {
console.log('No milestone associated with this event');
return;
}
await updateReleaseIfExists(milestone);
================================================
FILE: .gitignore
================================================
*.suo
*.user
bin/
obj/
.vs/
*.DotSettings.user
.idea/
*.received.*
nugets/
nul
/src/Benchmarks/BenchmarkDotNet.Artifacts
================================================
FILE: claude.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
MarkdownSnippets is a .NET tool/library that extracts code snippets from source files (via `begin-snippet`/`end-snippet` markers and C# regions) and merges them into markdown documents. Distributed as:
- **dotnet global tool** (`MarkdownSnippets.Tool`, command: `mdsnippets`)
- **MSBuild task** (`MarkdownSnippets.MsBuild`)
- **Library** (`MarkdownSnippets`)
## Build Commands
All commands must be run from the repo root directory.
```bash
# Build everything
dotnet build src
# Run all tests
dotnet test src
# Run a single test project
dotnet test src/Tests/Tests.csproj
dotnet test src/ConfigReader.Tests/ConfigReader.Tests.csproj
dotnet test src/MarkdownSnippets.Tool.Tests/MarkdownSnippets.Tool.Tests.csproj
# Run a specific test by name
dotnet test src/Tests/Tests.csproj --filter "FullyQualifiedName~TestClassName.TestMethodName"
# Pack the tool
dotnet pack src/MarkdownSnippets.Tool/MarkdownSnippets.Tool.csproj
```
Requires .NET SDK 10.0 (preview). See `src/global.json` for exact version.
## Architecture
All source is under `src/`. There is no `.sln` file; build by targeting `src/` or individual `.csproj` files.
### Core Library (`src/MarkdownSnippets/`)
Two main subsystems:
- **Reading** (`Reading/`): Extracts snippets from source files. `FileSnippetExtractor` scans files for `begin-snippet`/`end-snippet` markers. `StartEndTester` handles marker detection including C# regions. `Snippet` is the data model.
- **Processing** (`Processing/`): Transforms markdown files. `DirectoryMarkdownProcessor` is the top-level orchestrator that scans directories, collects snippets, and processes markdown. `MarkdownProcessor` handles individual file transformation. `IncludeProcessor` handles include directives.
### Tool (`src/MarkdownSnippets.Tool/`)
CLI entry point. `Program.cs` uses top-level statements. `CommandRunner` parses args via `CommandLineParser` and delegates to `DirectoryMarkdownProcessor`. Shares `ConfigReader` source files via `<Compile Include>` (not a project reference).
### ConfigReader (`src/ConfigReader/`)
Reads `mdsnippets.json` configuration files. Source files are compiled directly into the Tool project (not referenced as a library).
### MsBuild Task (`src/MarkdownSnippets.MsBuild/`)
Wraps the library as an MSBuild task. Uses `PackageShader.MsBuild` for dependency isolation. Targets netstandard2.0 and net10.0.
## Testing
- Framework: **xunit.v3** with **Verify** (snapshot testing)
- Snapshots are `.verified.txt` files alongside tests. When a test fails due to output changes, review the `.received.txt` diff and accept with the Verify tooling if correct.
- Main test project (`src/Tests/`) targets net10.0 (and net48 on Windows)
- Test data directories (e.g., `DirectoryMarkdownProcessor/`, `SnippetExtractor/`) are copied to output via csproj settings
- The markdown files in this repo are regenerated by running tests, not as part of the build
## Build Configuration
- `src/Directory.Build.props`: Version (28.0.1), LangVersion preview, TreatWarningsAsErrors, EnforceCodeStyleInBuild
- `src/Directory.Packages.props`: Central package management with transitive pinning
- Global type alias: `CharSpan` = `System.ReadOnlySpan<System.Char>` (defined in Directory.Build.props)
- Multi-targeting: Library targets netstandard2.0/2.1, net48, net8.0, net9.0, net10.0
## Document Conventions
The tool supports two modes for processing markdown:
- **SourceTransform** (default): Reads `*.source.md`, writes output to `*.md`
- **InPlaceOverwrite**: Modifies `*.md` files directly
================================================
FILE: code_of_conduct.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at simon.cropp@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: docs/api.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/api.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Library Usage
## NuGet package
https://nuget.org/packages/MarkdownSnippets/ [](https://www.nuget.org/packages/MarkdownSnippets/)
## Reading snippets from files
<!-- snippet: ReadingFilesSimple -->
<a id='snippet-ReadingFilesSimple'></a>
```cs
var files = Directory.EnumerateFiles(@"C:\path", "*.cs", SearchOption.AllDirectories);
var snippets = FileSnippetExtractor.Read(files);
```
<sup><a href='/src/Tests/Snippets/Usage.cs#L8-L14' title='Snippet source file'>snippet source</a> | <a href='#snippet-ReadingFilesSimple' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## Ignored paths
To change conventions manipulate lists `MarkdownSnippets.Exclusions.NoAcceptCommentsExtensions` and `MarkdownSnippets.Exclusions.BinaryFileExtensions`.
================================================
FILE: docs/config-file.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/config-file.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Config File
The [dotnet tool](/readme.md#installation) and the [MSBuild Task](msbuild.md) support a config file convention.
Add a file named `mdsnippets.json` at the target directory with the following content:
## For [InPlaceOverwrite](https://github.com/SimonCropp/MarkdownSnippets#inplaceoverwrite)
<!-- snippet: InPlaceOverwrite.json -->
<a id='snippet-InPlaceOverwrite.json'></a>
```json
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"Convention": "InPlaceOverwrite"
}
```
<sup><a href='/src/ConfigReader.Tests/InPlaceOverwrite.json#L1-L4' title='Snippet source file'>snippet source</a> | <a href='#snippet-InPlaceOverwrite.json' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## For [SourceTransform](https://github.com/SimonCropp/MarkdownSnippets#sourcetransform)
<!-- snippet: SourceTransform.json -->
<a id='snippet-SourceTransform.json'></a>
```json
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"Convention": "SourceTransform"
}
```
<sup><a href='/src/ConfigReader.Tests/SourceTransform.json#L1-L4' title='Snippet source file'>snippet source</a> | <a href='#snippet-SourceTransform.json' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## All Settings
<!-- snippet: allConfig.json -->
<a id='snippet-allConfig.json'></a>
```json
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"ReadOnly": false,
"LinkFormat": "Tfs",
"TocLevel": 3,
"ExcludeDirectories": [ "Dir1", "Dir2" ],
"ExcludeMarkdownDirectories": [ "Dir2", "Dir3" ],
"ExcludeSnippetDirectories": [ "Dir4", "Dir5" ],
"ExcludeSnippetFiles": [ "*.verified.txt", "*.received.txt" ],
"UrlsAsSnippets": [ "Url1", "Url2" ],
"TocExcludes": [ "Exclude Heading1", "Exclude Heading2" ],
"Convention": "InPlaceOverwrite",
"WriteHeader": true,
"MaxWidth": 80,
"Header": "GENERATED FILE - Source File: {relativePath}",
"UrlPrefix": "TheUrlPrefix",
"TreatMissingAsWarning": true,
"ValidateContent": true,
"OmitSnippetLinks": true
}
```
<sup><a href='/src/ConfigReader.Tests/allConfig.json#L1-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-allConfig.json' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## JSON Schema
Editor help is available by adding the `$schema` field to the `mdsnippets.json` file.
```json
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json"
}
```
In the screenshot, [JetBrains Rider](https://jetbrains.com/rider), is able to offer code completion support.

The schema also includes `enum` values for constrained value types.

## More Info
* [ReadOnly: Mark resulting files as read only](/readme.md#mark-resulting-files-as-read-only)
* [LinkFormat](/readme.md#linkformat).
* [Convention](/readme.md#document-convention).
* [TocLevel: Heading level](/docs/toc.md#heading-level).
* [TocExcludes: Ignore headings](/docs/toc.md#ignore-headings).
* [Exclude: Exclude directories from discovery](/docs/exclusion.md).
* [WriteHeader: Disable Header](/docs/header.md#disable-header).
* [UrlPrefix](/readme.md#urlprefix).
* [UrlsAsSnippets: Urls to files to be included as snippets](/readme.md#urlsassnippets).
* 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.
================================================
FILE: docs/exclusion.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/exclusion.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Exclusions
## Exclude directories from snippet and markdown discovery
To exclude directories use `-e` or `--exclude-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets -e foo:bar
```
## Exclude snippets from directories
To exclude directories from snippet discovery use `--exclude-snippet-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets --exclude-snippet-directories foo:bar
```
## Exclude markdown from directories
To exclude directories from markdown discovery use `--exclude-markdown-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets --exclude-markdown-directories foo:bar
```
## Exclude files from snippet discovery
To exclude specific files from snippet discovery, add an `ExcludeSnippetFiles` array to [`mdsnippets.json`](/docs/config-file.md). Each entry is a glob pattern matched (case-insensitively) against the file *name* — not the full path. Patterns support `*` (any sequence of characters) and `?` (a single character).
For example, the following excludes all Verify snapshot files so that `<!-- begin-snippet -->` markers hand-added to a `*.verified.txt` are not picked up as snippet sources:
```json
{
"ExcludeSnippetFiles": [
"*.verified.txt",
"*.received.txt"
]
}
```
Matched files are still visible to the include/all-files enumeration — only snippet scanning skips them.
## Ignored paths
### Directory exclusion rules:
<!-- snippet: DefaultDirectoryExclusions.cs -->
<a id='snippet-DefaultDirectoryExclusions.cs'></a>
```cs
namespace MarkdownSnippets;
public static class DefaultDirectoryExclusions
{
public static bool ShouldExcludeDirectory(string path)
{
var suffix = Path
.GetFileName(path)
.ToLowerInvariant();
if (suffix is
// source control
".git" or
// ide temp files
".vs" or
".vscode" or
".idea" or
// package cache
"packages" or
"node_modules" or
// build output
"dist" or
".angular" or
"bin" or
"obj")
{
return true;
}
var directory = new DirectoryInfo(path);
return directory.Attributes.HasFlag(FileAttributes.Hidden);
}
}
```
<sup><a href='/src/MarkdownSnippets/Reading/Exclusions/DefaultDirectoryExclusions.cs#L1-L35' title='Snippet source file'>snippet source</a> | <a href='#snippet-DefaultDirectoryExclusions.cs' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
### File exclusion rules
All binary files as defined by https://github.com/sindresorhus/binary-extensions/:
<!-- snippet: BinaryFileExtensions -->
<a id='snippet-BinaryFileExtensions'></a>
```cs
"user",
// extra binary
"mdb",
"binlog",
"shp",
"dbf",
"shx",
"pbf",
"map",
"sbn",
//from https://github.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json
"3dm",
"3ds",
"3g2",
"3gp",
"7z",
"a",
"aac",
"adp",
"ai",
"aif",
"aiff",
"alz",
"ape",
"apk",
"appimage",
"ar",
"arj",
"asf",
"au",
"avi",
"bak",
"baml",
"bh",
"bin",
"bk",
"bmp",
"btif",
"bz2",
"bzip2",
"cab",
"caf",
"cgm",
"class",
"cmx",
"cpio",
"cr2",
"cur",
"dat",
"dcm",
"deb",
"dex",
"djvu",
"dll",
"dmg",
"dng",
"doc",
"docm",
"docx",
"dot",
"dotm",
"dra",
"DS_Store",
"dsk",
"dts",
"dtshd",
"dvb",
"dwg",
"dxf",
"ecelp4800",
"ecelp7470",
"ecelp9600",
"egg",
"eol",
"eot",
"epub",
"exe",
"f4v",
"fbs",
"fh",
"fla",
"flac",
"flatpak",
"fli",
"flv",
"fpx",
"fst",
"fvt",
"g3",
"gh",
"gif",
"graffle",
"gz",
"gzip",
"h261",
"h263",
"h264",
"icns",
"ico",
"ief",
"img",
"ipa",
"iso",
"jar",
"jpeg",
"jpg",
"jpgv",
"jpm",
"jxr",
"key",
"ktx",
"lha",
"lib",
"lvp",
"lz",
"lzh",
"lzma",
"lzo",
"m3u",
"m4a",
"m4v",
"mar",
"mdi",
"mht",
"mid",
"midi",
"mj2",
"mka",
"mkv",
"mmr",
"mng",
"mobi",
"mov",
"movie",
"mp3",
"mp4",
"mp4a",
"mpeg",
"mpg",
"mpga",
"mxu",
"nef",
"npx",
"numbers",
"nupkg",
"o",
"oga",
"ogg",
"ogv",
"otf",
"pages",
"pbm",
"pcx",
"pdb",
"pdf",
"pea",
"pgm",
"pic",
"png",
"pnm",
"pot",
"potm",
"potx",
"ppa",
"ppam",
"ppm",
"pps",
"ppsm",
"ppsx",
"ppt",
"pptm",
"pptx",
"psd",
"pya",
"pyc",
"pyo",
"pyv",
"qt",
"rar",
"ras",
"raw",
"resources",
"rgb",
"rip",
"rlc",
"rmf",
"rmvb",
"rpm",
"rtf",
"rz",
"s3m",
"s7z",
"scpt",
"sgi",
"shar",
"snap",
"sil",
"sketch",
"slk",
"smv",
"snk",
"so",
"stl",
"suo",
"sub",
"swf",
"tar",
"tbz",
"tbz2",
"tga",
"tgz",
"thmx",
"tif",
"tiff",
"tlz",
"ttc",
"ttf",
"txz",
"udf",
"uvh",
"uvi",
"uvm",
"uvp",
"uvs",
"uvu",
"viv",
"vob",
"war",
"wav",
"wax",
"wbmp",
"wdp",
"weba",
"webm",
"webp",
"whl",
"wim",
"wm",
"wma",
"wmv",
"wmx",
"woff",
"woff2",
"wrm",
"wvx",
"xbm",
"xif",
"xla",
"xlam",
"xls",
"xlsb",
"xlsm",
"xlsx",
"xlt",
"xltm",
"xltx",
"xm",
"xmind",
"xpi",
"xpm",
"xwd",
"xz",
"z",
"zip",
"zipx"
```
<sup><a href='/src/MarkdownSnippets/Reading/Exclusions/SnippetFileExclusions.cs#L54-L323' title='Snippet source file'>snippet source</a> | <a href='#snippet-BinaryFileExtensions' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
### No comment files
Files that cannot contain comments are excluded.
<!-- snippet: NoAcceptCommentsExtensions -->
<a id='snippet-NoAcceptCommentsExtensions'></a>
```cs
"DotSettings",
"csv",
"json",
"geojson",
"sln"
```
<sup><a href='/src/MarkdownSnippets/Reading/Exclusions/SnippetFileExclusions.cs#L17-L25' title='Snippet source file'>snippet source</a> | <a href='#snippet-NoAcceptCommentsExtensions' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
================================================
FILE: docs/github-action.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/github-action.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# GitHub Actions
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.
Add the following to `.github\workflows\on-push-do-doco.yml` in the target repository.
<!-- snippet: on-push-do-docs.yml -->
<a id='snippet-on-push-do-docs.yml'></a>
```yml
name: on-push-do-docs
on:
push:
jobs:
docs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Run MarkdownSnippets
run: |
dotnet tool install --global MarkdownSnippets.Tool
mdsnippets ${GITHUB_WORKSPACE}
shell: bash
- name: Push changes
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git commit -m "Docs changes" -a || echo "nothing to commit"
remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git"
branch="${GITHUB_REF:11}"
git push "${remote}" ${branch} || echo "nothing to push"
shell: bash
```
<sup><a href='/docs/on-push-do-docs.yml#L1-L22' title='Snippet source file'>snippet source</a> | <a href='#snippet-on-push-do-docs.yml' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
This action performs the following tasks:
* Use the [Checkout Action](https://github.com/marketplace/actions/checkout) to pull down the source
* Install the MarkdownSnippets dotnet tool
* Run MarkdownSnippets against the current directory
* Push any changes back to GitHub
## More Info
* [Software installed on GitHub-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners)
================================================
FILE: docs/header.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/header.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Header
When a .md file is written, a header is include. The default header is:
<!-- snippet: HeaderWriterTests.DefaultHeader.verified.txt -->
<a id='snippet-HeaderWriterTests.DefaultHeader.verified.txt'></a>
```txt
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: {relativePath}
To change this file edit the source file and then run MarkdownSnippets.
```
<sup><a href='/src/Tests/HeaderWriterTests.DefaultHeader.verified.txt#L1-L4' title='Snippet source file'>snippet source</a> | <a href='#snippet-HeaderWriterTests.DefaultHeader.verified.txt' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## Disable Header
To disable the header use `--write-header`
```ps
mdsnippets --write-header false
```
## Custom Header
To apply a custom header use `--header`. `{relativePath}` will be replaced with the relative path of the `.source.md` file.
```ps
mdsnippets --header "GENERATED FILE - Source File: {relativePath}"
```
## Newlines in Header
To insert a newline use `\n`
```ps
mdsnippets --header "GENERATED FILE\nSource File: {relativePath}"
```
================================================
FILE: docs/includes.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/includes.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Includes
## Including full code files
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:
<pre>
snippet: directory/FileToInclude.txt
</pre>
The path syntax uses forward slashes `/`.
## Including urls
<pre>
snippet: http://myurl
</pre>
## Including a snippet from an external URL
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 `#`:
<pre>
web-snippet:https://raw.githubusercontent.com/owner/repo/branch/path/to/file.cs#snippetKey
</pre>
This will fetch the file from the URL, extract the snippet with the given key, and embed it in your Markdown.
## Markdown includes
Markdown includes are pulled into the document before passing the content through the snippet insertion.
### Defining an include
Add a file anywhere in the target directory that is suffixed with `.include.md`. For example, the file might be named `theKey.include.md`.
### Using an include
Add the following to the markdown:
```
include: theKey
```
================================================
FILE: docs/indentation.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/indentation.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Code indentation
The code snippets will do smart trimming of snippet indentation.
For example given this snippet:
<pre>
••// begin-snippet MySnippetName
••Line one of the snippet
••••Line two of the snippet
••// end-snippet
</pre>
The leading two spaces (••) will be trimmed and the result will be:
```
Line one of the snippet
••Line two of the snippet
```
The same behavior will apply to leading tabs.
## Do not mix tabs and spaces
If tabs and spaces are mixed there is no way for the snippets to work out what to trim.
So given this snippet:
<pre>
••// begin-snippet MySnippetNamea
••Line one of the snippet
➙➙Line one of the snippet
••// end-snippet
</pre>
Where ➙ is a tab.
The resulting markdown will be will be
<pre>
Line one of the snippet
➙➙Line one of the snippet
</pre>
Note that none of the tabs have been trimmed.
================================================
FILE: docs/max-width.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/max-width.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Max Width
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.
## Usage
### Config File
```
{
"MaxWidth": 80
}
```
### Command Line
```
--max-width 80
```
### Code Api
<!-- snippet: DirectoryMarkdownProcessorRunMaxWidth -->
<a id='snippet-DirectoryMarkdownProcessorRunMaxWidth'></a>
```cs
var processor = new DirectoryMarkdownProcessor(
"targetDirectory",
maxWidth: 80,
directoryIncludes: _ => true,
markdownDirectoryIncludes: _ => true,
snippetDirectoryIncludes: _ => true);
processor.Run();
```
<sup><a href='/src/Tests/Snippets/Usage.cs#L33-L43' title='Snippet source file'>snippet source</a> | <a href='#snippet-DirectoryMarkdownProcessorRunMaxWidth' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## References
https://en.wikipedia.org/wiki/Line_length#Electronic_text
> 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.
================================================
FILE: docs/mdsource/api.source.md
================================================
# Library Usage
## NuGet package
https://nuget.org/packages/MarkdownSnippets/ [](https://www.nuget.org/packages/MarkdownSnippets/)
## Reading snippets from files
snippet: ReadingFilesSimple
## Ignored paths
To change conventions manipulate lists `MarkdownSnippets.Exclusions.NoAcceptCommentsExtensions` and `MarkdownSnippets.Exclusions.BinaryFileExtensions`.
================================================
FILE: docs/mdsource/config-file.source.md
================================================
# Config File
The [dotnet tool](/readme.md#installation) and the [MSBuild Task](msbuild.md) support a config file convention.
Add a file named `mdsnippets.json` at the target directory with the following content:
## For [InPlaceOverwrite](https://github.com/SimonCropp/MarkdownSnippets#inplaceoverwrite)
snippet: InPlaceOverwrite.json
## For [SourceTransform](https://github.com/SimonCropp/MarkdownSnippets#sourcetransform)
snippet: SourceTransform.json
## All Settings
snippet: allConfig.json
## JSON Schema
Editor help is available by adding the `$schema` field to the `mdsnippets.json` file.
```json
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json"
}
```
In the screenshot, [JetBrains Rider](https://jetbrains.com/rider), is able to offer code completion support.

The schema also includes `enum` values for constrained value types.

## More Info
* [ReadOnly: Mark resulting files as read only](/readme.md#mark-resulting-files-as-read-only)
* [LinkFormat](/readme.md#linkformat).
* [Convention](/readme.md#document-convention).
* [TocLevel: Heading level](/docs/toc.md#heading-level).
* [TocExcludes: Ignore headings](/docs/toc.md#ignore-headings).
* [Exclude: Exclude directories from discovery](/docs/exclusion.md).
* [WriteHeader: Disable Header](/docs/header.md#disable-header).
* [UrlPrefix](/readme.md#urlprefix).
* [UrlsAsSnippets: Urls to files to be included as snippets](/readme.md#urlsassnippets).
* 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.
================================================
FILE: docs/mdsource/doc-index.include.md
================================================
* Developer Information
* [.net API](/docs/api.md)
* [MsBuild Task](/docs/msbuild.md)
* [Github Action](/docs/github-action.md)
* Customisation
* [Config file convention](/docs/config-file.md)
* [Max Width](/docs/max-width.md)
* [Includes](/docs/includes.md)
* [Directory Exclusion](/docs/exclusion.md)
* [Header](/docs/header.md)
* Writing Documentation
* [Indentation](/docs/indentation.md)
* [Table of contents](/docs/toc.md)
================================================
FILE: docs/mdsource/exclusion.source.md
================================================
# Exclusions
## Exclude directories from snippet and markdown discovery
To exclude directories use `-e` or `--exclude-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets -e foo:bar
```
## Exclude snippets from directories
To exclude directories from snippet discovery use `--exclude-snippet-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets --exclude-snippet-directories foo:bar
```
## Exclude markdown from directories
To exclude directories from markdown discovery use `--exclude-markdown-directories`.
For example the following will exclude any directory containing 'foo' or 'bar'
```ps
mdsnippets --exclude-markdown-directories foo:bar
```
## Exclude files from snippet discovery
To exclude specific files from snippet discovery, add an `ExcludeSnippetFiles` array to [`mdsnippets.json`](/docs/config-file.md). Each entry is a glob pattern matched (case-insensitively) against the file *name* — not the full path. Patterns support `*` (any sequence of characters) and `?` (a single character).
For example, the following excludes all Verify snapshot files so that `<!-- begin-snippet -->` markers hand-added to a `*.verified.txt` are not picked up as snippet sources:
```json
{
"ExcludeSnippetFiles": [
"*.verified.txt",
"*.received.txt"
]
}
```
Matched files are still visible to the include/all-files enumeration — only snippet scanning skips them.
## Ignored paths
### Directory exclusion rules:
snippet: DefaultDirectoryExclusions.cs
### File exclusion rules
All binary files as defined by https://github.com/sindresorhus/binary-extensions/:
snippet: BinaryFileExtensions
### No comment files
Files that cannot contain comments are excluded.
snippet: NoAcceptCommentsExtensions
================================================
FILE: docs/mdsource/github-action.source.md
================================================
# GitHub Actions
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.
Add the following to `.github\workflows\on-push-do-doco.yml` in the target repository.
snippet: on-push-do-docs.yml
This action performs the following tasks:
* Use the [Checkout Action](https://github.com/marketplace/actions/checkout) to pull down the source
* Install the MarkdownSnippets dotnet tool
* Run MarkdownSnippets against the current directory
* Push any changes back to GitHub
## More Info
* [Software installed on GitHub-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/software-installed-on-github-hosted-runners)
================================================
FILE: docs/mdsource/header.source.md
================================================
# Header
When a .md file is written, a header is include. The default header is:
snippet: HeaderWriterTests.DefaultHeader.verified.txt
## Disable Header
To disable the header use `--write-header`
```ps
mdsnippets --write-header false
```
## Custom Header
To apply a custom header use `--header`. `{relativePath}` will be replaced with the relative path of the `.source.md` file.
```ps
mdsnippets --header "GENERATED FILE - Source File: {relativePath}"
```
## Newlines in Header
To insert a newline use `\n`
```ps
mdsnippets --header "GENERATED FILE\nSource File: {relativePath}"
```
================================================
FILE: docs/mdsource/includes.source.md
================================================
# Includes
## Including full code files
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:
<pre>
snippet: directory/FileToInclude.txt
</pre>
The path syntax uses forward slashes `/`.
## Including urls
<pre>
snippet: http://myurl
</pre>
## Including a snippet from an external URL
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 `#`:
<pre>
web-snippet:https://raw.githubusercontent.com/owner/repo/branch/path/to/file.cs#snippetKey
</pre>
This will fetch the file from the URL, extract the snippet with the given key, and embed it in your Markdown.
## Markdown includes
Markdown includes are pulled into the document before passing the content through the snippet insertion.
### Defining an include
Add a file anywhere in the target directory that is suffixed with `.include.md`. For example, the file might be named `theKey.include.md`.
### Using an include
Add the following to the markdown:
```
include: theKey
```
================================================
FILE: docs/mdsource/indentation.source.md
================================================
# Code indentation
The code snippets will do smart trimming of snippet indentation.
For example given this snippet:
<pre>
••// begin-snippet MySnippetName
••Line one of the snippet
••••Line two of the snippet
••// end-snippet
</pre>
The leading two spaces (••) will be trimmed and the result will be:
```
Line one of the snippet
••Line two of the snippet
```
The same behavior will apply to leading tabs.
## Do not mix tabs and spaces
If tabs and spaces are mixed there is no way for the snippets to work out what to trim.
So given this snippet:
<pre>
••// begin-snippet MySnippetNamea
••Line one of the snippet
➙➙Line one of the snippet
••// end-snippet
</pre>
Where ➙ is a tab.
The resulting markdown will be will be
<pre>
Line one of the snippet
➙➙Line one of the snippet
</pre>
Note that none of the tabs have been trimmed.
================================================
FILE: docs/mdsource/max-width.source.md
================================================
# Max Width
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.
## Usage
### Config File
```
{
"MaxWidth": 80
}
```
### Command Line
```
--max-width 80
```
### Code Api
snippet: DirectoryMarkdownProcessorRunMaxWidth
## References
https://en.wikipedia.org/wiki/Line_length#Electronic_text
> 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.
================================================
FILE: docs/mdsource/msbuild.source.md
================================================
# MsBuild
An [MsBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) for merging snippets into markdown documents.
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.
This package only need to be included in one project of the solution. A logical choice is the test project.
## NuGet package
https://nuget.org/packages/MarkdownSnippets.MsBuild/ [](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/)
## More Info
* [Config file convention](/docs/config-file.md).
================================================
FILE: docs/mdsource/readme.source.md
================================================
# Documentation
include: doc-index
================================================
FILE: docs/mdsource/toc/tocAfter.txt
================================================
# Title
<!-- toc -->
## Contents
* [Heading 1](#heading-1)
* [Heading 2](#heading-2)
<!-- endToc -->
## Heading 1
Text1
## Heading 2
Text2
================================================
FILE: docs/mdsource/toc/tocBefore.txt
================================================
# Title
toc
## Heading 1
Text1
## Heading 1
Text2
================================================
FILE: docs/mdsource/toc.source.md
================================================
# Table of contents
If a line is `toc` it will be replaced with a table of contents
So if a markdown document contains the following:
snippet: tocBefore.txt
The result will be rendered:
snippet: tocAfter.txt
## Heading Level
Headings with level 2 (`##`) or greater can be rendered. By default all level 2 and level 3 headings are included.
To include more levels use the `--toc-level` argument. So for example to include headings levels 2 though level 6 use:
```ps
mdsnippets --toc-level 5
```
## Ignore Headings
To exclude headings use the `--toc-excludes` argument. So for example to exclude `heading1` and `heading2` use:
```ps
mdsnippets --toc-excludes heading1:heading2
```
================================================
FILE: docs/msbuild.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/msbuild.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# MsBuild
An [MsBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) for merging snippets into markdown documents.
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.
This package only need to be included in one project of the solution. A logical choice is the test project.
## NuGet package
https://nuget.org/packages/MarkdownSnippets.MsBuild/ [](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/)
## More Info
* [Config file convention](/docs/config-file.md).
================================================
FILE: docs/on-push-do-docs.yml
================================================
name: on-push-do-docs
on:
push:
jobs:
docs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Run MarkdownSnippets
run: |
dotnet tool install --global MarkdownSnippets.Tool
mdsnippets ${GITHUB_WORKSPACE}
shell: bash
- name: Push changes
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git commit -m "Docs changes" -a || echo "nothing to commit"
remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git"
branch="${GITHUB_REF:11}"
git push "${remote}" ${branch} || echo "nothing to push"
shell: bash
================================================
FILE: docs/readme.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/readme.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Documentation
* Developer Information<!-- include: doc-index. path: /docs/mdsource/doc-index.include.md -->
* [.net API](/docs/api.md)
* [MsBuild Task](/docs/msbuild.md)
* [Github Action](/docs/github-action.md)
* Customisation
* [Config file convention](/docs/config-file.md)
* [Max Width](/docs/max-width.md)
* [Includes](/docs/includes.md)
* [Directory Exclusion](/docs/exclusion.md)
* [Header](/docs/header.md)
* Writing Documentation
* [Indentation](/docs/indentation.md)
* [Table of contents](/docs/toc.md)<!-- endInclude -->
================================================
FILE: docs/toc.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /docs/mdsource/toc.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# Table of contents
If a line is `toc` it will be replaced with a table of contents
So if a markdown document contains the following:
<!-- snippet: tocBefore.txt -->
<a id='snippet-tocBefore.txt'></a>
```txt
# Title
toc
## Heading 1
Text1
## Heading 1
Text2
```
<sup><a href='/docs/mdsource/toc/tocBefore.txt#L1-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-tocBefore.txt' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
The result will be rendered:
<!-- snippet: tocAfter.txt -->
<a id='snippet-tocAfter.txt'></a>
```txt
# Title
<!-- toc -->
## Contents
* [Heading 1](#heading-1)
* [Heading 2](#heading-2)
<!-- endToc -->
## Heading 1
Text1
## Heading 2
Text2
```
<sup><a href='/docs/mdsource/toc/tocAfter.txt#L1-L16' title='Snippet source file'>snippet source</a> | <a href='#snippet-tocAfter.txt' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## Heading Level
Headings with level 2 (`##`) or greater can be rendered. By default all level 2 and level 3 headings are included.
To include more levels use the `--toc-level` argument. So for example to include headings levels 2 though level 6 use:
```ps
mdsnippets --toc-level 5
```
## Ignore Headings
To exclude headings use the `--toc-excludes` argument. So for example to exclude `heading1` and `heading2` use:
```ps
mdsnippets --toc-excludes heading1:heading2
```
================================================
FILE: license.txt
================================================
The MIT License (MIT)
Copyright (c) 2013 Simon Cropp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: readme.md
================================================
<!--
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: /readme.source.md
To change this file edit the source file and then run MarkdownSnippets.
-->
# <img src="/src/icon.png" height="30px"> MarkdownSnippets
[](https://ci.appveyor.com/project/SimonCropp/MarkdownSnippets)
[](https://www.nuget.org/packages/MarkdownSnippets.Tool/)
[](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/)
[](https://www.nuget.org/packages/MarkdownSnippets/)
A [dotnet tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) or [MsBuild Task](/docs/msbuild.md) that extract snippets from code files and merges them into markdown documents.
**See [Milestones](../../milestones?state=closed) for release notes.**
**[.net 10](https://dotnet.microsoft.com/download/dotnet/10.0) or higher is required to run the dotnet tool.**
## Value Proposition
Automatically extract snippets from code and injecting them into markdown documents has several benefits:
* Snippets can be verified by a compiler or parser.
* Tests can be run on snippets, or snippets can be pulled from existing tests.
* Changes in code are automatically reflected in documentation.
* Snippets are less likely to get out of sync with the main code-base.
* Snippets in markdown is easier to create and maintain since any preferred editor can be used to edit them.
## Behavior
* Recursively scan the target directory for code files containing snippets. (See [exclusion](/docs/exclusion.md)).
* Recursively scan the target directory for markdown (`.md` or `mdx`) files. (See [Document Scanning](#document-convention)).
* Merge the snippets into those markdown files.
## Installation
Ensure [dotnet CLI is installed](https://docs.microsoft.com/en-us/dotnet/core/tools/).
Install [MarkdownSnippets.Tool](https://nuget.org/packages/MarkdownSnippets.Tool/)
```ps
dotnet tool install -g MarkdownSnippets.Tool
```
See also: [MsBuild Task usage](/docs/msbuild.md)
## Usage
```ps
mdsnippets C:\Code\TargetDirectory
```
If no directory is passed the current directory will be used, but only if it exists with a git repository directory tree. If not an error is returned.
### Document Convention
There are two approaches scanning and modifying markdown files.
#### SourceTransform
This is the default.
##### source.md file
The file convention recursively scans the target directory for all `*.source.md` files. Once snippets are merged the `.source.md` to produce `.md` files. So for example `readme.source.md` would be merged with snippets to produce `readme.md`. Note that this process will overwrite any existing `.md` files that have matching `.source.md` files.
#### mdsource directory
There is a secondary convention that leverages the use of a directory named `mdsource`. Where `.source.md` files are placed in a `mdsource` sub-directory, the `mdsource` part of the file path will be removed when calculating the target path. This allows the `.source.md` to be grouped in a sub directory and avoid cluttering up the main documentation directory.
When using the `mdsource` convention, all references to other files, such as links and images, should specify the full path from the root of the repository. This will allow those links to work correctly in both the source and generated markdown files. Relative paths cannot work for both the source and the target file.
#### InPlaceOverwrite
Recursively scans the target directory for all `*.md` files and merges snippets into those files.
##### Command line
```ps
mdsnippets -c InPlaceOverwrite
```
```ps
mdsnippets --convention InPlaceOverwrite
```
##### Config file
Can be enabled in [mdsnippets.json config file](/docs/config-file.md).
```json
{
"Convention": "InPlaceOverwrite"
}
```
#### Moving from SourceTransform to InPlaceOverwrite
* Ensure `"WriteHeader": false` is used in `mdsnippets.json`.
* Ensure `"ReadOnly": false` is used in `mdsnippets.json`.
* Ensure using the current stable version and a docs generation has run.
* Delete all `.source.md` files.
* Modify `mdsnippets.json` to add `"Convention": "InPlaceOverwrite"`.
* Run the docs generation.
### Mark resulting files as read only
To mark the resulting documents files as read only use `-r` or `--read-only`.
This can be helpful in preventing incorrectly editing the documents file instead of the `.source.` file conventions.
```ps
mdsnippets -r true
```
```ps
mdsnippets --read-only true
```
## Defining Snippets
Any code wrapped in a convention based comment will be picked up. The comment needs to start with `begin-snippet:` which is followed by the key. The snippet is then terminated by `end-snippet`.
```
// begin-snippet: MySnippetName
My Snippet Code
// end-snippet
```
Named [C# regions](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-region) will also be picked up, with the name of the region used as the key.
```
#region MySnippetName
My Snippet Code
#endregion
```
To stop regions collapsing in Visual Studio [disable 'enter outlining mode when files open'](/docs/stop-regions-collapsing.png). See [Visual Studio outlining](https://docs.microsoft.com/en-us/visualstudio/ide/outlining).
### UrlsAsSnippets
Urls to files to be included as snippets. Space ` ` separated for multiple values.
Each url will be accessible using the file name as a key. Any snippets within the files will be extracted and accessible as individual keyed snippets.
```ps
mdsnippets --urls-as-snippets "https://github.com/SimonCropp/MarkdownSnippets/snippet.cs"
```
```ps
mdsnippets -u "https://github.com/SimonCropp/MarkdownSnippets/snippet.cs"
```
## Using Snippets
The keyed snippets can be used in any documentation `.md` file by adding the text `snippet: KEY`.
Then snippets with that key.
For example
<pre>
Some blurb about the below snippet
snippet: MySnippetName
</pre>
The resulting markdown will be:
Some blurb about the below snippet
<!-- snippet: MySnippetName -->
<a id='snippet-MySnippetName'></a>
```
My Snippet Code
```
<sup><a href='/relativeUrlToFile#L1-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-MySnippetName' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
Notes:
* The vertical bar ( | ) is used to separate adjacent links as per web accessibility recommendations: https://webaim.org/techniques/hypertext/hypertext_links#groups
* [H33: Supplementing link text with the title attribute](https://www.w3.org/TR/WCAG20-TECHS/H33.html)
### Including a snippet from the web
Snippets that start with `http` will be downloaded and the contents rendered. For example:
`snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt`
Will render:
<!-- snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt'></a>
```txt
The MIT License (MIT)
...
```
<sup><a href='#snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kept.
`web-snippet:` can be used to reference remote content where a specific snippet is defined in that content.
`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`
Will render:
<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
You can optionally provide a second URL that will be used for the source link. This is useful when the raw content URL is different from the view URL. For example:
`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`
Will render:
<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet#L1-L3' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
### Including a full file
If no snippet is found matching the defined name. The target directory will be searched for a file matching that name. For example:
`snippet: license.txt`
Will render:
<!-- snippet: license.txt -->
<a id='snippet-license.txt'></a>
```txt
The MIT License (MIT)
Copyright (c) 2013 Simon Cropp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
<sup><a href='/license.txt#L1-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-license.txt' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
### LinkFormat
Defines the format of `snippet source` links that appear under each snippet.
#### Command line
```ps
mdsnippets --link-format Bitbucket
```
```ps
mdsnippets -l Bitbucket
```
#### Values
<!-- snippet: LinkFormat.cs -->
<a id='snippet-LinkFormat.cs'></a>
```cs
namespace MarkdownSnippets;
public enum LinkFormat
{
GitHub,
Tfs,
Bitbucket,
GitLab,
DevOps,
None
}
```
<sup><a href='/src/MarkdownSnippets/Processing/LinkFormat.cs#L1-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-LinkFormat.cs' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
Link format `None` will omit the source link but still keep the snippet anchor.
### OmitSnippetLinks
The links below a snippet can be omitted.
#### Command line
```ps
mdsnippets --omit-snippet-links true
```
#### Config file
```
{
"OmitSnippetLinks": true
}
```
#### How links are constructed
<!-- snippet: BuildLink -->
<a id='snippet-BuildLink'></a>
```cs
switch (linkFormat)
{
case LinkFormat.GitHub:
Polyfill.Append(builder, $"{path}#L{snippet.StartLine}-L{snippet.EndLine}");
return;
case LinkFormat.Tfs:
Polyfill.Append(builder, $"{path}&line={snippet.StartLine}&lineEnd={snippet.EndLine}");
return;
case LinkFormat.Bitbucket:
Polyfill.Append(builder, $"{path}#lines={snippet.StartLine}:{snippet.EndLine}");
return;
case LinkFormat.GitLab:
Polyfill.Append(builder, $"{path}#L{snippet.StartLine}-{snippet.EndLine}");
return;
case LinkFormat.DevOps:
Polyfill.Append(builder, $"?path={path}&line={snippet.StartLine}&lineEnd={snippet.EndLine}&lineStartColumn=1&lineEndColumn=999");
return;
case LinkFormat.None:
throw new($"Unknown LinkFormat: {linkFormat}");
}
```
<sup><a href='/src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs#L133-L154' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildLink' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
### UrlPrefix
UrlPrefix allows a string to be defined that will prefix all snippet links. This is helpful when the markdown file are being hosted on a site that is not co-located with the source code files. It can be defined in the [config file](/docs/config-file.md), the [MsBuild task](/docs/msbuild.md), and the dotnet tool.
#### Command line
```ps
mdsnippets --url-prefix "TheUrlPrefix"
```
#### Config file
```
{
"UrlPrefix": "TheUrlPrefix"
}
```
## Add to Windows Explorer
Use [src/context-menu.reg](context-menu.reg) to add MarkdownSnippets to the Windows Explorer context menu.
<!-- snippet: context-menu.reg -->
<a id='snippet-context-menu.reg'></a>
```reg
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\Shell]
@="none"
[HKEY_CLASSES_ROOT\Directory\shell\mdsnippets]
"MUIVerb"="run mdsnippets"
"Position"="bottom"
[HKEY_CLASSES_ROOT\Directory\Background\shell\mdsnippets]
"MUIVerb"="run mdsnippets"
"Position"="bottom"
[HKEY_CLASSES_ROOT\Directory\shell\mdsnippets\command]
@="cmd.exe /c mdsnippets \"%V\""
[HKEY_CLASSES_ROOT\Directory\Background\shell\mdsnippets\command]
@="cmd.exe /c mdsnippets \"%V\""
```
<sup><a href='/src/context-menu.reg#L1-L13' title='Snippet source file'>snippet source</a> | <a href='#snippet-context-menu.reg' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
## Expressive Code / Snippet Metadata
When defining snippets, additional metadata can be added at the source to the rendered snippet using the following syntax.
```csharp
// begin-snippet: HelloWorld(title=Program.cs {1})
Console.WriteLine("Hello, World");
// end-snippet
```
Note the text within the parenthesis; this is metadata we want to add to the rendered Markdown block immediately after the language declaration. The metadata is stripped and the key remains `HelloWorld`. The feature produces the following output destination (will vary based on configuration):
````markdown
<-- begin-snippet: HelloWorld -->
```csharp title=Program.cs {1}
Console.WriteLine("Hello, World");
```
<-- end-snippet -->
````
This syntax is known as [Expressive Code](https://expressive-code.com/) and is supported in documentation systems such as [Astro Starlight](https://github.com/withastro/starlight/) but can be installed in any Markdown-powered tool that supports [reHype](https://github.com/rehypejs/rehype).
It is important to note, the metadata is not explicitly limited to Expressive code. Any text within the `()` will be rendered after the language block. This can be useful for adding additional information based on specific rendering engine. For example, if a presentation tool such as [Sli.dev](https://sli.dev/), then this feature to apply [magic-move animations](https://sli.dev/features/shiki-magic-move).
```csharp
// begin-snippet: EncapsulateVariable({*|2})
Console.WriteLine("Hello, World");
// end-snippet
```
The above snippet will render as follows:
````markdown
<-- begin-snippet: EncapsulateVariable -->
```csharp {*|2}
Console.WriteLine("Hello, World");
```
<-- end-snippet -->
````
## Language Override
By default the language of a rendered fenced code block is derived from the source file extension (e.g. a snippet extracted from a `.cs` file renders as `csharp`). The language can be overridden per snippet by adding a `lang=` token as the first item inside the parenthesised metadata:
```csharp
// begin-snippet: SampleJson(lang=json)
{"hello": "world"}
// end-snippet
```
Renders as:
````markdown
<-- begin-snippet: SampleJson -->
```json
{"hello": "world"}
```
<-- end-snippet -->
````
`lang=` can be combined with Expressive Code metadata — the language token must come first, followed by a space, then the remaining metadata:
```csharp
// begin-snippet: SampleJson(lang=json title=config.json)
{"hello": "world"}
// end-snippet
```
The value must be lowercase alphanumeric.
## More Documentation
* Developer Information<!-- include: doc-index. path: /docs/mdsource/doc-index.include.md -->
* [.net API](/docs/api.md)
* [MsBuild Task](/docs/msbuild.md)
* [Github Action](/docs/github-action.md)
* Customisation
* [Config file convention](/docs/config-file.md)
* [Max Width](/docs/max-width.md)
* [Includes](/docs/includes.md)
* [Directory Exclusion](/docs/exclusion.md)
* [Header](/docs/header.md)
* Writing Documentation
* [Indentation](/docs/indentation.md)
* [Table of contents](/docs/toc.md)<!-- endInclude -->
## Credits
Loosely based on some code from https://github.com/shiftkey/scribble.
## Icon
[Down](https://thenounproject.com/AlfredoCreates/collection/arrows-5-glyph/) by [Alfredo Creates](https://thenounproject.com/AlfredoCreates) from [The Noun Project](https://thenounproject.com/).
================================================
FILE: readme.source.md
================================================
# <img src="/src/icon.png" height="30px"> MarkdownSnippets
[](https://ci.appveyor.com/project/SimonCropp/MarkdownSnippets)
[](https://www.nuget.org/packages/MarkdownSnippets.Tool/)
[](https://www.nuget.org/packages/MarkdownSnippets.MsBuild/)
[](https://www.nuget.org/packages/MarkdownSnippets/)
A [dotnet tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools) or [MsBuild Task](/docs/msbuild.md) that extract snippets from code files and merges them into markdown documents.
**See [Milestones](../../milestones?state=closed) for release notes.**
**[.net 10](https://dotnet.microsoft.com/download/dotnet/10.0) or higher is required to run the dotnet tool.**
## Value Proposition
Automatically extract snippets from code and injecting them into markdown documents has several benefits:
* Snippets can be verified by a compiler or parser.
* Tests can be run on snippets, or snippets can be pulled from existing tests.
* Changes in code are automatically reflected in documentation.
* Snippets are less likely to get out of sync with the main code-base.
* Snippets in markdown is easier to create and maintain since any preferred editor can be used to edit them.
## Behavior
* Recursively scan the target directory for code files containing snippets. (See [exclusion](/docs/exclusion.md)).
* Recursively scan the target directory for markdown (`.md` or `mdx`) files. (See [Document Scanning](#document-convention)).
* Merge the snippets into those markdown files.
## Installation
Ensure [dotnet CLI is installed](https://docs.microsoft.com/en-us/dotnet/core/tools/).
Install [MarkdownSnippets.Tool](https://nuget.org/packages/MarkdownSnippets.Tool/)
```ps
dotnet tool install -g MarkdownSnippets.Tool
```
See also: [MsBuild Task usage](/docs/msbuild.md)
## Usage
```ps
mdsnippets C:\Code\TargetDirectory
```
If no directory is passed the current directory will be used, but only if it exists with a git repository directory tree. If not an error is returned.
### Document Convention
There are two approaches scanning and modifying markdown files.
#### SourceTransform
This is the default.
##### source.md file
The file convention recursively scans the target directory for all `*.source.md` files. Once snippets are merged the `.source.md` to produce `.md` files. So for example `readme.source.md` would be merged with snippets to produce `readme.md`. Note that this process will overwrite any existing `.md` files that have matching `.source.md` files.
#### mdsource directory
There is a secondary convention that leverages the use of a directory named `mdsource`. Where `.source.md` files are placed in a `mdsource` sub-directory, the `mdsource` part of the file path will be removed when calculating the target path. This allows the `.source.md` to be grouped in a sub directory and avoid cluttering up the main documentation directory.
When using the `mdsource` convention, all references to other files, such as links and images, should specify the full path from the root of the repository. This will allow those links to work correctly in both the source and generated markdown files. Relative paths cannot work for both the source and the target file.
#### InPlaceOverwrite
Recursively scans the target directory for all `*.md` files and merges snippets into those files.
##### Command line
```ps
mdsnippets -c InPlaceOverwrite
```
```ps
mdsnippets --convention InPlaceOverwrite
```
##### Config file
Can be enabled in [mdsnippets.json config file](/docs/config-file.md).
```json
{
"Convention": "InPlaceOverwrite"
}
```
#### Moving from SourceTransform to InPlaceOverwrite
* Ensure `"WriteHeader": false` is used in `mdsnippets.json`.
* Ensure `"ReadOnly": false` is used in `mdsnippets.json`.
* Ensure using the current stable version and a docs generation has run.
* Delete all `.source.md` files.
* Modify `mdsnippets.json` to add `"Convention": "InPlaceOverwrite"`.
* Run the docs generation.
### Mark resulting files as read only
To mark the resulting documents files as read only use `-r` or `--read-only`.
This can be helpful in preventing incorrectly editing the documents file instead of the `.source.` file conventions.
```ps
mdsnippets -r true
```
```ps
mdsnippets --read-only true
```
## Defining Snippets
Any code wrapped in a convention based comment will be picked up. The comment needs to start with `begin-snippet:` which is followed by the key. The snippet is then terminated by `end-snippet`.
```
// begin-snippet: MySnippetName
My Snippet Code
// end-snippet
```
Named [C# regions](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-region) will also be picked up, with the name of the region used as the key.
```
#region MySnippetName
My Snippet Code
#endregion
```
To stop regions collapsing in Visual Studio [disable 'enter outlining mode when files open'](/docs/stop-regions-collapsing.png). See [Visual Studio outlining](https://docs.microsoft.com/en-us/visualstudio/ide/outlining).
### UrlsAsSnippets
Urls to files to be included as snippets. Space ` ` separated for multiple values.
Each url will be accessible using the file name as a key. Any snippets within the files will be extracted and accessible as individual keyed snippets.
```ps
mdsnippets --urls-as-snippets "https://github.com/SimonCropp/MarkdownSnippets/snippet.cs"
```
```ps
mdsnippets -u "https://github.com/SimonCropp/MarkdownSnippets/snippet.cs"
```
## Using Snippets
The keyed snippets can be used in any documentation `.md` file by adding the text `snippet: KEY`.
Then snippets with that key.
For example
<pre>
Some blurb about the below snippet
snippet: MySnippetName
</pre>
The resulting markdown will be:
Some blurb about the below snippet
<!-- snippet: MySnippetName -->
<a id='snippet-MySnippetName'></a>
```
My Snippet Code
```
<sup><a href='/relativeUrlToFile#L1-L11' title='Snippet source file'>snippet source</a> | <a href='#snippet-MySnippetName' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->
Notes:
* The vertical bar ( | ) is used to separate adjacent links as per web accessibility recommendations: https://webaim.org/techniques/hypertext/hypertext_links#groups
* [H33: Supplementing link text with the title attribute](https://www.w3.org/TR/WCAG20-TECHS/H33.html)
### Including a snippet from the web
Snippets that start with `http` will be downloaded and the contents rendered. For example:
`snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt`
Will render:
<!-- snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt'></a>
```txt
The MIT License (MIT)
...
```
<sup><a href='#snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/license.txt' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kept.
`web-snippet:` can be used to reference remote content where a specific snippet is defined in that content.
`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`
Will render:
<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
You can optionally provide a second URL that will be used for the source link. This is useful when the raw content URL is different from the view URL. For example:
`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`
Will render:
<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet#L1-L3' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->
### Including a full file
If no snippet is found matching the defined name. The target directory will be searched for a file matching that name. For example:
`snippet: license.txt`
Will render:
snippet: license.txt
### LinkFormat
Defines the format of `snippet source` links that appear under each snippet.
#### Command line
```ps
mdsnippets --link-format Bitbucket
```
```ps
mdsnippets -l Bitbucket
```
#### Values
snippet: LinkFormat.cs
Link format `None` will omit the source link but still keep the snippet anchor.
### OmitSnippetLinks
The links below a snippet can be omitted.
#### Command line
```ps
mdsnippets --omit-snippet-links true
```
#### Config file
```
{
"OmitSnippetLinks": true
}
```
#### How links are constructed
snippet: BuildLink
### UrlPrefix
UrlPrefix allows a string to be defined that will prefix all snippet links. This is helpful when the markdown file are being hosted on a site that is not co-located with the source code files. It can be defined in the [config file](/docs/config-file.md), the [MsBuild task](/docs/msbuild.md), and the dotnet tool.
#### Command line
```ps
mdsnippets --url-prefix "TheUrlPrefix"
```
#### Config file
```
{
"UrlPrefix": "TheUrlPrefix"
}
```
## Add to Windows Explorer
Use [src/context-menu.reg](context-menu.reg) to add MarkdownSnippets to the Windows Explorer context menu.
snippet: context-menu.reg
## Expressive Code / Snippet Metadata
When defining snippets, additional metadata can be added at the source to the rendered snippet using the following syntax.
```csharp
// begin-snippet: HelloWorld(title=Program.cs {1})
Console.WriteLine("Hello, World");
// end-snippet
```
Note the text within the parenthesis; this is metadata we want to add to the rendered Markdown block immediately after the language declaration. The metadata is stripped and the key remains `HelloWorld`. The feature produces the following output destination (will vary based on configuration):
````markdown
<-- begin-snippet: HelloWorld -->
```csharp title=Program.cs {1}
Console.WriteLine("Hello, World");
```
<-- end-snippet -->
````
This syntax is known as [Expressive Code](https://expressive-code.com/) and is supported in documentation systems such as [Astro Starlight](https://github.com/withastro/starlight/) but can be installed in any Markdown-powered tool that supports [reHype](https://github.com/rehypejs/rehype).
It is important to note, the metadata is not explicitly limited to Expressive code. Any text within the `()` will be rendered after the language block. This can be useful for adding additional information based on specific rendering engine. For example, if a presentation tool such as [Sli.dev](https://sli.dev/), then this feature to apply [magic-move animations](https://sli.dev/features/shiki-magic-move).
```csharp
// begin-snippet: EncapsulateVariable({*|2})
Console.WriteLine("Hello, World");
// end-snippet
```
The above snippet will render as follows:
````markdown
<-- begin-snippet: EncapsulateVariable -->
```csharp {*|2}
Console.WriteLine("Hello, World");
```
<-- end-snippet -->
````
## Language Override
By default the language of a rendered fenced code block is derived from the source file extension (e.g. a snippet extracted from a `.cs` file renders as `csharp`). The language can be overridden per snippet by adding a `lang=` token as the first item inside the parenthesised metadata:
```csharp
// begin-snippet: SampleJson(lang=json)
{"hello": "world"}
// end-snippet
```
Renders as:
````markdown
<-- begin-snippet: SampleJson -->
```json
{"hello": "world"}
```
<-- end-snippet -->
````
`lang=` can be combined with Expressive Code metadata — the language token must come first, followed by a space, then the remaining metadata:
```csharp
// begin-snippet: SampleJson(lang=json title=config.json)
{"hello": "world"}
// end-snippet
```
The value must be lowercase alphanumeric.
## More Documentation
include: doc-index
## Credits
Loosely based on some code from https://github.com/shiftkey/scribble.
## Icon
[Down](https://thenounproject.com/AlfredoCreates/collection/arrows-5-glyph/) by [Alfredo Creates](https://thenounproject.com/AlfredoCreates) from [The Noun Project](https://thenounproject.com/).
================================================
FILE: schema.json
================================================
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "MarkdownSnippets",
"title": "Root",
"type": "object",
"required": [
],
"properties": {
"OmitSnippetLinks": {
"$id": "#root/OmitSnippetLinks",
"title": "OmitSnippetLinks",
"type": "boolean",
"examples": [
true
],
"default": false
},
"ReadOnly": {
"$id": "#root/ReadOnly",
"title": "Readonly",
"type": "boolean",
"examples": [
true
],
"default": false
},
"LinkFormat": {
"$id": "#root/LinkFormat",
"title": "Linkformat",
"type": "string",
"default": "GitHub",
"enum": [
"Tfs",
"GitHub",
"Bitbucket",
"GitLab",
"DevOps",
"None"
],
"pattern": "^.*$"
},
"TocLevel": {
"$id": "#root/TocLevel",
"title": "Toclevel",
"type": "integer",
"examples": [
3
],
"default": 1
},
"UrlsAsSnippets": {
"$id": "#root/UrlsAsSnippets",
"title": "Urlsassnippets",
"type": "array",
"default": [],
"items": {
"$id": "#root/UrlsAsSnippets/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"https://github.com/SimonCropp/MarkdownSnippets/snippet.cs"
],
"pattern": "^.*$"
}
},
"TocExcludes": {
"$id": "#root/TocExcludes",
"title": "Tocexcludes",
"type": "array",
"default": [],
"items": {
"$id": "#root/TocExcludes/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"heading1",
"heading2"
],
"pattern": "^.*$"
}
},
"ExcludeMarkdownDirectories": {
"$id": "#root/ExcludeMarkdownDirectories",
"title": "ExcludeMarkdownDirectories",
"type": "array",
"default": [],
"items": {
"$id": "#root/ExcludeMarkdownDirectories/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"directory1",
"directory2"
],
"pattern": "^.*$"
}
},
"ExcludeSnippetDirectories": {
"$id": "#root/ExcludeSnippetDirectories",
"title": "ExcludeSnippetDirectories",
"type": "array",
"default": [],
"items": {
"$id": "#root/ExcludeSnippetDirectories/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"directory1",
"directory1"
],
"pattern": "^.*$"
}
},
"ExcludeSnippetFiles": {
"$id": "#root/ExcludeSnippetFiles",
"title": "ExcludeSnippetFiles",
"type": "array",
"default": [],
"items": {
"$id": "#root/ExcludeSnippetFiles/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"*.verified.txt",
"*.received.txt"
],
"pattern": "^.*$"
}
},
"ExcludeDirectories": {
"$id": "#root/ExcludeDirectories",
"title": "ExcludeDirectories",
"type": "array",
"default": [],
"items": {
"$id": "#root/ExcludeDirectories/items",
"title": "Items",
"type": "string",
"default": "",
"examples": [
"directory1",
"directory2"
],
"pattern": "^.*$"
}
},
"DocumentExtensions": {
"$id": "#root/DocumentExtensions",
"title": "Documentextensions",
"type": "array",
"default": [],
"items": {
"$id": "#root/DocumentExtensions/items",
"title": "Items",
"type": "string",
"default": "md",
"examples": [
"md"
],
"pattern": "^.*$"
}
},
"Convention": {
"$id": "#root/Convention",
"title": "Convention",
"type": "string",
"default": "InPlaceOverwrite",
"enum": [
"InPlaceOverwrite",
"SourceTransform"
],
"pattern": "^.*$"
},
"WriteHeader": {
"$id": "#root/WriteHeader",
"title": "Writeheader",
"type": "boolean",
"examples": [
false
],
"default": true
},
"MaxWidth": {
"$id": "#root/MaxWidth",
"title": "Maxwidth",
"type": "integer",
"examples": [
80
],
"default": 100
},
"Header": {
"$id": "#root/Header",
"title": "Header",
"type": "string",
"default": "",
"examples": [
"GENERATED FILE - Source File: {relativePath}"
],
"pattern": "^.*$"
},
"UrlPrefix": {
"$id": "#root/UrlPrefix",
"title": "UrlPrefix",
"type": "string",
"default": "",
"examples": [
"TheUrlPrefix"
],
"pattern": "^.*$"
},
"TreatMissingAsWarning": {
"$id": "#root/TreatMissingAsWarning",
"title": "TreatMissingAsWarning",
"type": "boolean",
"examples": [
false
],
"default": true
},
"ValidateContent": {
"$id": "#root/ValidateContent",
"title": "ValidateContent",
"type": "boolean",
"examples": [
false
],
"default": true
}
}
}
================================================
FILE: src/Benchmarks/Benchmarks.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<ProjectReference Include="..\MarkdownSnippets\MarkdownSnippets.csproj" />
</ItemGroup>
</Project>
================================================
FILE: src/Benchmarks/FullRenderBenchmarks.cs
================================================
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BenchmarkDotNet.Attributes;
using MarkdownSnippets;
[MemoryDiagnoser]
public class FullRenderBenchmarks
{
string repoRoot = null!;
Dictionary<string, IReadOnlyList<Snippet>> snippetLookup = null!;
string markdownInput = null!;
static bool DirectoryFilter(string path) =>
!path.Contains("IncludeFileFinder") &&
!path.Contains("DirectoryMarkdownProcessor") &&
!DefaultDirectoryExclusions.ShouldExcludeDirectory(path);
static bool SnippetDirectoryFilter(string path) =>
DirectoryFilter(path) &&
!path.Contains("badsnippets");
[GlobalSetup]
public void Setup()
{
repoRoot = GitRepoDirectoryFinder.FindForFilePath();
// pre-load snippets for the in-memory MarkdownProcessor benchmark
var processor = new DirectoryMarkdownProcessor(
targetDirectory: repoRoot,
directoryIncludes: DirectoryFilter,
markdownDirectoryIncludes: _ => true,
snippetDirectoryIncludes: SnippetDirectoryFilter,
scanForMdFiles: false,
tocLevel: 1);
snippetLookup = processor.Snippets
.Where(s => !s.IsInError)
.GroupBy(s => s.Key)
.ToDictionary(g => g.Key, g => (IReadOnlyList<Snippet>) g.ToList());
// build a synthetic markdown doc that references real snippet keys
var sb = new StringBuilder();
sb.AppendLine("# Benchmark Document");
sb.AppendLine();
foreach (var key in snippetLookup.Keys.Take(50))
{
sb.AppendLine($"snippet: {key}");
sb.AppendLine();
}
markdownInput = sb.ToString();
}
/// <summary>
/// End-to-end: file discovery, snippet extraction, markdown processing, and file writing.
/// </summary>
[Benchmark(Description = "Full repo render")]
public void FullRepoRender()
{
var processor = new DirectoryMarkdownProcessor(
targetDirectory: repoRoot,
directoryIncludes: DirectoryFilter,
markdownDirectoryIncludes: _ => true,
snippetDirectoryIncludes: SnippetDirectoryFilter,
tocLevel: 1,
tocExcludes: ["Icon", "Credits", "Release Notes"]);
processor.Run();
}
/// <summary>
/// Constructor only: file discovery + snippet extraction (no markdown processing).
/// </summary>
[Benchmark(Description = "File discovery + snippet extraction")]
public DirectoryMarkdownProcessor FileDiscoveryAndSnippetExtraction() =>
new(
targetDirectory: repoRoot,
directoryIncludes: DirectoryFilter,
markdownDirectoryIncludes: _ => true,
snippetDirectoryIncludes: SnippetDirectoryFilter,
scanForMdFiles: false,
tocLevel: 1);
/// <summary>
/// In-memory markdown processing with pre-loaded snippets, no file I/O.
/// </summary>
[Benchmark(Description = "MarkdownProcessor.Apply (50 snippets)")]
public string MarkdownProcessorApply()
{
var processor = new MarkdownProcessor(
convention: DocumentConvention.SourceTransform,
snippets: snippetLookup,
includes: [],
appendSnippets: SimpleSnippetMarkdownHandling.Append,
snippetSourceFiles: [],
tocLevel: 2,
writeHeader: false,
targetDirectory: repoRoot,
validateContent: false,
allFiles: []);
return processor.Apply(markdownInput, "benchmark.source.md");
}
}
================================================
FILE: src/Benchmarks/Program.cs
================================================
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<FullRenderBenchmarks>(args: args);
================================================
FILE: src/ConfigReader/AssemblyInfo.cs
================================================
[assembly: InternalsVisibleTo("ConfigReader.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")]
================================================
FILE: src/ConfigReader/ConfigDefaults.cs
================================================
public static class ConfigDefaults
{
public static ConfigResult Convert(ConfigInput? fileConfig, ConfigInput otherConfig)
{
if (fileConfig == null)
{
return new()
{
ValidateContent = otherConfig.ValidateContent.GetValueOrDefault(),
OmitSnippetLinks = otherConfig.OmitSnippetLinks.GetValueOrDefault(),
ReadOnly = otherConfig.ReadOnly.GetValueOrDefault(),
WriteHeader = otherConfig.WriteHeader,
LinkFormat = otherConfig.LinkFormat.GetValueOrDefault(LinkFormat.GitHub),
Convention = otherConfig.Convention.GetValueOrDefault(DocumentConvention.SourceTransform),
ExcludeDirectories = otherConfig.ExcludeDirectories,
ExcludeMarkdownDirectories = otherConfig.ExcludeMarkdownDirectories,
ExcludeSnippetDirectories = otherConfig.ExcludeSnippetDirectories,
ExcludeSnippetFiles = otherConfig.ExcludeSnippetFiles,
Header = otherConfig.Header,
UrlPrefix = otherConfig.UrlPrefix,
TocExcludes = otherConfig.TocExcludes,
UrlsAsSnippets = otherConfig.UrlsAsSnippets,
TocLevel = otherConfig.TocLevel.GetValueOrDefault(2),
MaxWidth = otherConfig.MaxWidth.GetValueOrDefault(int.MaxValue),
TreatMissingAsWarning = otherConfig.TreatMissingAsWarning.GetValueOrDefault(),
};
}
return new()
{
ValidateContent = GetValueOrDefault("ValidateContent", otherConfig.ValidateContent, fileConfig.ValidateContent, false),
OmitSnippetLinks = GetValueOrDefault("OmitSnippetLinks", otherConfig.OmitSnippetLinks, fileConfig.OmitSnippetLinks, false),
ReadOnly = GetValueOrNull("ReadOnly", otherConfig.ReadOnly, fileConfig.ReadOnly),
WriteHeader = GetValueOrNull("WriteHeader", otherConfig.WriteHeader, fileConfig.WriteHeader),
LinkFormat = GetValueOrDefault("LinkFormat", otherConfig.LinkFormat, fileConfig.LinkFormat, LinkFormat.GitHub),
Convention = GetValueOrDefault("Convention", otherConfig.Convention, fileConfig.Convention, DocumentConvention.SourceTransform),
TocLevel = GetValueOrDefault("TocLevel", otherConfig.TocLevel, fileConfig.TocLevel, 2),
MaxWidth = GetValueOrDefault("MaxWidth", otherConfig.MaxWidth, fileConfig.MaxWidth, int.MaxValue),
Header = GetValueOrDefault("Header", otherConfig.Header, fileConfig.Header),
UrlPrefix = GetValueOrDefault("UrlPrefix", otherConfig.UrlPrefix, fileConfig.UrlPrefix),
ExcludeDirectories = JoinLists(fileConfig.ExcludeDirectories, otherConfig.ExcludeDirectories),
ExcludeSnippetDirectories = JoinLists(fileConfig.ExcludeSnippetDirectories, otherConfig.ExcludeSnippetDirectories),
ExcludeSnippetFiles = JoinLists(fileConfig.ExcludeSnippetFiles, otherConfig.ExcludeSnippetFiles),
ExcludeMarkdownDirectories = JoinLists(fileConfig.ExcludeMarkdownDirectories, otherConfig.ExcludeMarkdownDirectories),
TocExcludes = JoinLists(fileConfig.TocExcludes, otherConfig.TocExcludes),
UrlsAsSnippets = JoinLists(fileConfig.UrlsAsSnippets, otherConfig.UrlsAsSnippets),
TreatMissingAsWarning = GetValueOrDefault(
"TreatMissingAsWarning",
otherConfig.TreatMissingAsWarning,
fileConfig.TreatMissingAsWarning,
false),
};
}
static List<string> JoinLists(List<string> list1, List<string> list2) =>
list1
.Concat(list2)
.Distinct()
.ToList();
static T GetValueOrDefault<T>(string name, T? input, T? config, T defaultValue)
where T : struct
{
if (input != null && config != null)
{
throw new ConfigurationException($"'{name}' cannot be defined in both mdsnippets.json and input");
}
if (input != null)
{
return input.Value;
}
if (config != null)
{
return config.Value;
}
return defaultValue;
}
static T? GetValueOrNull<T>(string name, T? input, T? config)
where T : struct
{
if (input != null &&
config != null)
{
throw new ConfigurationException($"'{name}' cannot be defined in both mdsnippets.json and input");
}
if (input != null)
{
return input.Value;
}
return config;
}
static string? GetValueOrDefault(string name, string? input, string? config)
{
if (input != null && config != null)
{
throw new ConfigurationException($"'{name}' cannot be defined in both mdsnippets.json and input");
}
if (input != null)
{
return input;
}
return config;
}
}
================================================
FILE: src/ConfigReader/ConfigInput.cs
================================================
public class ConfigInput
{
public bool? ReadOnly { get; init; }
public bool? ValidateContent { get; init; }
public bool? OmitSnippetLinks { get; init; }
public LinkFormat? LinkFormat { get; init; }
public DocumentConvention? Convention { get; init; }
public int? TocLevel { get; init; }
public int? MaxWidth { get; init; }
public List<string> UrlsAsSnippets { get; init; } = [];
public List<string> ExcludeDirectories { get; init; } = [];
public List<string> ExcludeMarkdownDirectories { get; init; } = [];
public List<string> ExcludeSnippetDirectories { get; init; } = [];
public List<string> ExcludeSnippetFiles { get; init; } = [];
public bool? WriteHeader { get; init; }
public string? Header { get; init; }
public string? UrlPrefix { get; init; }
public List<string> TocExcludes { get; init; } = [];
public bool? TreatMissingAsWarning { get; init; }
}
================================================
FILE: src/ConfigReader/ConfigReader.cs
================================================
public static class ConfigReader
{
public static (ConfigInput? config, string path) Read(string directory)
{
var exists = TryFindConfigFile(directory, out var path);
if (!exists)
{
return (null, path);
}
return (Parse(File.ReadAllText(path), path), path);
}
static bool TryFindConfigFile(string directory, out string path)
{
path = Path.Combine(directory, "mdsnippets.json");
var exists = File.Exists(path);
if (exists)
{
return true;
}
foreach (var subDirectory in Directory.EnumerateDirectories(directory))
{
path = Path.Combine(subDirectory, "mdsnippets.json");
exists = File.Exists(path);
if (exists)
{
return true;
}
}
path = "";
return false;
}
public static ConfigInput Parse(string contents, string path)
{
var config = DeSerialize(contents);
return new()
{
WriteHeader = config.WriteHeader,
ReadOnly = config.ReadOnly,
ValidateContent = config.ValidateContent,
OmitSnippetLinks = config.OmitSnippetLinks,
UrlsAsSnippets = config.UrlsAsSnippets,
ExcludeDirectories = config.ExcludeDirectories,
ExcludeMarkdownDirectories = config.ExcludeMarkdownDirectories,
ExcludeSnippetDirectories = config.ExcludeSnippetDirectories,
ExcludeSnippetFiles = config.ExcludeSnippetFiles,
Header = config.Header,
UrlPrefix = config.UrlPrefix,
TocExcludes = config.TocExcludes,
TocLevel = config.TocLevel,
MaxWidth = config.MaxWidth,
LinkFormat = GetLinkFormat(config.LinkFormat, path),
Convention = GetConvention(config.Convention, path),
TreatMissingAsWarning = config.TreatMissingAsWarning,
};
}
static ConfigSerialization DeSerialize(string contents)
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))
{
Position = 0
};
var serializer = new DataContractJsonSerializer(typeof(ConfigSerialization));
try
{
return (ConfigSerialization) serializer.ReadObject(stream)!;
}
catch (SerializationException exception)
{
throw new SnippetException(
$"""
Failed to deserialize configuration. Error: {exception.Message}.
Content:
{contents}
""");
}
}
static DocumentConvention? GetConvention(string? value, string path)
{
if (value == null)
{
return null;
}
if (Enum.TryParse<DocumentConvention>(value, out var convention))
{
return convention;
}
throw new ConfigurationException($"Failed to parse DocumentConvention: {convention}. FilePath: {path}.");
}
static LinkFormat? GetLinkFormat(string? value, string path)
{
if (value == null)
{
return null;
}
if (!Enum.TryParse<LinkFormat>(value, out var linkFormat))
{
throw new ConfigurationException($"Failed to parse LinkFormat: {linkFormat}. FilePath: {path}.");
}
return linkFormat;
}
}
================================================
FILE: src/ConfigReader/ConfigReader.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net48;net8.0;net9.0</TargetFrameworks>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MarkdownSnippets\MarkdownSnippets.csproj" />
<PackageReference Include="ProjectDefaults" PrivateAssets="all" />
</ItemGroup>
</Project>
================================================
FILE: src/ConfigReader/ConfigResult.cs
================================================
public class ConfigResult
{
public bool? ReadOnly { get; init; }
public bool ValidateContent { get; init; }
public bool OmitSnippetLinks { get; init; }
public LinkFormat LinkFormat { get; init; }
public DocumentConvention Convention { get; init; }
public int TocLevel { get; init; }
public int MaxWidth { get; init; }
public List<string>? UrlsAsSnippets { get; init; }
public List<string>? ExcludeDirectories { get; init; }
public List<string>? ExcludeMarkdownDirectories { get; init; }
public List<string>? ExcludeSnippetDirectories { get; init; }
public List<string>? ExcludeSnippetFiles { get; init; }
public bool? WriteHeader { get; init; }
public string? Header { get; init; }
public string? UrlPrefix { get; init; }
public List<string>? TocExcludes { get; init; }
public bool TreatMissingAsWarning { get; init; }
}
================================================
FILE: src/ConfigReader/ConfigSerialization.cs
================================================
public class ConfigSerialization
{
public bool? ReadOnly { get; set; }
public bool? ValidateContent { get; set; }
public bool? OmitSnippetLinks { get; set; }
public string? LinkFormat { get; set; }
public string? Convention { get; set; }
public bool? WriteHeader { get; set; }
public string? Header { get; set; }
public string? UrlPrefix { get; set; }
public int? TocLevel { get; set; }
public int? MaxWidth { get; set; }
public List<string> UrlsAsSnippets { get; set; } = [];
public List<string> ExcludeDirectories { get; set; } = [];
public List<string> ExcludeMarkdownDirectories { get; set; } = [];
public List<string> ExcludeSnippetDirectories { get; set; } = [];
public List<string> ExcludeSnippetFiles { get; set; } = [];
public List<string> TocExcludes { get; set; } = [];
public bool? TreatMissingAsWarning { get; set; }
}
================================================
FILE: src/ConfigReader/ConfigurationException.cs
================================================
class ConfigurationException(string message) :
Exception(message);
================================================
FILE: src/ConfigReader/ExcludeToFilterBuilder.cs
================================================
static class ExcludeToFilterBuilder
{
public static ShouldIncludeDirectory ExcludesToFilter(List<string>? excludes) =>
path =>
{
if (DefaultDirectoryExclusions.ShouldExcludeDirectory(path))
{
return false;
}
if(excludes == null ||
excludes.Count == 0)
{
return true;
}
if (!excludes.Any(path.Contains))
{
return true;
}
if (!excludes.Any(path.Replace('\\', '/').Contains))
{
return true;
}
if (!excludes.Any(path.Replace('/', '\\').Contains))
{
return true;
}
return false;
};
public static ShouldIncludeFile? FileExcludesToFilter(List<string>? excludes)
{
if (excludes == null ||
excludes.Count == 0)
{
return null;
}
var regexes = excludes
.Select(GlobToRegex)
.ToArray();
return path =>
{
var name = Path.GetFileName(path);
foreach (var regex in regexes)
{
if (regex.IsMatch(name))
{
return false;
}
}
return true;
};
}
static Regex GlobToRegex(string glob)
{
var builder = new StringBuilder("^");
foreach (var c in glob)
{
switch (c)
{
case '*':
builder.Append(".*");
break;
case '?':
builder.Append('.');
break;
default:
builder.Append(Regex.Escape(c.ToString()));
break;
}
}
builder.Append('$');
return new(builder.ToString(), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
}
================================================
FILE: src/ConfigReader/LogBuilder.cs
================================================
static class LogBuilder
{
public static string BuildConfigLogMessage(string targetDirectory, ConfigResult config, string configFilePath)
{
var builder = new StringBuilder(
$"""
Config:
TargetDirectory: {targetDirectory}
UrlPrefix: {config.UrlPrefix}
LinkFormat: {config.LinkFormat}
Convention: {config.Convention}
TocLevel: {config.TocLevel}
ValidateContent: {config.ValidateContent}
OmitSnippetLinks: {config.OmitSnippetLinks}
TreatMissingAsWarning: {config.TreatMissingAsWarning}
FileConfigPath: {configFilePath} (exists:{File.Exists(configFilePath)})
""");
if (config.Convention == DocumentConvention.SourceTransform)
{
var header = GetHeader(config);
Polyfill.AppendLine(
builder,
$"""
ReadOnly: {config.ReadOnly}
WriteHeader: {config.WriteHeader}
Header: {header}
""");
}
var maxWidth = config.MaxWidth;
if (maxWidth != int.MaxValue && maxWidth != 0)
{
Polyfill.AppendLine(
builder,
$" MaxWidth: {maxWidth}");
}
var excludeDirectories = config.ExcludeDirectories;
if (excludeDirectories != null && excludeDirectories.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
ExcludeDirectories:
{string.Join("\r\n ", excludeDirectories)}
""");
}
var excludeMarkdownDirectories = config.ExcludeMarkdownDirectories;
if (excludeMarkdownDirectories != null && excludeMarkdownDirectories.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
ExcludeMarkdownDirectories:
{string.Join("\r\n ", excludeMarkdownDirectories)}
""");
}
var excludeSnippetDirectories = config.ExcludeSnippetDirectories;
if (excludeSnippetDirectories != null && excludeSnippetDirectories.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
ExcludeSnippetDirectories:
{string.Join("\r\n ", excludeSnippetDirectories)}
""");
}
var excludeSnippetFiles = config.ExcludeSnippetFiles;
if (excludeSnippetFiles != null && excludeSnippetFiles.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
ExcludeSnippetFiles:
{string.Join("\r\n ", excludeSnippetFiles)}
""");
}
var tocExcludes = config.TocExcludes;
if (tocExcludes != null && tocExcludes.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
TocExcludes:
{string.Join("\r\n ", tocExcludes)}
""");
}
var urlsAsSnippets = config.UrlsAsSnippets;
if (urlsAsSnippets != null && urlsAsSnippets.Count != 0)
{
Polyfill.AppendLine(
builder,
$"""
UrlsAsSnippets:
{string.Join("\r\n ", urlsAsSnippets)}
""");
}
#if NET48
builder.AppendLine(" TargetFramework: net48");
#endif
#if NET9_0
builder.AppendLine(" TargetFramework: net9.0");
#endif
builder.TrimEnd();
return builder.ToString();
}
static string? GetHeader(ConfigResult config)
{
var header = config.Header;
if (header == null)
{
return null;
}
var newlineIndent = $"{Environment.NewLine} ";
header = string.Join(newlineIndent, header.Lines());
header = header.Replace(@"\n", newlineIndent);
return $"""
{header}
""";
}
}
================================================
FILE: src/ConfigReader/SharedGlobalUsings.cs
================================================
global using Polyfills;
global using MarkdownSnippets;
global using System.Runtime.Serialization;
global using System.Runtime.Serialization.Json;
global using System.Text;
global using System.Text.RegularExpressions;
================================================
FILE: src/ConfigReader.Tests/ConfigReader.Tests.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<NoWarn>$(NoWarn);xUnit1051</NoWarn>
<RootNamespace>testing</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ConfigReader\ConfigReader.csproj" />
<PackageReference Include="ProjectFiles" />
<PackageReference Include="Verify.DiffPlex" />
<PackageReference Include="Verify.XunitV3" />
<PackageReference Include="Argon" />
<PackageReference Include="DiffEngine" />
<PackageReference Include="EmptyFiles" />
<PackageReference Include="SimpleInfoName" />
<PackageReference Include="Verify" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="ProjectDefaults" PrivateAssets="all" />
<None Update="allConfig.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="SourceTransform.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="InPlaceOverwrite.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
================================================
FILE: src/ConfigReader.Tests/ConfigReaderTests.BadJson.verified.txt
================================================
{
Type: SnippetException,
Message:
Failed to deserialize configuration. Error: There was an error deserializing the object of type ConfigSerialization. Encountered unexpected character '"'..
Content:
{
"ValidateContent": true
"Convention": "InPlaceOverwrite"
},
StackTrace:
at ConfigReader.DeSerialize(String contents)
at ConfigReader.Parse(String contents, String path)
at ConfigReaderTests.<>c.<BadJson>b__1_0()
}
================================================
FILE: src/ConfigReader.Tests/ConfigReaderTests.Empty.verified.txt
================================================
{}
================================================
FILE: src/ConfigReader.Tests/ConfigReaderTests.Values.verified.txt
================================================
{
ReadOnly: false,
ValidateContent: true,
OmitSnippetLinks: true,
LinkFormat: Tfs,
Convention: InPlaceOverwrite,
TocLevel: 3,
MaxWidth: 80,
UrlsAsSnippets: [
Url1,
Url2
],
ExcludeDirectories: [
Dir1,
Dir2
],
ExcludeMarkdownDirectories: [
Dir2,
Dir3
],
ExcludeSnippetDirectories: [
Dir4,
Dir5
],
ExcludeSnippetFiles: [
*.verified.txt,
*.received.txt
],
WriteHeader: true,
Header: GENERATED FILE - Source File: {relativePath},
UrlPrefix: TheUrlPrefix,
TocExcludes: [
Exclude Heading1,
Exclude Heading2
],
TreatMissingAsWarning: true
}
================================================
FILE: src/ConfigReader.Tests/ConfigReaderTests.cs
================================================
public class ConfigReaderTests
{
[Fact]
public Task Empty()
{
var config = ConfigReader.Parse("{}", "filePath");
return Verify(config);
}
[Fact]
public Task BadJson() =>
Throws(() => ConfigReader.Parse(
"""
{
"ValidateContent": true
"Convention": "InPlaceOverwrite"
}
""",
"filePath"));
[Fact]
public Task Values()
{
var stream = File.ReadAllText("allConfig.json");
var config = ConfigReader.Parse(stream, "filePath");
return Verify(config);
}
[Fact]
public void FileExcludesToFilter_NullOrEmpty_ReturnsNull()
{
Assert.Null(ExcludeToFilterBuilder.FileExcludesToFilter(null));
Assert.Null(ExcludeToFilterBuilder.FileExcludesToFilter([]));
}
[Fact]
public void FileExcludesToFilter_GlobMatching()
{
var filter = ExcludeToFilterBuilder.FileExcludesToFilter(
[
"*.verified.txt",
"*.received.*",
"ignore?.cs"
])!;
Assert.False(filter("/a/b/Foo.verified.txt"));
Assert.False(filter(@"C:\x\y\Foo.received.md"));
Assert.False(filter("ignore1.cs"));
Assert.True(filter("Foo.cs"));
Assert.True(filter("Foo.txt"));
Assert.True(filter("ignoreAB.cs"));
}
[Fact]
public void FileExcludesToFilter_CaseInsensitive()
{
var filter = ExcludeToFilterBuilder.FileExcludesToFilter(["*.VERIFIED.txt"])!;
Assert.False(filter("foo.verified.TXT"));
}
}
================================================
FILE: src/ConfigReader.Tests/GlobalUsings.cs
================================================
global using VerifyTests.DiffPlex;
================================================
FILE: src/ConfigReader.Tests/InPlaceOverwrite.json
================================================
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"Convention": "InPlaceOverwrite"
}
================================================
FILE: src/ConfigReader.Tests/ModuleInitializer.cs
================================================
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Initialize() =>
VerifyDiffPlex.Initialize(OutputType.Compact);
}
================================================
FILE: src/ConfigReader.Tests/SourceTransform.json
================================================
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"Convention": "SourceTransform"
}
================================================
FILE: src/ConfigReader.Tests/allConfig.json
================================================
{
"$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json",
"ReadOnly": false,
"LinkFormat": "Tfs",
"TocLevel": 3,
"ExcludeDirectories": [ "Dir1", "Dir2" ],
"ExcludeMarkdownDirectories": [ "Dir2", "Dir3" ],
"ExcludeSnippetDirectories": [ "Dir4", "Dir5" ],
"ExcludeSnippetFiles": [ "*.verified.txt", "*.received.txt" ],
"UrlsAsSnippets": [ "Url1", "Url2" ],
"TocExcludes": [ "Exclude Heading1", "Exclude Heading2" ],
"Convention": "InPlaceOverwrite",
"WriteHeader": true,
"MaxWidth": 80,
"Header": "GENERATED FILE - Source File: {relativePath}",
"UrlPrefix": "TheUrlPrefix",
"TreatMissingAsWarning": true,
"ValidateContent": true,
"OmitSnippetLinks": true
}
================================================
FILE: src/Directory.Build.props
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<NoWarn>CS1591;NU1608;NU1109</NoWarn>
<Version>28.3.0</Version>
<LangVersion>preview</LangVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
<PackageTags>Markdown, Snippets, mdsnippets, documentation, MarkdownSnippets</PackageTags>
<Description>Extracts snippets from code files and merges them into markdown documents.</Description>
<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
<PolyStringInterpolation>true</PolyStringInterpolation>
<PackageReadmeFile>readme.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)nuget-readme.md" Pack="true" PackagePath="readme.md" />
<Using Include="System.ReadOnlySpan<System.Char>" Alias="CharSpan" />
</ItemGroup>
</Project>
================================================
FILE: src/Directory.Packages.props
================================================
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Argon" Version="0.33.5" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Microsoft.Build.Tasks.Core" Version="18.4.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.0" />
<PackageVersion Include="Microsoft.Sbom.Targets" Version="4.1.5" />
<PackageVersion Include="PackageShader.MsBuild" Version="1.1.0" />
<PackageVersion Include="Polyfill" Version="10.3.0" />
<PackageVersion Include="ProjectDefaults" Version="1.0.173" />
<PackageVersion Include="ProjectFiles" Version="1.1.1" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.7" />
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.6.1" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.7" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="Verify.DiffPlex" Version="3.1.2" />
<PackageVersion Include="Verify.XunitV3" Version="31.16.2" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="DiffEngine" Version="19.1.3" />
<PackageVersion Include="EmptyFiles" Version="8.18.1" />
<PackageVersion Include="SimpleInfoName" Version="3.2.0" />
<PackageVersion Include="Verify" Version="31.16.2" />
</ItemGroup>
</Project>
================================================
FILE: src/MarkdownSnippets/AssemblyInfo.cs
================================================
[assembly: InternalsVisibleTo("Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")]
[assembly: InternalsVisibleTo("mdsnippets, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")]
[assembly: InternalsVisibleTo("MarkdownSnippets.MsBuild, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")]
[assembly: InternalsVisibleTo("ConfigReader, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859fcd1deee68b96927c170783ced0c9a471a6424a0a011cfd31156a49dd73c4ad4a88b995fb918c0b43e0c005ef5fb72d53a328a64bde825cb5f2e4c53d66f69fcbb87d6737128b98e677a42091974b5f56093123a2dd6bc738af751b101d41c4f7a996e217b61967a3aa1ae7bc791d19c1cbeef47f0cdd20d288dff1a3")]
================================================
FILE: src/MarkdownSnippets/ContentValidation.cs
================================================
static class ContentValidation
{
static FrozenDictionary<string, string> phrases = FrozenDictionary.Create<string, string>([
new("a majority of ", "most"),
new("a number of", "some or many"),
new("at an early date", "soon"),
new("at the conclusion of", "after or following"),
new("at the present time", "now"),
new("at this point in time", "now"),
new("based on the fact that", "because or since"),
new("despite the fact that", "although"),
new("due to the fact that", "because"),
new("during the course of", "during"),
new("during the time that", "during or while"),
new("have the capability to", "can"),
new("in connection with", "about"),
new("in order to", "to"),
new("in regard to ", "regarding or about"),
new("in the event of", "if"),
new("in view of the fact that", "because"),
new("it is often the case that", "often"),
new("make reference to ", "refer to"),
new("of the opinion that", "think that "),
new("on a daily basis", "daily"),
new("on the grounds that", "because"),
new("prior to", "before"),
new("so as to", "to"),
new("subsequent to", "after"),
new("take into consideration", "consider"),
new("until such time as", "until"),
new("a lot", "many"),
new("sort of", "similar or approximately"),
new("kind of", "similar or approximately ")
]);
static FrozenSet<string> invalidWordSet = new[]
{
"you",
"we",
"our",
"your",
"us",
"please",
"yourself",
"just",
"simply",
"simple",
"easy",
"feel",
"think",
"above-mentioned",
"aforementioned",
"foregoing",
"henceforth",
"hereafter",
"heretofore",
"herewith",
"thereafter",
"thereof",
"therewith",
"whatsoever",
"whereat",
"wherein",
"whereof"
}.ToFrozenSet();
static FrozenDictionary<string, KeyValuePair<string, string>[]> phrasesByFirstWord =
phrases
.GroupBy(p =>
{
var spaceIndex = p.Key.IndexOf(' ');
return spaceIndex == -1 ? p.Key : p.Key[..spaceIndex];
})
.ToFrozenDictionary(g => g.Key, g => g.ToArray());
public static IEnumerable<(string error, int column)> Verify(string line)
{
if (line.StartsWith('>'))
{
yield break;
}
var cleanedLine = Clean(line);
var message = "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/. ";
var exclamationIndex1 = cleanedLine.IndexOf("! ");
if (exclamationIndex1 != -1)
{
yield return (message, exclamationIndex1);
}
// Tokenize words with positions
var words = Tokenize(cleanedLine);
// Check invalid words via set lookup (report first occurrence only)
var seenWords = new HashSet<string>();
foreach (var (word, start) in words)
{
if (invalidWordSet.Contains(word) && seenWords.Add(word))
{
yield return ($"Invalid word detected: '{word}'", start - 1);
}
}
// Check phrases via first-word lookup (report first occurrence only)
var seenPhrases = new HashSet<string>();
foreach (var (word, start) in words)
{
if (phrasesByFirstWord.TryGetValue(word, out var candidates))
{
foreach (var candidate in candidates)
{
if (seenPhrases.Contains(candidate.Key))
{
continue;
}
if (cleanedLine.AsSpan(start).StartsWith(candidate.Key.AsSpan(), StringComparison.Ordinal))
{
seenPhrases.Add(candidate.Key);
yield return ($"Invalid phrase detected: '{candidate.Key}'. Instead consider '{candidate.Value}'", start);
}
}
}
}
}
static List<(string word, int start)> Tokenize(string cleanedLine)
{
var words = new List<(string word, int start)>();
var span = cleanedLine.AsSpan();
var pos = 0;
while (pos < span.Length)
{
if (span[pos] == ' ')
{
pos++;
continue;
}
var wordStart = pos;
while (pos < span.Length && span[pos] != ' ')
{
pos++;
}
words.Add((span[wordStart..pos].ToString(), wordStart));
}
return words;
}
static string Clean(string input)
{
var length = input.Length + 2; // +2 for leading and trailing spaces
return string.Create(length, input, (span, source) =>
{
span[0] = ' ';
var index = 1;
foreach (var ch in source)
{
if (ch is '\'' or '?' or '.' or ',')
{
span[index++] = ' ';
}
else
{
span[index++] = char.ToLowerInvariant(ch);
}
}
span[index] = ' ';
});
}
}
================================================
FILE: src/MarkdownSnippets/ContentValidationException.cs
================================================
namespace MarkdownSnippets;
public class ContentValidationException(IReadOnlyList<ValidationError> errors) :
SnippetException(BuildMessage(errors))
{
public IReadOnlyList<ValidationError> Errors { get; } = errors;
static string BuildMessage(IReadOnlyList<ValidationError> errors)
{
var builder = new StringBuilder("Content validation errors:");
builder.AppendLine();
foreach (var error in errors)
{
if (error.File == null)
{
Polyfill.AppendLine(
builder,
$"""
{error.Error}
Line: {error.Line}
Column: {error.Column}
""");
}
Polyfill.AppendLine(
builder,
$"""
{error.Error}
File: {error.File}
Line: {error.Line}
Column: {error.Column}
""");
}
return builder.ToString();
}
public override string ToString() => Message;
}
================================================
FILE: src/MarkdownSnippets/Downloader/Downloader.cs
================================================
static class Downloader
{
static string cache = Path.Combine(Path.GetTempPath(), "MarkdownSnippets");
static Downloader()
{
Directory.CreateDirectory(cache);
foreach (var file in new DirectoryInfo(cache)
.GetFiles()
.OrderByDescending(_ => _.LastWriteTime)
.Skip(100))
{
file.Delete();
}
}
static HttpClient httpClient = new()
{
Timeout = TimeSpan.FromSeconds(30)
};
public static async Task<(bool success, string? path)> DownloadFile(string uri)
{
var file = Path.Combine(cache, FileNameFromUrl.ConvertToFileName(uri));
if (File.Exists(file))
{
var fileTimestamp = Timestamp.GetTimestamp(file);
if (fileTimestamp.Expiry > DateTime.UtcNow)
{
return (true, file);
}
}
Timestamp webTimeStamp;
using (var request = new HttpRequestMessage(HttpMethod.Head, uri))
{
using var headResponse = await httpClient.SendAsync(request);
if (headResponse.StatusCode != HttpStatusCode.OK)
{
return (false, null);
}
webTimeStamp = Timestamp.GetTimestamp(headResponse);
if (File.Exists(file))
{
var fileTimestamp = Timestamp.GetTimestamp(file);
if (fileTimestamp.LastModified == webTimeStamp.LastModified)
{
return (true, file);
}
File.Delete(file);
}
}
using var response = await httpClient.GetAsync(uri);
using var httpStream = await response.Content.ReadAsStreamAsync();
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))
{
await httpStream.CopyToAsync(fileStream);
}
webTimeStamp = Timestamp.GetTimestamp(response);
Timestamp.SetTimestamp(file, webTimeStamp);
return (true, file);
}
public static async Task<(bool success, string? content)> DownloadContent(string uri)
{
var (success, path) = await DownloadFile(uri);
if (success)
{
return (true, await File.ReadAllTextAsync(path!));
}
return (false, null);
}
}
================================================
FILE: src/MarkdownSnippets/Downloader/FileNameFromUrl.cs
================================================
static class FileNameFromUrl
{
static FrozenSet<char> invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToFrozenSet();
public static string ConvertToFileName(string url)
{
var builder = StringBuilderCache.Acquire(url.Length);
foreach (var ch in url)
{
if (invalid.Contains(ch))
{
builder.Append('_');
continue;
}
builder.Append(ch);
}
return StringBuilderCache.GetStringAndRelease(builder);
}
}
================================================
FILE: src/MarkdownSnippets/Downloader/Timestamp.cs
================================================
class Timestamp
{
static DateTime minFileDate = DateTime.FromFileTimeUtc(0);
public DateTime? Expiry;
public DateTime? LastModified;
public static Timestamp GetTimestamp(HttpResponseMessage headResponse)
{
var timestamp = new Timestamp();
var headers = headResponse.Content.Headers;
if (headers.LastModified != null)
{
timestamp.LastModified = headers.LastModified.Value.UtcDateTime;
}
if (headers.Expires != null)
{
timestamp.Expiry = headers.Expires.Value.UtcDateTime;
}
return timestamp;
}
public static void SetTimestamp(string path, Timestamp timestamp)
{
File.SetCreationTimeUtc(path, timestamp.LastModified.GetValueOrDefault(DateTime.UtcNow));
File.SetLastWriteTimeUtc(path, timestamp.Expiry.GetValueOrDefault(minFileDate));
}
public static Timestamp GetTimestamp(string path)
{
var timestamp = new Timestamp();
var creationTimeUtc = File.GetCreationTimeUtc(path);
if (creationTimeUtc != minFileDate)
{
timestamp.LastModified = creationTimeUtc;
}
var lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
if (lastWriteTimeUtc != minFileDate)
{
timestamp.Expiry = lastWriteTimeUtc;
}
return timestamp;
}
}
================================================
FILE: src/MarkdownSnippets/Extensions.cs
================================================
static class Extensions
{
public static bool TryFindNewline(this TextReader reader, [NotNullWhen(true)] out string? newline)
{
do
{
var c = reader.Read();
if (c == -1)
{
break;
}
if (c == '\r')
{
var peek = reader.Peek();
if (peek == -1)
{
newline = "\r";
return true;
}
if (peek == '\n')
{
newline = "\r\n";
return true;
}
newline = "\r";
return true;
}
if (c == '\n')
{
newline = "\n";
return true;
}
} while (true);
newline = null;
return false;
}
public static void TrimEnd(this StringBuilder builder)
{
var i = builder.Length - 1;
for (; i >= 0; i--)
{
if (!char.IsWhiteSpace(builder[i]))
{
break;
}
}
if (i < builder.Length - 1)
{
builder.Length = i + 1;
}
}
public static IReadOnlyList<T> ToReadonlyList<T>(this IEnumerable<T> value) => value.ToList();
public static int LineCount(this CharSpan input)
{
var count = 1;
var len = input.Length;
for (var i = 0; i != len; ++i)
{
switch (input[i])
{
case '\r':
++count;
if (i + 1 != len && input[i + 1] == '\n')
{
++i;
}
break;
case '\n':
++count;
break;
}
}
return count;
}
public static int LastIndexOfSequence(this CharSpan value, char c, int max)
{
var index = 0;
while (true)
{
if (index == max)
{
return index;
}
if (index == value.Length)
{
return index;
}
var ch = value[index];
if (c != ch)
{
return index;
}
index++;
}
}
public static CharSpan TrimBackCommentChars(this CharSpan input, int startIndex)
{
for (var index = input.Length - 1; index >= startIndex; index--)
{
var ch = input[index];
if (char.IsLetterOrDigit(ch) || ch is ']' or ' ' or ')')
{
return input[startIndex..(index + 1)];
}
}
return string.Empty;
}
public static string[] Lines(this string value) =>
value.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
public static bool IsWhiteSpace(this CharSpan target)
{
for (var i = 0; i < target.Length; i++)
{
if (!char.IsWhiteSpace(target[i]))
{
return false;
}
}
return true;
}
}
================================================
FILE: src/MarkdownSnippets/FileEx.cs
================================================
static class FileEx
{
public static string FixFileCapitalization(string file)
{
var fileName = Path.GetFileName(file);
var directory = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(directory))
{
directory = ".";
}
var filePaths = Directory.GetFiles(directory, fileName, SearchOption.TopDirectoryOnly);
if (filePaths.Length == 0)
{
throw new FileNotFoundException($"Could not find file: {file}");
}
return filePaths[0];
}
public static string GetRelativePath(string file, string directory)
{
var fileUri = new Uri(file);
// Folders must end in a slash
if (!directory.EndsWith(Path.DirectorySeparatorChar))
{
directory += Path.DirectorySeparatorChar;
}
var directoryUri = new Uri(directory);
return Uri.UnescapeDataString(directoryUri.MakeRelativeUri(fileUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}
public static string PrependSlash(string path)
{
if (path.StartsWith('/'))
{
return path;
}
return $"/{path}";
}
public static void ClearReadOnly(string path)
{
if (!File.Exists(path))
{
return;
}
var attributes = File.GetAttributes(path);
if ((attributes & FileAttributes.ReadOnly) != 0)
{
File.SetAttributes(path, attributes & ~FileAttributes.ReadOnly);
}
}
public static void MakeReadOnly(string path)
{
var attributes = File.GetAttributes(path);
File.SetAttributes(path, attributes | FileAttributes.ReadOnly);
}
}
================================================
FILE: src/MarkdownSnippets/GitRepoDirectoryFinder.cs
================================================
namespace MarkdownSnippets;
public static class GitRepoDirectoryFinder
{
public static string FindForFilePath([CallerFilePath] string sourceFilePath = "")
{
Guard.FileExists(sourceFilePath, nameof(sourceFilePath));
var directory = Path.GetDirectoryName(sourceFilePath)!;
return FindForDirectory(directory);
}
public static string FindForDirectory(string directory)
{
Guard.DirectoryExists(directory, nameof(directory));
if (TryFind(directory, out var path))
{
return path;
}
throw new("Could not find git repository directory");
}
static bool TryFind(string directory, [NotNullWhen(true)] out string? path)
{
if (TryFind(directory, ".git", out var targetDirectory))
{
path = targetDirectory;
return true;
}
if (TryFind(directory, ".gitignore", out targetDirectory))
{
path = targetDirectory;
return true;
}
path = null;
return false;
}
public static bool IsInGitRepository(string directory)
{
Guard.DirectoryExists(directory, nameof(directory));
return TryFind(directory, out _);
}
static bool TryFind(string directory, string suffix, [NotNullWhen(true)] out string? path)
{
Guard.DirectoryExists(directory, nameof(directory));
do
{
var combine = Path.Combine(directory, suffix);
if (Directory.Exists(combine) ||
File.Exists(combine))
{
path = directory;
return true;
}
var parent = Directory.GetParent(directory);
if (parent == null)
{
path = null;
return false;
}
directory = parent.FullName;
} while (true);
}
}
================================================
FILE: src/MarkdownSnippets/GlobalUsings.cs
================================================
global using System.Collections.Frozen;
global using System.Diagnostics.CodeAnalysis;
global using System.Net;
global using System.Net.Http;
global using System.Text.RegularExpressions;
global using MarkdownSnippets;
================================================
FILE: src/MarkdownSnippets/Guard.cs
================================================
static class Guard
{
public static void AgainstUpperCase(string value, string argumentName)
{
foreach (var c in value)
{
if (char.IsUpper(c))
{
throw new ArgumentException($"Cannot contain upper case. Value: {value}", argumentName);
}
}
}
public static void AgainstNegativeAndZero(int value, string argumentName)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(argumentName,value, "Zero or less");
}
}
public static void AgainstNegative(int value, string argumentName)
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(argumentName, value, "negative");
}
}
public static void AgainstNullAndEmpty(string? value, string argumentName)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentNullException(argumentName);
}
}
public static void DirectoryExists(string path, string argumentName)
{
AgainstNullAndEmpty(path, argumentName);
if (!Directory.Exists(path))
{
throw new ArgumentException($"Directory does not exist: {path}", argumentName);
}
}
public static void FileExists(string? path, string argumentName)
{
AgainstNullAndEmpty(path, argumentName);
if (!File.Exists(path))
{
throw new ArgumentException($"File does not exist: {path}", argumentName);
}
}
public static void AgainstEmpty(string? value, string argumentName)
{
if (value == null)
{
return;
}
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Cannot be only whitespace.", argumentName);
}
}
}
================================================
FILE: src/MarkdownSnippets/InterpretErrors.cs
================================================
namespace MarkdownSnippets;
/// <summary>
/// Extension method to convert various error cases.
/// </summary>
public static class InterpretErrors
{
/// <summary>
/// Converts <see cref="IEnumerable{Snippet}"/> to a markdown string.
/// </summary>
public static string ErrorsAsMarkdown(this IReadOnlyList<Snippet> snippets)
{
if (snippets.Count == 0)
{
return "";
}
var builder = StringBuilderCache.Acquire();
builder.AppendLine("## Snippet errors\r\n");
foreach (var error in snippets)
{
Polyfill.AppendLine(
builder,
$" * {error}");
}
builder.AppendLine();
return StringBuilderCache.GetStringAndRelease(builder);
}
/// <summary>
/// Converts <see cref="ProcessResult.MissingSnippets"/> to a markdown string.
/// </summary>
public static string ErrorsAsMarkdown(this ProcessResult processResult)
{
var builder = StringBuilderCache.Acquire();
var missingSnippets = processResult.MissingSnippets;
if (missingSnippets.Count != 0)
{
builder.Append(
"""
## Missing snippets
""");
foreach (var error in missingSnippets)
{
Polyfill.AppendLine(
builder,
$" * Key:'{error.Key}' Line:'{error.LineNumber}'");
}
}
//TODO: handle other errors
builder.AppendLine();
return StringBuilderCache.GetStringAndRelease(builder);
}
}
================================================
FILE: src/MarkdownSnippets/KeyValidator.cs
================================================
static class KeyValidator
{
public static bool IsValidKey(CharSpan key)
{
if (key.Length == 0)
{
return false;
}
if (!char.IsLetterOrDigit(key[0]))
{
return false;
}
if (!char.IsLetterOrDigit(key[^1]))
{
return false;
}
return true;
}
}
================================================
FILE: src/MarkdownSnippets/MarkdownProcessingException.cs
================================================
namespace MarkdownSnippets;
public class MarkdownProcessingException :
SnippetException
{
public string? File { get; }
public int LineNumber { get; }
public MarkdownProcessingException(string message, string? file, int lineNumber) :
base($"{message} File: {file}. LineNumber: {lineNumber}.")
{
Guard.AgainstNegativeAndZero(lineNumber, nameof(lineNumber));
Guard.AgainstEmpty(file, nameof(file));
File = file;
LineNumber = lineNumber;
}
public override string ToString() => Message;
}
================================================
FILE: src/MarkdownSnippets/MarkdownSnippets.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net48;net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Sbom.Targets" PrivateAssets="all" Condition="'$(CI)' == 'true'" />
<PackageReference Include="ProjectDefaults" PrivateAssets="all" />
<PackageReference Include="Polyfill" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Condition="'$(TargetFramework)' != 'net10.0'"/>
<PackageReference Include="System.Memory" Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard' OR '$(TargetFrameworkIdentifier)' == '.NETFramework' OR '$(TargetFrameworkIdentifier)' == '.NETCOREAPP'" />
<PackageReference Include="System.Net.Http" Condition="'$(TargetFramework)' == 'net48'" />
</ItemGroup>
</Project>
================================================
FILE: src/MarkdownSnippets/MissingIncludesException.cs
================================================
namespace MarkdownSnippets;
public class MissingIncludesException(IReadOnlyList<MissingInclude> missing) :
SnippetException($"Missing includes: {string.Join(", ", missing.Select(_ => _.Key))}")
{
public IReadOnlyList<MissingInclude> Missing { get; } = missing;
public override string ToString() => Message;
}
================================================
FILE: src/MarkdownSnippets/MissingSnippetsException.cs
================================================
namespace MarkdownSnippets;
public class MissingSnippetsException(IReadOnlyList<MissingSnippet> missing) :
SnippetException($"Missing snippets:{Environment.NewLine} {Report(missing)}")
{
public IReadOnlyList<MissingSnippet> Missing { get; } = missing;
static string Report(IReadOnlyList<MissingSnippet> missing) =>
string.Join(
$"{Environment.NewLine} ",
missing.GroupBy(_ => _.File ?? "file-unknown")
.Select(_ => $"{_.Key}: {string.Join(',', _.Select(_ => _.Key))}"));
public override string ToString() => Message;
}
================================================
FILE: src/MarkdownSnippets/NewLineConfigReader.cs
================================================
static class NewLineConfigReader
{
public static string ReadNewLine(string directory, IEnumerable<string> mdFiles)
{
var newLine = TryReadFromGitAttributes(directory);
if (newLine != null)
{
return newLine;
}
newLine = TryReadFromEditorConfig(directory);
if (newLine != null)
{
return newLine;
}
return DetectFromFiles(mdFiles);
}
static string DetectFromFiles(IEnumerable<string> mdFiles)
{
foreach (var mdFile in mdFiles.OrderBy(_ => _.Length))
{
using var reader = File.OpenText(mdFile);
if (reader.TryFindNewline(out var detectedNewLine))
{
return detectedNewLine;
}
}
return Environment.NewLine;
}
static string? TryReadFromGitAttributes(string directory)
{
var gitAttributesPath = FindFileUpward(directory, ".gitattributes");
if (gitAttributesPath == null)
{
return null;
}
var lines = File.ReadAllLines(gitAttributesPath);
return ParseGitAttributesEol(lines);
}
static string? ParseGitAttributesEol(string[] lines)
{
string? wildcardEol = null;
string? extensionEol = null;
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.Length == 0 || trimmed.StartsWith('#'))
{
continue;
}
var eolValue = ExtractGitAttributeEol(trimmed);
if (eolValue == null)
{
continue;
}
var pattern = GetGitAttributePattern(trimmed);
if (pattern == "*")
{
wildcardEol = eolValue;
}
else if (pattern is "*.md" or "*.MD")
{
extensionEol = eolValue;
}
}
// More specific pattern wins
var eol = extensionEol ?? wildcardEol;
return EolValueToNewLine(eol);
}
static string? ExtractGitAttributeEol(string line)
{
// Look for eol=lf or eol=crlf in the line
var eolIndex = line.IndexOf("eol=", StringComparison.OrdinalIgnoreCase);
if (eolIndex == -1)
{
return null;
}
var valueStart = eolIndex + 4;
var valueEnd = valueStart;
while (valueEnd < line.Length && !char.IsWhiteSpace(line[valueEnd]))
{
valueEnd++;
}
var value = line.AsSpan(valueStart, valueEnd - valueStart);
if (value.Equals("lf", StringComparison.OrdinalIgnoreCase))
{
return "lf";
}
if (value.Equals("crlf", StringComparison.OrdinalIgnoreCase))
{
return "crlf";
}
if (value.Equals("cr", StringComparison.OrdinalIgnoreCase))
{
return "cr";
}
return null;
}
static string GetGitAttributePattern(string line)
{
// Pattern is the first whitespace-delimited token
var end = 0;
while (end < line.Length && !char.IsWhiteSpace(line[end]))
{
end++;
}
return line[..end];
}
static string? TryReadFromEditorConfig(string directory)
{
var editorConfigPath = FindFileUpward(directory, ".editorconfig");
if (editorConfigPath == null)
{
return null;
}
var lines = File.ReadAllLines(editorConfigPath);
return ParseEditorConfigEol(lines);
}
static string? ParseEditorConfigEol(string[] lines)
{
string? globalEol = null;
string? extensionEol = null;
var inWildcardSection = false;
var inExtensionSection = false;
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.Length == 0 || trimmed.StartsWith('#') || trimmed.StartsWith(';'))
{
continue;
}
// Check for section headers
if (trimmed.StartsWith('[') && trimmed.EndsWith(']'))
{
var section = trimmed.AsSpan(1, trimmed.Length - 2);
inWildcardSection = section.SequenceEqual("*");
inExtensionSection = EditorConfigSectionMatchesMd(section);
continue;
}
// Parse key=value
var equalsIndex = trimmed.IndexOf('=');
if (equalsIndex == -1)
{
continue;
}
var key = trimmed[..equalsIndex].Trim().ToLowerInvariant();
var value = trimmed[(equalsIndex + 1)..].Trim().ToLowerInvariant();
if (key == "end_of_line")
{
if (inExtensionSection)
{
extensionEol = value;
}
else if (inWildcardSection)
{
globalEol = value;
}
}
}
// More specific section wins
var eol = extensionEol ?? globalEol;
return EolValueToNewLine(eol);
}
static bool EditorConfigSectionMatchesMd(CharSpan section)
{
// Handle patterns like *.md, *.{md,txt}, etc.
if (section.Length == 0 || section[0] != '*')
{
return false;
}
var pattern = section[1..];
if (pattern.Equals(".md", StringComparison.OrdinalIgnoreCase))
{
return true;
}
// Handle {md,txt} style patterns like *.{md,txt}
if (pattern.Length > 0 && pattern[0] == '.')
{
var braceStart = pattern.IndexOf('{');
var braceEnd = pattern.IndexOf('}');
if (braceStart != -1 && braceEnd > braceStart)
{
var extensionsSpan = pattern.Slice(braceStart + 1, braceEnd - braceStart - 1);
foreach (var range in extensionsSpan.Split(','))
{
var ext = extensionsSpan[range].Trim();
if (ext.Equals("md", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
return false;
}
static string? EolValueToNewLine(string? eolValue) =>
eolValue switch
{
"lf" => "\n",
"crlf" => "\r\n",
"cr" => "\r",
_ => null
};
static string? FindFileUpward(string directory, string fileName)
{
var current = directory;
while (current != null)
{
var filePath = Path.Combine(current, fileName);
if (File.Exists(filePath))
{
return filePath;
}
var parent = Directory.GetParent(current);
current = parent?.FullName;
}
return null;
}
}
================================================
FILE: src/MarkdownSnippets/Paths.cs
================================================
static class Paths
{
public static bool IsMdFile(this string value) =>
value.EndsWith(".md", StringComparison.OrdinalIgnoreCase) ||
value.EndsWith(".mdx", StringComparison.OrdinalIgnoreCase);
public static bool IsSourceMdFile(this string value) =>
value.EndsWith(".source.md", StringComparison.OrdinalIgnoreCase) ||
value.EndsWith(".source.mdx", StringComparison.OrdinalIgnoreCase);
public static bool IsIncludeMdFile(this string value) =>
value.EndsWith(".include.md", StringComparison.OrdinalIgnoreCase);
}
================================================
FILE: src/MarkdownSnippets/Processing/AppendSnippetsToMarkdown.cs
================================================
namespace MarkdownSnippets;
public delegate void AppendSnippetsToMarkdown(string key, IEnumerable<Snippet> snippets, Action<string> appendLine);
================================================
FILE: src/MarkdownSnippets/Processing/DirectoryMarkdownProcessor.cs
================================================
namespace MarkdownSnippets;
public class DirectoryMarkdownProcessor
{
DocumentConvention convention;
bool writeHeader;
bool validateContent;
string? header;
bool readOnly;
int tocLevel;
IEnumerable<string>? tocExcludes;
Action<string> log;
string targetDirectory;
List<string> mdFiles = [];
List<Include> includes = [];
List<Snippet> snippets = [];
public IReadOnlyList<Snippet> Snippets => snippets;
List<string> snippetSourceFiles = [];
AppendSnippetsToMarkdown appendSnippets;
bool treatMissingAsWarning;
string newLine;
List<string> allFiles;
public DirectoryMarkdownProcessor(
string targetDirectory,
ShouldIncludeDirectory directoryIncludes,
ShouldIncludeDirectory markdownDirectoryIncludes,
ShouldIncludeDirectory snippetDirectoryIncludes,
DocumentConvention convention = DocumentConvention.SourceTransform,
bool scanForMdFiles = true,
bool scanForSnippets = true,
bool scanForIncludes = true,
Action<string>? log = null,
bool? writeHeader = null,
string? header = null,
bool? readOnly = null,
LinkFormat linkFormat = LinkFormat.GitHub,
int tocLevel = 2,
IEnumerable<string>? tocExcludes = null,
bool treatMissingAsWarning = false,
int maxWidth = int.MaxValue,
string? urlPrefix = null,
bool validateContent = false,
string? newLine = null,
bool omitSnippetLinks = false,
ShouldIncludeFile? snippetFileIncludes = null) :
this(
targetDirectory,
new SnippetMarkdownHandling(targetDirectory, linkFormat, omitSnippetLinks, urlPrefix).Append,
directoryIncludes,
markdownDirectoryIncludes,
snippetDirectoryIncludes,
convention,
scanForMdFiles: scanForMdFiles,
scanForSnippets: scanForSnippets,
scanForIncludes: scanForIncludes,
log: log,
writeHeader: writeHeader,
header: header,
readOnly: readOnly,
tocLevel: tocLevel,
tocExcludes: tocExcludes,
treatMissingAsWarning: treatMissingAsWarning,
maxWidth: maxWidth,
validateContent: validateContent,
newLine: newLine,
snippetFileIncludes: snippetFileIncludes)
{
}
public DirectoryMarkdownProcessor(
string targetDirectory,
AppendSnippetsToMarkdown appendSnippets,
ShouldIncludeDirectory directoryIncludes,
ShouldIncludeDirectory markdownDirectoryIncludes,
ShouldIncludeDirectory snippetDirectoryIncludes,
DocumentConvention convention = DocumentConvention.SourceTransform,
bool scanForMdFiles = true,
bool scanForSnippets = true,
bool scanForIncludes = true,
Action<string>? log = null,
bool? writeHeader = null,
string? header = null,
bool? readOnly = null,
int tocLevel = 2,
IEnumerable<string>? tocExcludes = null,
bool treatMissingAsWarning = false,
int maxWidth = int.MaxValue,
bool validateContent = false,
string? newLine = null,
ShouldIncludeFile? snippetFileIncludes = null)
{
this.appendSnippets = appendSnippets;
this.convention = convention;
this.writeHeader = writeHeader.GetValueOrDefault(convention == DocumentConvention.SourceTransform);
this.readOnly = readOnly.GetValueOrDefault(false);
this.validateContent = validateContent;
this.header = header;
this.tocLevel = tocLevel;
this.tocExcludes = tocExcludes;
this.newLine = newLine!;
this.treatMissingAsWarning = treatMissingAsWarning;
this.log = log ?? (_ => Trace.WriteLine(_));
Guard.DirectoryExists(targetDirectory, nameof(targetDirectory));
this.targetDirectory = Path.GetFullPath(targetDirectory);
var fileFinder = new FileFinder(targetDirectory, convention, directoryIncludes, markdownDirectoryIncludes, snippetDirectoryIncludes, snippetFileIncludes);
var (snippetFiles, mdFiles, includeFiles, allFiles) = fileFinder.FindFiles();
this.allFiles = allFiles;
if (scanForMdFiles)
{
this.mdFiles.AddRange(mdFiles);
}
if (scanForSnippets)
{
InitNewLine();
if (snippetFiles.Count != 0)
{
snippetSourceFiles.AddRange(snippetFiles);
this.log($"Found {snippetFiles.Count} files for snippets");
}
var stopwatch = Stopwatch.StartNew();
var read = FileSnippetExtractor.Read(snippetFiles, maxWidth, this.newLine).ToList();
if (read.Count != 0)
{
snippets.AddRange(read);
this.log($"Added {read.Count} snippets ({stopwatch.ElapsedMilliseconds}ms)");
}
}
if (scanForIncludes)
{
var includeKeys = new HashSet<string>();
foreach (var file in includeFiles)
{
var key = Path.GetFileName(file).Replace(".include.md", "");
if (!includeKeys.Add(key))
{
throw new($"Duplicate include: {key}");
}
includes.Add(Include.Build(key, File.ReadAllLines(file), file));
}
}
}
[MemberNotNull(nameof(newLine))]
void InitNewLine()
{
if (newLine != null)
{
return;
}
newLine = NewLineConfigReader.ReadNewLine(targetDirectory, mdFiles);
}
public void AddSnippets(List<Snippet> snippets)
{
var files = snippets
.Where(_ => _.Path != null)
.Select(_ => _.Path!)
.Distinct()
.ToList();
if (files.Count != 0)
{
snippetSourceFiles.AddRange(files);
}
if (snippets.Count != 0)
{
this.snippets.AddRange(snippets);
}
}
public void AddSnippets(params Snippet[] snippets) =>
AddSnippets(snippets.ToList());
public void AddMdFiles(params string[] files)
{
foreach (var file in files)
{
mdFiles.Add(file);
}
}
public void Run()
{
if (mdFiles.Count == 0)
{
if (convention == DocumentConvention.InPlaceOverwrite)
{
throw new SnippetException("No markdown files found.");
}
throw new SnippetException(
$$"""
No markdown files found. This may be due to the DocumentConvention being SourceTransform.
See https://github.com/SimonCropp/MarkdownSnippets#document-convention
To move to InPlaceOverwrite add a file named `mdsnippets.json` in the target directory ({{targetDirectory}}) that contains:
{
"Convention": "InPlaceOverwrite"
}
""");
}
foreach (var group in snippets.GroupBy(_ => _.Path))
{
if (group.Key == null)
{
log("Snippets added with no path");
}
else
{
log($"Snippets extracted from {group.Key}");
}
foreach (var snippet in group)
{
log($"\t{snippet.Key}");
}
}
var stopwatch = Stopwatch.StartNew();
var processor = new MarkdownProcessor(
convention,
Snippets.ToDictionary(),
includes,
appendSnippets,
snippetSourceFiles,
allFiles,
tocLevel,
writeHeader,
targetDirectory,
validateContent,
header,
tocExcludes,
newLine);
foreach (var sourceFile in mdFiles)
{
ProcessFile(sourceFile, processor);
}
log($"MarkdownProcessor Finished. {stopwatch.ElapsedMilliseconds}ms");
}
void ProcessFile(string sourceFile, MarkdownProcessor markdownProcessor)
{
string targetFile;
if (convention == DocumentConvention.SourceTransform)
{
targetFile = TargetFileForSourceTransform(sourceFile, targetDirectory);
}
else
{
targetFile = sourceFile;
}
var lines = ReadLines(sourceFile);
FileEx.ClearReadOnly(targetFile);
var relativeSource = sourceFile[targetDirectory.Length..]
.Replace('\\', '/');
var result = markdownProcessor.Apply(lines, newLine, relativeSource);
var missingSnippets = result.MissingSnippets;
if (missingSnippets.Count != 0)
{
// If the config value is set to treat missing snippets as warnings, then don't throw
if (treatMissingAsWarning)
{
foreach (var missing in missingSnippets)
{
log($"WARN: The source file:{missing.File} includes a key {missing.Key}, however the snippet is missing. Make sure that the snippet is defined.");
}
}
else
{
throw new MissingSnippetsException(missingSnippets);
}
}
var missingIncludes = result.MissingIncludes;
if (missingIncludes.Count != 0)
{
// If the config value is set to treat missing include as warnings, then don't throw
if (treatMissingAsWarning)
{
foreach (var missing in missingIncludes)
{
log($"WARN: The source file:{missing.File} includes a key {missing.Key}, however the include is missing. Make sure that the include is defined.");
}
}
else
{
throw new MissingIncludesException(missingIncludes);
}
}
var errors = result.ValidationErrors;
if (errors.Count != 0)
{
throw new ContentValidationException(errors);
}
WriteLines(targetFile, lines);
if (readOnly)
{
FileEx.MakeReadOnly(targetFile);
}
}
void WriteLines(string target, List<Line> lines)
{
var directoryName = Path.GetDirectoryName(target)!;
Directory.CreateDirectory(directoryName);
using var writer = File.CreateText(target);
writer.NewLine = newLine;
foreach (var line in lines)
{
writer.WriteLine(line.Current);
}
}
static List<Line> ReadLines(string sourceFile)
{
using var reader = File.OpenText(sourceFile);
return Lines.ReadAllLines(reader, sourceFile).ToList();
}
static string TargetFileForSourceTransform(string sourceFile, string targetDirectory)
{
var relativePath = FileEx.GetRelativePath(sourceFile, targetDirectory);
var filtered = relativePath.Split(Path.DirectorySeparatorChar)
.Where(_ => !string.Equals(_, "mdsource", StringComparison.OrdinalIgnoreCase))
.ToArray();
var sourceTrimmed = Path.Combine(filtered);
var targetFile = Path.Combine(targetDirectory, sourceTrimmed);
// remove ".md" from ".source.md" then change ".source" to ".md"
targetFile = targetFile.Replace(".source.", ".");
return targetFile;
}
}
================================================
FILE: src/MarkdownSnippets/Processing/DocumentConvention.cs
================================================
namespace MarkdownSnippets;
public enum DocumentConvention
{
SourceTransform,
InPlaceOverwrite
}
================================================
FILE: src/MarkdownSnippets/Processing/HeaderWriter.cs
================================================
static class HeaderWriter
{
static string[] defaultHeaderLines;
internal const string DefaultHeader =
"""
GENERATED FILE - DO NOT EDIT
This file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSnippets).
Source File: {relativePath}
To change this file edit the source file and then run MarkdownSnippets.
""";
static HeaderWriter() =>
defaultHeaderLines = DefaultHeader.Lines();
public static string WriteHeader(string relativePath, string? header, string newline)
{
var lines = Header(header);
var inner = string.Join(newline, lines)
.Replace("{relativePath}", relativePath)
.Replace(@"\n", newline);
return $"<!--{newline}{inner}{newline}-->{newline}";
}
static string[] separator = ["\r\n", "\r", "\n", @"\n"];
static string[] Header(string? header)
{
if (header == null)
{
return defaultHeaderLines;
}
if (header.Contains("<!--") ||
header.Contains("-->"))
{
throw new SnippetException("Header cannot contain `<!--` or `-->`.");
}
return header.Split(separator, StringSplitOptions.None);
}
}
================================================
FILE: src/MarkdownSnippets/Processing/IncludeProcessor.cs
================================================
class IncludeProcessor
{
DocumentConvention convention;
Dictionary<string, Include> includesLookup;
IReadOnlyDictionary<string, IReadOnlyList<Snippet>> snippets;
IReadOnlyList<string> allFiles;
string targetDirectory;
public IncludeProcessor(
DocumentConvention convention,
IReadOnlyList<Include> includes,
IReadOnlyDictionary<string, IReadOnlyList<Snippet>> snippets,
string targetDirectory,
IReadOnlyList<string> allFiles)
{
targetDirectory = Path.GetFullPath(targetDirectory);
this.targetDirectory = targetDirectory.Replace('\\', '/');
this.convention = convention;
includesLookup = includes.ToDictionary(_ => _.Key);
this.snippets = snippets;
this.allFiles = allFiles;
}
public bool TryProcessInclude(List<Line> lines, Line line, ICollection<Include> used, int index, List<MissingInclude> missing, string? relativePath)
{
var current = line.Current;
if (current.StartsWith("include: "))
{
var includeKey = line.Current[9..];
Inner(lines, line, used, index, missing, includeKey, relativePath);
return true;
}
if (convention == DocumentConvention.SourceTransform)
{
return false;
}
static string GetIncludeKey(CharSpan substring)
{
var indexOfDotPath = substring.IndexOf(". path:", StringComparison.Ordinal);
if (indexOfDotPath != -1)
{
return substring[..indexOfDotPath].ToString();
}
return substring[..^4].ToString();
}
var indexSingleLineInclude = current.IndexOf("<!-- singleLineInclude: ", StringComparison.Ordinal);
if (indexSingleLineInclude > 0)
{
var substring = current.AsSpan()[(indexSingleLineInclude + 24)..];
var includeKey = GetIncludeKey(substring);
Inner(lines, line, used, index, missing, includeKey, relativePath);
return true;
}
if (current.StartsWith("<!-- include: ", StringComparison.Ordinal))
{
var substring = current.AsSpan()[14..];
var includeKey = GetIncludeKey(substring);
lines.RemoveUntil(index + 1, "<!-- endInclude -->", line.Path, line);
Inner(lines, line, used, index, missing, includeKey, relativePath);
return true;
}
var indexOfInclude = current.IndexOf("<!-- include: ", StringComparison.Ordinal);
if (indexOfInclude > 0)
{
var substring = current.AsSpan()[(indexOfInclude + 14)..];
var includeKey = GetIncludeKey(substring);
lines.RemoveUntil(index + 1, "<!-- endInclude -->", line.Path, line);
Inner(lines, line, used, index, missing, includeKey, relativePath);
return true;
}
return false;
}
void Inner(List<Line> lines, Line line, ICollection<Include> used, int index, List<MissingInclude> missing, string includeKey, string? relativePath)
{
if (includesLookup.Try
gitextract_lnivcw9c/
├── .claude/
│ └── settings.local.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ ├── merge-dependabot.yml
│ └── milestone-release.yml
├── .gitignore
├── claude.md
├── code_of_conduct.md
├── docs/
│ ├── api.md
│ ├── config-file.md
│ ├── exclusion.md
│ ├── github-action.md
│ ├── header.md
│ ├── includes.md
│ ├── indentation.md
│ ├── max-width.md
│ ├── mdsource/
│ │ ├── api.source.md
│ │ ├── config-file.source.md
│ │ ├── doc-index.include.md
│ │ ├── exclusion.source.md
│ │ ├── github-action.source.md
│ │ ├── header.source.md
│ │ ├── includes.source.md
│ │ ├── indentation.source.md
│ │ ├── max-width.source.md
│ │ ├── msbuild.source.md
│ │ ├── readme.source.md
│ │ ├── toc/
│ │ │ ├── tocAfter.txt
│ │ │ └── tocBefore.txt
│ │ └── toc.source.md
│ ├── msbuild.md
│ ├── on-push-do-docs.yml
│ ├── readme.md
│ └── toc.md
├── license.txt
├── readme.md
├── readme.source.md
├── schema.json
└── src/
├── Benchmarks/
│ ├── Benchmarks.csproj
│ ├── FullRenderBenchmarks.cs
│ └── Program.cs
├── ConfigReader/
│ ├── AssemblyInfo.cs
│ ├── ConfigDefaults.cs
│ ├── ConfigInput.cs
│ ├── ConfigReader.cs
│ ├── ConfigReader.csproj
│ ├── ConfigResult.cs
│ ├── ConfigSerialization.cs
│ ├── ConfigurationException.cs
│ ├── ExcludeToFilterBuilder.cs
│ ├── LogBuilder.cs
│ └── SharedGlobalUsings.cs
├── ConfigReader.Tests/
│ ├── ConfigReader.Tests.csproj
│ ├── ConfigReaderTests.BadJson.verified.txt
│ ├── ConfigReaderTests.Empty.verified.txt
│ ├── ConfigReaderTests.Values.verified.txt
│ ├── ConfigReaderTests.cs
│ ├── GlobalUsings.cs
│ ├── InPlaceOverwrite.json
│ ├── ModuleInitializer.cs
│ ├── SourceTransform.json
│ └── allConfig.json
├── Directory.Build.props
├── Directory.Packages.props
├── MarkdownSnippets/
│ ├── AssemblyInfo.cs
│ ├── ContentValidation.cs
│ ├── ContentValidationException.cs
│ ├── Downloader/
│ │ ├── Downloader.cs
│ │ ├── FileNameFromUrl.cs
│ │ └── Timestamp.cs
│ ├── Extensions.cs
│ ├── FileEx.cs
│ ├── GitRepoDirectoryFinder.cs
│ ├── GlobalUsings.cs
│ ├── Guard.cs
│ ├── InterpretErrors.cs
│ ├── KeyValidator.cs
│ ├── MarkdownProcessingException.cs
│ ├── MarkdownSnippets.csproj
│ ├── MissingIncludesException.cs
│ ├── MissingSnippetsException.cs
│ ├── NewLineConfigReader.cs
│ ├── Paths.cs
│ ├── Processing/
│ │ ├── AppendSnippetsToMarkdown.cs
│ │ ├── DirectoryMarkdownProcessor.cs
│ │ ├── DocumentConvention.cs
│ │ ├── HeaderWriter.cs
│ │ ├── IncludeProcessor.cs
│ │ ├── Line.cs
│ │ ├── Lines.cs
│ │ ├── LinkFormat.cs
│ │ ├── Markdown.cs
│ │ ├── MarkdownProcessor.cs
│ │ ├── MissingInclude.cs
│ │ ├── MissingSnippet.cs
│ │ ├── ProcessResult.cs
│ │ ├── RelativeFile.cs
│ │ ├── SimpleSnippetMarkdownHandling.cs
│ │ ├── SnippetKey.cs
│ │ ├── SnippetMarkdownHandling.cs
│ │ ├── TocBuilder.cs
│ │ └── ValidationError.cs
│ ├── Reading/
│ │ ├── EndFunc.cs
│ │ ├── Exclusions/
│ │ │ ├── DefaultDirectoryExclusions.cs
│ │ │ └── SnippetFileExclusions.cs
│ │ ├── FileFinder.cs
│ │ ├── FileSnippetExtractor.cs
│ │ ├── IContent.cs
│ │ ├── Include.cs
│ │ ├── LineTooLongException.cs
│ │ ├── LoopStack.cs
│ │ ├── LoopState.cs
│ │ ├── ReadSnippets.cs
│ │ ├── ShouldIncludeDirectory.cs
│ │ ├── ShouldIncludeFile.cs
│ │ ├── Snippet.cs
│ │ └── StartEndTester.cs
│ ├── SnippetException.cs
│ ├── SnippetExtensions.cs
│ ├── SnippetReadingException.cs
│ └── StringBuilderCache.cs
├── MarkdownSnippets.MsBuild/
│ ├── DocoTask.cs
│ ├── LoggingHelper.cs
│ ├── MarkdownSnippets.MsBuild.csproj
│ └── MarkdownSnippets.MsBuild.targets
├── MarkdownSnippets.Tool/
│ ├── AssemblyInfo.cs
│ ├── CommandLineException.cs
│ ├── CommandRunner.cs
│ ├── GlobalUsings.cs
│ ├── Invoke.cs
│ ├── MarkdownSnippets.Tool.csproj
│ ├── Options.cs
│ └── Program.cs
├── MarkdownSnippets.Tool.Tests/
│ ├── CommandRunnerTests.ConventionLong.verified.txt
│ ├── CommandRunnerTests.ConventionShort.verified.txt
│ ├── CommandRunnerTests.Empty.verified.txt
│ ├── CommandRunnerTests.ExcludeLong.verified.txt
│ ├── CommandRunnerTests.ExcludeMarkdownDirectoriesLong.verified.txt
│ ├── CommandRunnerTests.ExcludeMultiple.verified.txt
│ ├── CommandRunnerTests.ExcludeShort.verified.txt
│ ├── CommandRunnerTests.ExcludeSnippetDirectoriesLong.verified.txt
│ ├── CommandRunnerTests.Header.verified.txt
│ ├── CommandRunnerTests.LinkFormatLong.verified.txt
│ ├── CommandRunnerTests.LinkFormatShort.verified.txt
│ ├── CommandRunnerTests.MaxWidthLong.verified.txt
│ ├── CommandRunnerTests.OmitSnippetLinks.verified.txt
│ ├── CommandRunnerTests.ReadOnlyLong.verified.txt
│ ├── CommandRunnerTests.ReadOnlyShort.verified.txt
│ ├── CommandRunnerTests.SingleUnNamedArg.verified.txt
│ ├── CommandRunnerTests.TargetDirectoryLong.verified.txt
│ ├── CommandRunnerTests.TargetDirectoryShort.verified.txt
│ ├── CommandRunnerTests.TocLevelLong.verified.txt
│ ├── CommandRunnerTests.UrlPrefix.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsLong.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsMultiple.verified.txt
│ ├── CommandRunnerTests.UrlsAsSnippetsShort.verified.txt
│ ├── CommandRunnerTests.ValidateContentLong.verified.txt
│ ├── CommandRunnerTests.ValidateContentShort.verified.txt
│ ├── CommandRunnerTests.VerifyContentLong.verified.txt
│ ├── CommandRunnerTests.VerifyContentShort.verified.txt
│ ├── CommandRunnerTests.WriteHeader.verified.txt
│ ├── CommandRunnerTests.cs
│ ├── GlobalUsings.cs
│ ├── LogBuilderTests.BuildConfigLogMessage.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessage.DotNet9_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageMinimal.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageMinimal.DotNet9_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet10_0.verified.txt
│ ├── LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet9_0.verified.txt
│ ├── LogBuilderTests.cs
│ ├── MarkdownSnippets.Tool.Tests.csproj
│ └── ModuleInitializer.cs
├── MarkdownSnippets.slnx
├── MarkdownSnippets.slnx.DotSettings
├── Shared.sln.DotSettings
├── Tests/
│ ├── ContentValidationTest.CheckInvalidWord.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordSentenceEnd.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordSentenceStart.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordStringEnd.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordWithComma.verified.txt
│ ├── ContentValidationTest.CheckInvalidWordWithQuestionMark.verified.txt
│ ├── ContentValidationTest.cs
│ ├── DirectoryMarkdownProcessor/
│ │ ├── BinaryFileSnippet/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.dot
│ │ ├── Convention/
│ │ │ ├── mdsource/
│ │ │ │ └── two.source.md
│ │ │ └── one.source.md
│ │ ├── ConventionWithNestedDir/
│ │ │ └── mdsource/
│ │ │ └── Nested/
│ │ │ └── one.source.md
│ │ ├── ExplicitFileInclude/
│ │ │ ├── Nested/
│ │ │ │ ├── fileToInclude3.txt
│ │ │ │ ├── fileToInclude4.txt
│ │ │ │ ├── fileToInclude5.txt
│ │ │ │ └── fileToInclude6.txt
│ │ │ ├── fileToInclude1.txt
│ │ │ ├── fileToInclude2.txt
│ │ │ └── one.source.md
│ │ ├── ExplicitFileIncludeWithMergedSnippet/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── ExplicitFileIncludeWithSnippetAtEnd/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── FileSnippet/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.txt
│ │ ├── FileSnippetMissing/
│ │ │ └── one.source.md
│ │ ├── FileSnippetWithHash/
│ │ │ ├── one.source.md
│ │ │ └── source#File.txt
│ │ ├── FileSnippetWithWhiteSpace/
│ │ │ ├── one.source.md
│ │ │ └── sourceFile.txt
│ │ ├── InPlaceOverwriteExists/
│ │ │ ├── file.md
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteExistsMdx/
│ │ │ ├── file.mdx
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteNotExists/
│ │ │ ├── file.md
│ │ │ ├── fileToInclude.txt
│ │ │ ├── includeWithCode.txt
│ │ │ └── multiLineFileToInclude.txt
│ │ ├── InPlaceOverwriteUrlInclude/
│ │ │ └── one.md
│ │ ├── InPlaceOverwriteUrlSnippet/
│ │ │ └── one.md
│ │ ├── InPlaceOverwriteWithFileSnippetMissing/
│ │ │ └── file.md
│ │ ├── Mdx/
│ │ │ ├── one.source.mdx
│ │ │ └── sourceFile.txt
│ │ ├── MissingInclude/
│ │ │ ├── one.md
│ │ │ └── one.source.md
│ │ ├── MixedCaseInclude/
│ │ │ ├── fileToInclude.txt
│ │ │ └── one.source.md
│ │ ├── NonMd/
│ │ │ ├── mdsource/
│ │ │ │ └── two.source.txt
│ │ │ └── one.source.txt
│ │ ├── ReadOnly/
│ │ │ └── one.source.md
│ │ ├── UrlInclude/
│ │ │ └── one.source.md
│ │ ├── UrlIncludeMissing/
│ │ │ └── one.source.md
│ │ ├── UrlSnippet/
│ │ │ └── one.source.md
│ │ ├── UrlSnippetMissing/
│ │ │ └── one.source.md
│ │ └── ValidationErrors/
│ │ ├── one.md
│ │ └── one.source.md
│ ├── DirectoryMarkdownProcessorTests.BinaryFileSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.Convention.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ConventionWithNestedDir.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithMergedSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ExplicitFileIncludeWithSnippetAtEnd.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetExplicitIncludeBypassesExcludeSnippetFiles.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetWithHash.verified.txt
│ ├── DirectoryMarkdownProcessorTests.FileSnippetWithWhiteSpace.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExists.verified.md
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteExistsMdx.verified.mdx
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteNotExists.verified.md
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteUrlSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.InPlaceOverwriteWithFileSnippetMissing.verified.md
│ ├── DirectoryMarkdownProcessorTests.Mdx.verified.txt
│ ├── DirectoryMarkdownProcessorTests.MixedCaseInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlInclude.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlIncludeMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlSnippet.verified.txt
│ ├── DirectoryMarkdownProcessorTests.UrlSnippetMissing.verified.txt
│ ├── DirectoryMarkdownProcessorTests.ValidationErrors.verified.txt
│ ├── DirectoryMarkdownProcessorTests.cs
│ ├── DirectorySnippetExtractor/
│ │ ├── Case/
│ │ │ ├── code1.txt
│ │ │ └── code2.txt
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ └── code.txt
│ │ ├── Simple/
│ │ │ ├── code1.txt
│ │ │ ├── code2.txt
│ │ │ ├── code3.txt
│ │ │ └── code4.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── code4.txt
│ ├── DownloaderTests.Valid.verified.txt
│ ├── DownloaderTests.cs
│ ├── FileExTests.cs
│ ├── FileToUseAsSnippet.txt
│ ├── GirRepoDirectoryFinderTests.cs
│ ├── GitDirs/
│ │ ├── NoRef/
│ │ │ └── HEAD
│ │ └── WithRef/
│ │ ├── HEAD
│ │ └── refs/
│ │ └── heads/
│ │ └── master
│ ├── GlobalUsings.cs
│ ├── HeaderWriterTests.DefaultHeader.verified.txt
│ ├── HeaderWriterTests.WriteHeaderDefaultHeader.verified.txt
│ ├── HeaderWriterTests.WriteHeaderHeaderCustom.verified.txt
│ ├── HeaderWriterTests.cs
│ ├── IncludeFileFinder/
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ ├── file.include.md
│ │ │ └── other.txt
│ │ ├── Simple/
│ │ │ ├── file1.include.md
│ │ │ ├── file2.include.md
│ │ │ └── other.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── file.include.md
│ ├── IncludeFinder/
│ │ └── file.include.md
│ ├── IndexReaderTests.cs
│ ├── LoopState/
│ │ ├── LoopStateTests.ExcludeEmptyPaddingLines.verified.txt
│ │ ├── LoopStateTests.TrimIndentation.verified.txt
│ │ ├── LoopStateTests.TrimIndentation_no_initial_padding.verified.txt
│ │ ├── LoopStateTests.TrimIndentation_with_mis_match.verified.txt
│ │ └── LoopStateTests.cs
│ ├── MarkdownProcessor/
│ │ ├── MarkdownProcessorTests.Empty_snippet_key.verified.txt
│ │ ├── MarkdownProcessorTests.MissingInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Missing_endInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Missing_endToc.verified.txt
│ │ ├── MarkdownProcessorTests.MixedNewlinesInFile.verified.txt
│ │ ├── MarkdownProcessorTests.Simple.verified.txt
│ │ ├── MarkdownProcessorTests.Simple_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.SkipHeadingBeforeToc.verified.txt
│ │ ├── MarkdownProcessorTests.SnippetInInclude.verified.txt
│ │ ├── MarkdownProcessorTests.SnippetInIncludeLast.verified.txt
│ │ ├── MarkdownProcessorTests.TableInInclude.verified.txt
│ │ ├── MarkdownProcessorTests.Toc.verified.txt
│ │ ├── MarkdownProcessorTests.Toc1.verified.txt
│ │ ├── MarkdownProcessorTests.TocRetainedIfNoHeadingsInFile.verified.txt
│ │ ├── MarkdownProcessorTests.Toc_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.Whitespace_snippet_key.verified.txt
│ │ ├── MarkdownProcessorTests.WithCommentWebSnippetUpdate.verified.txt
│ │ ├── MarkdownProcessorTests.WithCommentWebSnippetWithViewUrl.verified.txt
│ │ ├── MarkdownProcessorTests.WithDoubleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithEmptyMultiLineInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithEmptyMultipleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedCommentSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedMultiLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedSnippetMultipleSpaces.verified.txt
│ │ ├── MarkdownProcessorTests.WithIndentedWebSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithInlineWebSnippetWithViewUrl.verified.txt
│ │ ├── MarkdownProcessorTests.WithMixedCaseInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithMixedCaseSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultiLineInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultiLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithMultipleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleInclude.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleInclude_Overwrite.verified.txt
│ │ ├── MarkdownProcessorTests.WithSingleSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithTabIndentedSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WithTwoLineSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.WrongNewlineInSnippet.verified.txt
│ │ ├── MarkdownProcessorTests.cs
│ │ ├── SnippetKey_ExtractStartCommentSnippet.cs
│ │ ├── SnippetKey_ExtractStartCommentWebSnippet.cs
│ │ ├── SnippetKey_ExtractTransform.cs
│ │ ├── TocBuilderTests.Deep.verified.txt
│ │ ├── TocBuilderTests.DuplicateNested.verified.txt
│ │ ├── TocBuilderTests.Duplicates.verified.txt
│ │ ├── TocBuilderTests.EmptyHeading.verified.txt
│ │ ├── TocBuilderTests.Exclude.verified.txt
│ │ ├── TocBuilderTests.IgnoreTop.verified.txt
│ │ ├── TocBuilderTests.Nested.verified.txt
│ │ ├── TocBuilderTests.SanitizeLink.verified.txt
│ │ ├── TocBuilderTests.Single.verified.txt
│ │ ├── TocBuilderTests.StopAtLevel.verified.txt
│ │ ├── TocBuilderTests.StripMarkdown.verified.txt
│ │ ├── TocBuilderTests.WithSpaces.verified.txt
│ │ └── TocBuilderTests.cs
│ ├── ModuleInitializer.cs
│ ├── MsBuildIntegrationTests.cs
│ ├── NewLineConfigReaderTests.cs
│ ├── PathsTests.cs
│ ├── ProcessResultConverter.cs
│ ├── SimpleSnippetMarkdownHandlingTests.Append.verified.txt
│ ├── SimpleSnippetMarkdownHandlingTests.ExpressiveCode.verified.txt
│ ├── SimpleSnippetMarkdownHandlingTests.cs
│ ├── SnippetConverter.cs
│ ├── SnippetExtensionsTests.ToDictionary.verified.txt
│ ├── SnippetExtensionsTests.ToDictionary_SameKey.verified.txt
│ ├── SnippetExtensionsTests.cs
│ ├── SnippetExtractor/
│ │ ├── SnippetExtractorTests.AppendFileAsSnippet.verified.txt
│ │ ├── SnippetExtractorTests.AppendUrlAsSnippet.verified.txt
│ │ ├── SnippetExtractorTests.AppendUrlAsSnippetInline.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractFromRegion.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractFromXml.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithExpressiveCode.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithInnerWhiteSpace.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithMissingSpaces.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithNoTrailingCharacters.verified.txt
│ │ ├── SnippetExtractorTests.CanExtractWithTrailingWhitespace.verified.txt
│ │ ├── SnippetExtractorTests.CanReadFileWhileLockedByAnotherProcess.verified.txt
│ │ ├── SnippetExtractorTests.LanguageOverride.verified.txt
│ │ ├── SnippetExtractorTests.LanguageOverrideWithExpressiveCode.verified.txt
│ │ ├── SnippetExtractorTests.MixedNewLines.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.TooWide.verified.txt
│ │ ├── SnippetExtractorTests.UnClosedRegion.verified.txt
│ │ ├── SnippetExtractorTests.UnClosedSnippet.verified.txt
│ │ └── SnippetExtractorTests.cs
│ ├── SnippetFileFinder/
│ │ ├── Nested/
│ │ │ └── nested/
│ │ │ └── nested/
│ │ │ └── code.txt
│ │ ├── Simple/
│ │ │ ├── code1.txt
│ │ │ ├── code2.txt
│ │ │ ├── code3.txt
│ │ │ └── code4.txt
│ │ └── VerifyLambdasAreCalled/
│ │ └── subpath/
│ │ └── code4.txt
│ ├── SnippetFileFinderTests.ExcludeSnippetFiles.verified.txt
│ ├── SnippetFileFinderTests.Nested.verified.txt
│ ├── SnippetFileFinderTests.Simple.verified.txt
│ ├── SnippetFileFinderTests.VerifyLambdasAreCalled.verified.txt
│ ├── SnippetFileFinderTests.cs
│ ├── SnippetMarkdownHandlingTests.Append.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendHashed.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendOmitSnippetLinks.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendOmitSourceLink.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendPrefixed.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendWebSnippet.verified.txt
│ ├── SnippetMarkdownHandlingTests.AppendWebSnippetWithViewUrl.verified.txt
│ ├── SnippetMarkdownHandlingTests.cs
│ ├── SnippetVerifier.cs
│ ├── Snippets/
│ │ └── Usage.cs
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForEmptyLanguageValue.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForInvalidLanguageValue.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyEndingWithSymbol.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForKeyStartingWithSymbol.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.ShouldThrowForNoKey.verified.txt
│ ├── StartEndTester_IsBeginSnippetTests.cs
│ ├── StartEndTester_IsStartRegionTests.cs
│ ├── Tests.csproj
│ ├── WebSnippetTests.cs
│ └── badsnippets/
│ └── code.txt
├── appveyor.yml
├── context-menu.reg
├── global.json
├── key.snk
├── mdsnippets.json
├── nuget-readme.md
└── nuget.config
SYMBOL INDEX (613 symbols across 98 files)
FILE: src/Benchmarks/FullRenderBenchmarks.cs
class FullRenderBenchmarks (line 8) | [MemoryDiagnoser]
method DirectoryFilter (line 15) | static bool DirectoryFilter(string path) =>
method SnippetDirectoryFilter (line 20) | static bool SnippetDirectoryFilter(string path) =>
method Setup (line 24) | [GlobalSetup]
method FullRepoRender (line 58) | [Benchmark(Description = "Full repo render")]
method FileDiscoveryAndSnippetExtraction (line 74) | [Benchmark(Description = "File discovery + snippet extraction")]
method MarkdownProcessorApply (line 87) | [Benchmark(Description = "MarkdownProcessor.Apply (50 snippets)")]
FILE: src/ConfigReader.Tests/ConfigReaderTests.cs
class ConfigReaderTests (line 1) | public class ConfigReaderTests
method Empty (line 3) | [Fact]
method BadJson (line 11) | [Fact]
method Values (line 22) | [Fact]
method FileExcludesToFilter_NullOrEmpty_ReturnsNull (line 30) | [Fact]
method FileExcludesToFilter_GlobMatching (line 37) | [Fact]
method FileExcludesToFilter_CaseInsensitive (line 55) | [Fact]
FILE: src/ConfigReader.Tests/ModuleInitializer.cs
class ModuleInitializer (line 1) | public static class ModuleInitializer
method Initialize (line 3) | [ModuleInitializer]
FILE: src/ConfigReader/ConfigDefaults.cs
class ConfigDefaults (line 1) | public static class ConfigDefaults
method Convert (line 3) | public static ConfigResult Convert(ConfigInput? fileConfig, ConfigInpu...
method JoinLists (line 55) | static List<string> JoinLists(List<string> list1, List<string> list2) =>
method GetValueOrDefault (line 61) | static T GetValueOrDefault<T>(string name, T? input, T? config, T defa...
method GetValueOrNull (line 82) | static T? GetValueOrNull<T>(string name, T? input, T? config)
method GetValueOrDefault (line 99) | static string? GetValueOrDefault(string name, string? input, string? c...
FILE: src/ConfigReader/ConfigInput.cs
class ConfigInput (line 1) | public class ConfigInput
FILE: src/ConfigReader/ConfigReader.cs
class ConfigReader (line 1) | public static class ConfigReader
method Read (line 3) | public static (ConfigInput? config, string path) Read(string directory)
method TryFindConfigFile (line 14) | static bool TryFindConfigFile(string directory, out string path)
method Parse (line 37) | public static ConfigInput Parse(string contents, string path)
method DeSerialize (line 63) | static ConfigSerialization DeSerialize(string contents)
method GetConvention (line 85) | static DocumentConvention? GetConvention(string? value, string path)
method GetLinkFormat (line 100) | static LinkFormat? GetLinkFormat(string? value, string path)
FILE: src/ConfigReader/ConfigResult.cs
class ConfigResult (line 1) | public class ConfigResult
FILE: src/ConfigReader/ConfigSerialization.cs
class ConfigSerialization (line 1) | public class ConfigSerialization
FILE: src/ConfigReader/ConfigurationException.cs
class ConfigurationException (line 1) | class ConfigurationException(string message) :
FILE: src/ConfigReader/ExcludeToFilterBuilder.cs
class ExcludeToFilterBuilder (line 1) | static class ExcludeToFilterBuilder
method ExcludesToFilter (line 3) | public static ShouldIncludeDirectory ExcludesToFilter(List<string>? ex...
method FileExcludesToFilter (line 35) | public static ShouldIncludeFile? FileExcludesToFilter(List<string>? ex...
method GlobToRegex (line 62) | static Regex GlobToRegex(string glob)
FILE: src/ConfigReader/LogBuilder.cs
class LogBuilder (line 1) | static class LogBuilder
method BuildConfigLogMessage (line 3) | public static string BuildConfigLogMessage(string targetDirectory, Con...
method GetHeader (line 116) | static string? GetHeader(ConfigResult config)
FILE: src/MarkdownSnippets.MsBuild/DocoTask.cs
class DocoTask (line 6) | public class DocoTask :
method Execute (line 29) | public override bool Execute()
method Cancel (line 167) | public void Cancel()
FILE: src/MarkdownSnippets.MsBuild/LoggingHelper.cs
class LoggingHelper (line 3) | static class LoggingHelper
method LogFileError (line 5) | public static void LogFileError(this TaskLoggingHelper loggingHelper, ...
FILE: src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.cs
class CommandRunnerTests (line 1) | public class CommandRunnerTests
method Empty (line 6) | [Fact]
method SingleUnNamedArg (line 13) | [Fact]
method Header (line 20) | [Fact]
method UrlPrefix (line 27) | [Fact]
method WriteHeader (line 34) | [Fact]
method OmitSnippetLinks (line 41) | [Fact]
method ValidateContentShort (line 48) | [Fact]
method ValidateContentLong (line 55) | [Fact]
method ConventionShort (line 62) | [Fact]
method ConventionLong (line 69) | [Fact]
method ReadOnlyShort (line 76) | [Fact]
method ReadOnlyLong (line 83) | [Fact]
method LinkFormatShort (line 90) | [Fact]
method LinkFormatLong (line 97) | [Fact]
method TargetDirectoryShort (line 104) | [Fact]
method TargetDirectoryLong (line 111) | [Fact]
method MaxWidthLong (line 118) | [Fact]
method TocLevelLong (line 125) | [Fact]
method ExcludeShort (line 132) | [Fact]
method ExcludeMultiple (line 139) | [Fact]
method ExcludeDuplicates (line 146) | [Fact]
method ExcludeWhitespace (line 150) | [Fact]
method ExcludeLong (line 154) | [Fact]
method ExcludeMarkdownDirectoriesLong (line 161) | [Fact]
method ExcludeSnippetDirectoriesLong (line 168) | [Fact]
method UrlsAsSnippetsShort (line 175) | [Fact]
method UrlsAsSnippetsMultiple (line 182) | [Fact]
method UrlsAsSnippetsDuplicates (line 189) | [Fact]
method UrlsAsSnippetsWhitespace (line 193) | [Fact]
method UrlsAsSnippetsLong (line 196) | [Fact]
method Capture (line 203) | Task Capture(string targetDirectory, ConfigInput configInput)
method VerifyResult (line 210) | Task VerifyResult() =>
FILE: src/MarkdownSnippets.Tool.Tests/LogBuilderTests.cs
class LogBuilderTests (line 1) | public class LogBuilderTests
method BuildConfigLogMessage (line 3) | [Fact]
method BuildConfigLogMessageSourceTransform (line 28) | [Fact]
method BuildConfigLogMessageMinimal (line 53) | [Fact]
FILE: src/MarkdownSnippets.Tool.Tests/ModuleInitializer.cs
class ModuleInitializer (line 1) | public static class ModuleInitializer
method Initialize (line 3) | [ModuleInitializer]
FILE: src/MarkdownSnippets.Tool/CommandLineException.cs
class CommandLineException (line 1) | class CommandLineException(string message) :
FILE: src/MarkdownSnippets.Tool/CommandRunner.cs
class CommandRunner (line 1) | static class CommandRunner
method RunCommand (line 3) | public static Task RunCommand(Invoke invoke, params string[] args)
method ValidateAndApplyDefaults (line 42) | static void ValidateAndApplyDefaults(Options options)
method ValidateItems (line 88) | static void ValidateItems(string name, IList<string> items)
FILE: src/MarkdownSnippets.Tool/Options.cs
class Options (line 1) | public class Options
FILE: src/MarkdownSnippets/ContentValidation.cs
class ContentValidation (line 1) | static class ContentValidation
method Verify (line 76) | public static IEnumerable<(string error, int column)> Verify(string line)
method Tokenize (line 128) | static List<(string word, int start)> Tokenize(string cleanedLine)
method Clean (line 153) | static string Clean(string input)
FILE: src/MarkdownSnippets/ContentValidationException.cs
class ContentValidationException (line 3) | public class ContentValidationException(IReadOnlyList<ValidationError> e...
method BuildMessage (line 8) | static string BuildMessage(IReadOnlyList<ValidationError> errors)
method ToString (line 38) | public override string ToString() => Message;
FILE: src/MarkdownSnippets/Downloader/Downloader.cs
class Downloader (line 1) | static class Downloader
method Downloader (line 5) | static Downloader()
method DownloadFile (line 22) | public static async Task<(bool success, string? path)> DownloadFile(st...
method DownloadContent (line 71) | public static async Task<(bool success, string? content)> DownloadCont...
FILE: src/MarkdownSnippets/Downloader/FileNameFromUrl.cs
class FileNameFromUrl (line 1) | static class FileNameFromUrl
method ConvertToFileName (line 5) | public static string ConvertToFileName(string url)
FILE: src/MarkdownSnippets/Downloader/Timestamp.cs
class Timestamp (line 1) | class Timestamp
method GetTimestamp (line 7) | public static Timestamp GetTimestamp(HttpResponseMessage headResponse)
method SetTimestamp (line 24) | public static void SetTimestamp(string path, Timestamp timestamp)
method GetTimestamp (line 30) | public static Timestamp GetTimestamp(string path)
FILE: src/MarkdownSnippets/Extensions.cs
class Extensions (line 1) | static class Extensions
method TryFindNewline (line 3) | public static bool TryFindNewline(this TextReader reader, [NotNullWhen...
method TrimEnd (line 44) | public static void TrimEnd(this StringBuilder builder)
method ToReadonlyList (line 61) | public static IReadOnlyList<T> ToReadonlyList<T>(this IEnumerable<T> v...
method LineCount (line 63) | public static int LineCount(this CharSpan input)
method LastIndexOfSequence (line 88) | public static int LastIndexOfSequence(this CharSpan value, char c, int...
method TrimBackCommentChars (line 113) | public static CharSpan TrimBackCommentChars(this CharSpan input, int s...
method Lines (line 127) | public static string[] Lines(this string value) =>
method IsWhiteSpace (line 130) | public static bool IsWhiteSpace(this CharSpan target)
FILE: src/MarkdownSnippets/FileEx.cs
class FileEx (line 1) | static class FileEx
method FixFileCapitalization (line 3) | public static string FixFileCapitalization(string file)
method GetRelativePath (line 21) | public static string GetRelativePath(string file, string directory)
method PrependSlash (line 34) | public static string PrependSlash(string path)
method ClearReadOnly (line 44) | public static void ClearReadOnly(string path)
method MakeReadOnly (line 58) | public static void MakeReadOnly(string path)
FILE: src/MarkdownSnippets/GitRepoDirectoryFinder.cs
class GitRepoDirectoryFinder (line 3) | public static class GitRepoDirectoryFinder
method FindForFilePath (line 5) | public static string FindForFilePath([CallerFilePath] string sourceFil...
method FindForDirectory (line 12) | public static string FindForDirectory(string directory)
method TryFind (line 23) | static bool TryFind(string directory, [NotNullWhen(true)] out string? ...
method IsInGitRepository (line 41) | public static bool IsInGitRepository(string directory)
method TryFind (line 47) | static bool TryFind(string directory, string suffix, [NotNullWhen(true...
FILE: src/MarkdownSnippets/Guard.cs
class Guard (line 1) | static class Guard
method AgainstUpperCase (line 3) | public static void AgainstUpperCase(string value, string argumentName)
method AgainstNegativeAndZero (line 14) | public static void AgainstNegativeAndZero(int value, string argumentName)
method AgainstNegative (line 22) | public static void AgainstNegative(int value, string argumentName)
method AgainstNullAndEmpty (line 30) | public static void AgainstNullAndEmpty(string? value, string argumentN...
method DirectoryExists (line 38) | public static void DirectoryExists(string path, string argumentName)
method FileExists (line 47) | public static void FileExists(string? path, string argumentName)
method AgainstEmpty (line 56) | public static void AgainstEmpty(string? value, string argumentName)
FILE: src/MarkdownSnippets/InterpretErrors.cs
class InterpretErrors (line 6) | public static class InterpretErrors
method ErrorsAsMarkdown (line 11) | public static string ErrorsAsMarkdown(this IReadOnlyList<Snippet> snip...
method ErrorsAsMarkdown (line 34) | public static string ErrorsAsMarkdown(this ProcessResult processResult)
FILE: src/MarkdownSnippets/KeyValidator.cs
class KeyValidator (line 1) | static class KeyValidator
method IsValidKey (line 3) | public static bool IsValidKey(CharSpan key)
FILE: src/MarkdownSnippets/MarkdownProcessingException.cs
class MarkdownProcessingException (line 3) | public class MarkdownProcessingException :
method MarkdownProcessingException (line 9) | public MarkdownProcessingException(string message, string? file, int l...
method ToString (line 18) | public override string ToString() => Message;
FILE: src/MarkdownSnippets/MissingIncludesException.cs
class MissingIncludesException (line 3) | public class MissingIncludesException(IReadOnlyList<MissingInclude> miss...
method ToString (line 8) | public override string ToString() => Message;
FILE: src/MarkdownSnippets/MissingSnippetsException.cs
class MissingSnippetsException (line 3) | public class MissingSnippetsException(IReadOnlyList<MissingSnippet> miss...
method Report (line 8) | static string Report(IReadOnlyList<MissingSnippet> missing) =>
method ToString (line 14) | public override string ToString() => Message;
FILE: src/MarkdownSnippets/NewLineConfigReader.cs
class NewLineConfigReader (line 1) | static class NewLineConfigReader
method ReadNewLine (line 3) | public static string ReadNewLine(string directory, IEnumerable<string>...
method DetectFromFiles (line 20) | static string DetectFromFiles(IEnumerable<string> mdFiles)
method TryReadFromGitAttributes (line 34) | static string? TryReadFromGitAttributes(string directory)
method ParseGitAttributesEol (line 46) | static string? ParseGitAttributesEol(string[] lines)
method ExtractGitAttributeEol (line 81) | static string? ExtractGitAttributeEol(string line)
method GetGitAttributePattern (line 116) | static string GetGitAttributePattern(string line)
method TryReadFromEditorConfig (line 128) | static string? TryReadFromEditorConfig(string directory)
method ParseEditorConfigEol (line 140) | static string? ParseEditorConfigEol(string[] lines)
method EditorConfigSectionMatchesMd (line 192) | static bool EditorConfigSectionMatchesMd(CharSpan section)
method EolValueToNewLine (line 228) | static string? EolValueToNewLine(string? eolValue) =>
method FindFileUpward (line 237) | static string? FindFileUpward(string directory, string fileName)
FILE: src/MarkdownSnippets/Paths.cs
class Paths (line 1) | static class Paths
method IsMdFile (line 3) | public static bool IsMdFile(this string value) =>
method IsSourceMdFile (line 7) | public static bool IsSourceMdFile(this string value) =>
method IsIncludeMdFile (line 11) | public static bool IsIncludeMdFile(this string value) =>
FILE: src/MarkdownSnippets/Processing/DirectoryMarkdownProcessor.cs
class DirectoryMarkdownProcessor (line 3) | public class DirectoryMarkdownProcessor
method DirectoryMarkdownProcessor (line 24) | public DirectoryMarkdownProcessor(
method DirectoryMarkdownProcessor (line 71) | public DirectoryMarkdownProcessor(
method InitNewLine (line 152) | [MemberNotNull(nameof(newLine))]
method AddSnippets (line 163) | public void AddSnippets(List<Snippet> snippets)
method AddSnippets (line 181) | public void AddSnippets(params Snippet[] snippets) =>
method AddMdFiles (line 184) | public void AddMdFiles(params string[] files)
method Run (line 192) | public void Run()
method ProcessFile (line 252) | void ProcessFile(string sourceFile, MarkdownProcessor markdownProcessor)
method WriteLines (line 320) | void WriteLines(string target, List<Line> lines)
method ReadLines (line 332) | static List<Line> ReadLines(string sourceFile)
method TargetFileForSourceTransform (line 338) | static string TargetFileForSourceTransform(string sourceFile, string t...
FILE: src/MarkdownSnippets/Processing/DocumentConvention.cs
type DocumentConvention (line 3) | public enum DocumentConvention
FILE: src/MarkdownSnippets/Processing/HeaderWriter.cs
class HeaderWriter (line 1) | static class HeaderWriter
method HeaderWriter (line 13) | static HeaderWriter() =>
method WriteHeader (line 16) | public static string WriteHeader(string relativePath, string? header, ...
method Header (line 27) | static string[] Header(string? header)
FILE: src/MarkdownSnippets/Processing/IncludeProcessor.cs
class IncludeProcessor (line 1) | class IncludeProcessor
method IncludeProcessor (line 9) | public IncludeProcessor(
method TryProcessInclude (line 24) | public bool TryProcessInclude(List<Line> lines, Line line, ICollection...
method Inner (line 82) | void Inner(List<Line> lines, Line line, ICollection<Include> used, int...
method AddInclude (line 128) | void AddInclude(List<Line> lines, Line line, ICollection<Include> used...
method BuildIncludes (line 140) | IEnumerable<Line> BuildIncludes(Line line, Include include, bool write...
method BuildMultiple (line 158) | static IEnumerable<Line> BuildMultiple(Line line, string? path, Includ...
method ShouldWriteIncludeOnDiffLine (line 207) | static bool ShouldWriteIncludeOnDiffLine(string line) =>
method BuildEmpty (line 214) | static IEnumerable<Line> BuildEmpty(Line line, string? path, Include i...
method BuildSingle (line 226) | static IEnumerable<Line> BuildSingle(Line line, string? path, Include ...
method GetPath (line 257) | string? GetPath(IContent include)
FILE: src/MarkdownSnippets/Processing/Line.cs
class Line (line 1) | [DebuggerDisplay("Line={LineNumber}, Original={Original}, Current={Curre...
method Line (line 4) | public Line(string original, string? path, int lineNumber)
method WithCurrent (line 13) | public Line WithCurrent(string current) =>
method ToString (line 21) | public override string ToString() =>
method GetLeadingWhitespace (line 44) | static string GetLeadingWhitespace(string text)
FILE: src/MarkdownSnippets/Processing/Lines.cs
class Lines (line 1) | static class Lines
method RemoveUntil (line 3) | public static void RemoveUntil(
method ReadAllLines (line 25) | public static IEnumerable<Line> ReadAllLines(TextReader textReader, st...
FILE: src/MarkdownSnippets/Processing/LinkFormat.cs
type LinkFormat (line 3) | public enum LinkFormat
FILE: src/MarkdownSnippets/Processing/Markdown.cs
class Markdown (line 1) | static class Markdown
method StripMarkdown (line 5) | public static string StripMarkdown(string input)
FILE: src/MarkdownSnippets/Processing/MarkdownProcessor.cs
class MarkdownProcessor (line 6) | public class MarkdownProcessor
method MarkdownProcessor (line 30) | public MarkdownProcessor(
method Apply (line 83) | public string Apply(string input, string? file = null)
method Apply (line 109) | public ProcessResult Apply(TextReader textReader, TextWriter writer, s...
method Apply (line 127) | internal ProcessResult Apply(List<Line> lines, string newLine, string?...
method ValidateContent (line 272) | bool ValidateContent(string? relativePath, Line line, List<ValidationE...
method ProcessSnippetLine (line 295) | void ProcessSnippetLine(Action<string> appendLine, List<MissingSnippet...
method ProcessWebSnippetLine (line 315) | void ProcessWebSnippetLine(Action<string> appendLine, List<MissingSnip...
method TryGetSnippets (line 376) | bool TryGetSnippets(
method FilesToSnippets (line 395) | bool FilesToSnippets(
method SnippetsForFile (line 423) | List<Snippet> SnippetsForFile(string key, string relativeToRoot) =>
method GetForHttp (line 426) | bool GetForHttp(string key, out IReadOnlyList<Snippet> snippetsForKey)
method FileToSnippet (line 439) | Snippet FileToSnippet(string key, string file, string? path)
method ReadNonStartEndLines (line 458) | (string text, int lineCount) ReadNonStartEndLines(string file)
FILE: src/MarkdownSnippets/Processing/MissingInclude.cs
class MissingInclude (line 6) | [DebuggerDisplay("Key={Key}, Line={LineNumber}")]
method MissingInclude (line 12) | public MissingInclude(string key, int lineNumber, string? file)
method ToString (line 37) | public override string ToString()
FILE: src/MarkdownSnippets/Processing/MissingSnippet.cs
class MissingSnippet (line 6) | [DebuggerDisplay("Key={Key}, Line={LineNumber}")]
method MissingSnippet (line 12) | public MissingSnippet(string key, int lineNumber, string? file)
method ToString (line 37) | public override string ToString()
FILE: src/MarkdownSnippets/Processing/ProcessResult.cs
class ProcessResult (line 6) | public class ProcessResult(
method GetEnumerator (line 27) | public virtual IEnumerator<Snippet> GetEnumerator()
method GetEnumerator (line 47) | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
FILE: src/MarkdownSnippets/Processing/RelativeFile.cs
class RelativeFile (line 1) | static class RelativeFile
method InnerFind (line 3) | static bool InnerFind(IReadOnlyList<string> allFiles, string targetDir...
method Find (line 66) | public static bool Find(
FILE: src/MarkdownSnippets/Processing/SimpleSnippetMarkdownHandling.cs
class SimpleSnippetMarkdownHandling (line 6) | public static class SimpleSnippetMarkdownHandling
method Append (line 8) | public static void Append(string key, IEnumerable<Snippet> snippets, A...
method WriteSnippet (line 16) | static void WriteSnippet(Action<string> appendLine, Snippet snippet)
FILE: src/MarkdownSnippets/Processing/SnippetKey.cs
class SnippetKey (line 1) | static class SnippetKey
method ExtractStartCommentSnippet (line 3) | public static bool ExtractStartCommentSnippet(Line line, [NotNullWhen(...
method ExtractStartCommentWebSnippet (line 23) | public static bool ExtractStartCommentWebSnippet(Line line, [NotNullWh...
method ExtractStartCommentWebSnippet (line 26) | public static bool ExtractStartCommentWebSnippet(Line line, [NotNullWh...
method ExtractSnippet (line 75) | public static bool ExtractSnippet(Line line, [NotNullWhen(true)] out s...
method ExtractWebSnippet (line 94) | public static bool ExtractWebSnippet(Line line, [NotNullWhen(true)] ou...
method ExtractWebSnippet (line 97) | public static bool ExtractWebSnippet(Line line, [NotNullWhen(true)] ou...
method IsSnippetLine (line 138) | public static bool IsSnippetLine(string line) =>
method IsSnippetLine (line 141) | public static bool IsSnippetLine(CharSpan line) =>
method IsStartCommentSnippetLine (line 144) | public static bool IsStartCommentSnippetLine(string line) =>
method IsStartCommentSnippetLine (line 147) | public static bool IsStartCommentSnippetLine(CharSpan line) =>
method IsWebSnippetLine (line 150) | public static bool IsWebSnippetLine(string line) =>
method IsWebSnippetLine (line 153) | public static bool IsWebSnippetLine(CharSpan line) =>
method IsStartCommentWebSnippetLine (line 156) | public static bool IsStartCommentWebSnippetLine(string line) =>
method IsStartCommentWebSnippetLine (line 159) | public static bool IsStartCommentWebSnippetLine(CharSpan line) =>
FILE: src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs
class SnippetMarkdownHandling (line 6) | public class SnippetMarkdownHandling
method SnippetMarkdownHandling (line 13) | public SnippetMarkdownHandling(string targetDirectory, LinkFormat link...
method Append (line 23) | public void Append(string key, IEnumerable<Snippet> snippets, Action<s...
method WriteSnippet (line 34) | void WriteSnippet(Action<string> appendLine, Snippet snippet, uint index)
method GetAnchorText (line 51) | static string GetAnchorText(Snippet snippet, uint index)
method GetSupText (line 74) | string GetSupText(Snippet snippet, string anchor)
method WriteSnippetValueAndLanguage (line 109) | static void WriteSnippetValueAndLanguage(Action<string> appendLine, Sn...
method BuildLink (line 123) | void BuildLink(Snippet snippet, string path, StringBuilder builder)
FILE: src/MarkdownSnippets/Processing/TocBuilder.cs
class TocBuilder (line 1) | static class TocBuilder
method BuildToc (line 3) | public static string BuildToc(List<Line> headerLines, int level, List<...
method GetTitle (line 64) | static string GetTitle(string current)
method BuildLink (line 70) | static void BuildLink(StringBuilder builder, Dictionary<string, int> p...
method SanitizeLink (line 82) | internal static void SanitizeLink(StringBuilder builder, CharSpan title)
FILE: src/MarkdownSnippets/Processing/ValidationError.cs
class ValidationError (line 6) | [DebuggerDisplay("Error={Error}, Line={Line}:{Column}")]
method ValidationError (line 12) | public ValidationError(string error, int line, int column, string? file)
method ToString (line 44) | public override string ToString()
FILE: src/MarkdownSnippets/Reading/Exclusions/DefaultDirectoryExclusions.cs
class DefaultDirectoryExclusions (line 3) | public static class DefaultDirectoryExclusions
method ShouldExcludeDirectory (line 5) | public static bool ShouldExcludeDirectory(string path)
FILE: src/MarkdownSnippets/Reading/Exclusions/SnippetFileExclusions.cs
class SnippetFileExclusions (line 3) | public static class SnippetFileExclusions
method IsBinary (line 5) | public static bool IsBinary(string extension) =>
method CanContainCommentsExtension (line 8) | public static bool CanContainCommentsExtension(string extension) =>
method AddNoAcceptCommentsExtensions (line 28) | public static void AddNoAcceptCommentsExtensions(params string[] exten...
method RemoveNoAcceptCommentsExtensions (line 39) | public static void RemoveNoAcceptCommentsExtensions(params string[] ex...
method AddBinaryFileExtensions (line 326) | public static void AddBinaryFileExtensions(params string[] extensions)
method RemoveBinaryFileExtensions (line 337) | public static void RemoveBinaryFileExtensions(params string[] extensions)
FILE: src/MarkdownSnippets/Reading/FileFinder.cs
class FileFinder (line 1) | class FileFinder(
method FindFiles (line 14) | public (List<string> snippetFiles, List<string> mdFiles, List<string> ...
method FindFiles (line 30) | void FindFiles(string directory)
method ProcessFiles (line 41) | void ProcessFiles(string directory)
method IncludeAsSnippet (line 93) | bool IncludeAsSnippet(string file) =>
method EnumerateFiles (line 96) | static IEnumerable<string> EnumerateFiles(string directory) =>
method ProcessMarkdown (line 100) | void ProcessMarkdown(string file)
method ShouldInclude (line 120) | static bool ShouldInclude(string file)
FILE: src/MarkdownSnippets/Reading/FileSnippetExtractor.cs
class FileSnippetExtractor (line 6) | public static class FileSnippetExtractor
method AppendUrlAsSnippet (line 11) | public static Task AppendUrlAsSnippet(this ICollection<Snippet> snippe...
method AppendUrlsAsSnippets (line 20) | public static Task AppendUrlsAsSnippets(this ICollection<Snippet> snip...
method AppendUrlsAsSnippets (line 26) | public static async Task AppendUrlsAsSnippets(this ICollection<Snippet...
method AppendUrlAsSnippet (line 37) | public static async Task AppendUrlAsSnippet(ICollection<Snippet> snipp...
method AppendFileAsSnippet (line 56) | public static void AppendFileAsSnippet(this ICollection<Snippet> snipp...
method AppendFilesAsSnippets (line 62) | public static void AppendFilesAsSnippets(this ICollection<Snippet> sni...
method AppendFileAsSnippet (line 70) | public static void AppendFileAsSnippet(ICollection<Snippet> snippets, ...
method Read (line 84) | public static IEnumerable<Snippet> Read(IEnumerable<string> paths, int...
method Read (line 95) | public static IEnumerable<Snippet> Read(string path, int maxWidth = in...
method Read (line 116) | public static IEnumerable<Snippet> Read(TextReader textReader, string ...
method GetLanguageFromPath (line 123) | public static string GetLanguageFromPath(string path)
method GetSnippets (line 131) | static IEnumerable<Snippet> GetSnippets(TextReader stringReader, strin...
method BuildSnippet (line 199) | static Snippet BuildSnippet(string path, LoopStack loopStack, string l...
FILE: src/MarkdownSnippets/Reading/IContent.cs
type IContent (line 3) | public interface IContent
FILE: src/MarkdownSnippets/Reading/Include.cs
class Include (line 3) | [DebuggerDisplay("Key={Key}, Path={Path}, Error={Error}")]
method BuildError (line 10) | public static Include BuildError(string key, int lineNumberInError, st...
method Build (line 27) | public static Include Build(string key, IReadOnlyList<string> lines, s...
method ThrowIfIsInError (line 65) | void ThrowIfIsInError()
method ToString (line 73) | public override string ToString() =>
FILE: src/MarkdownSnippets/Reading/LineTooLongException.cs
class LineTooLongException (line 1) | class LineTooLongException(string line) :
FILE: src/MarkdownSnippets/Reading/LoopStack.cs
class LoopStack (line 1) | [DebuggerDisplay("Depth={stack.Count}, IsInSnippet={IsInSnippet}")]
method AppendLine (line 8) | public void AppendLine(string line)
method Pop (line 16) | public void Pop() => stack.Pop();
method Push (line 18) | public void Push(EndFunc endFunc, CharSpan key, int startLine, int max...
FILE: src/MarkdownSnippets/Reading/LoopState.cs
class LoopState (line 1) | [DebuggerDisplay("Key={Key}")]
method GetLines (line 4) | public string GetLines()
method AppendLine (line 22) | public void AppendLine(string line)
method CheckWhiteSpace (line 63) | void CheckWhiteSpace(CharSpan line, char whiteSpace)
FILE: src/MarkdownSnippets/Reading/ReadSnippets.cs
class ReadSnippets (line 3) | [DebuggerDisplay("Count={Snippets.Count}")]
method ReadSnippets (line 12) | public ReadSnippets(IReadOnlyList<Snippet> snippets, IReadOnlyList<str...
method GetEnumerator (line 23) | public virtual IEnumerator<Snippet> GetEnumerator()
method GetEnumerator (line 33) | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
FILE: src/MarkdownSnippets/Reading/Snippet.cs
class Snippet (line 3) | [DebuggerDisplay("Key={Key}, FileLocation={FileLocation}, Error={Error}")]
method BuildError (line 10) | public static Snippet BuildError(string key, int lineNumberInError, st...
method Build (line 29) | public static Snippet Build(int startLine, int endLine, string value, ...
method Build (line 59) | public static Snippet Build(int startLine, int endLine, string value, ...
method ThrowIfIsInError (line 163) | void ThrowIfIsInError()
method ToString (line 171) | public override string ToString() =>
FILE: src/MarkdownSnippets/Reading/StartEndTester.cs
class StartEndTester (line 1) | static class StartEndTester
method IsStartOrEnd (line 3) | internal static bool IsStartOrEnd(CharSpan line)
method IsStart (line 12) | internal static bool IsStart(
method IsEndRegion (line 44) | static bool IsEndRegion(CharSpan line) =>
method IsEndSnippet (line 47) | static bool IsEndSnippet(CharSpan line) =>
method IsStartRegion (line 50) | static bool IsStartRegion(CharSpan line) =>
method IsStartRegion (line 53) | internal static bool IsStartRegion(
method IsBeginSnippet (line 76) | static bool IsBeginSnippet(CharSpan line)
method IsBeginSnippet (line 82) | internal static bool IsBeginSnippet(
method IsBeginSnippet (line 89) | internal static bool IsBeginSnippet(
method ExtractLanguage (line 159) | static CharSpan ExtractLanguage(CharSpan args, scoped CharSpan key, sc...
method IndexOf (line 212) | static int IndexOf(CharSpan line, CharSpan value)
FILE: src/MarkdownSnippets/SnippetException.cs
class SnippetException (line 3) | public class SnippetException(string message) :
FILE: src/MarkdownSnippets/SnippetExtensions.cs
class SnippetExtensions (line 1) | static class SnippetExtensions
method ToDictionary (line 3) | public static Dictionary<string, IReadOnlyList<Snippet>> ToDictionary(...
method ScrubPath (line 10) | static string? ScrubPath(Snippet snippet)
FILE: src/MarkdownSnippets/SnippetReadingException.cs
class SnippetReadingException (line 3) | public class SnippetReadingException(string message) :
FILE: src/MarkdownSnippets/StringBuilderCache.cs
class StringBuilderCache (line 1) | static class StringBuilderCache
method Acquire (line 8) | public static StringBuilder Acquire(int capacity = 16)
method Release (line 25) | public static void Release(StringBuilder builder)
method GetStringAndRelease (line 33) | public static string GetStringAndRelease(StringBuilder builder)
FILE: src/Tests/ContentValidationTest.cs
class ContentValidationTest (line 1) | public class ContentValidationTest
method CheckInvalidWord (line 3) | [Fact]
method CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage (line 6) | [Fact]
method CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase (line 10) | [Fact]
method CheckInvalidWordWithQuestionMark (line 14) | [Fact]
method CheckInvalidWordWithComma (line 18) | [Fact]
method CheckInvalidWordSentenceEnd (line 22) | [Fact]
method CheckInvalidWordSentenceStart (line 26) | [Fact]
method CheckInvalidWordStringEnd (line 30) | [Fact]
method CheckInvalidWordDoesNotThrowWhenNoMatch (line 34) | [Fact]
method CheckInvalidWordDoesNotThrowWhenIsQuote (line 38) | [Fact]
method CheckInvalidWordInUrl (line 42) | [Fact]
FILE: src/Tests/DirectoryMarkdownProcessorTests.cs
class DirectoryMarkdownProcessorTests (line 1) | public class DirectoryMarkdownProcessorTests
method Run (line 3) | [Fact]
method InPlaceOverwriteExists (line 20) | [Fact]
method InPlaceOverwriteExistsMdx (line 38) | [Fact]
method InPlaceOverwriteNotExists (line 56) | [Fact]
method InPlaceOverwriteUrlSnippet (line 76) | [Fact]
method InPlaceOverwriteUrlInclude (line 93) | [Fact]
method InPlaceOverwriteWithFileSnippetMissing (line 110) | [Fact]
method ReadOnly (line 129) | [Fact]
method FileSnippetMissing (line 160) | [Fact]
method UrlSnippetMissing (line 174) | [Fact]
method ValidationErrors (line 188) | [Fact]
method UrlIncludeMissing (line 202) | [Fact]
method UrlSnippet (line 216) | [Fact]
method BinaryFileSnippet (line 234) | [Fact]
method FileSnippetWithWhiteSpace (line 252) | [Fact]
method Mdx (line 269) | [Fact]
method FileSnippet (line 286) | [Fact]
method FileSnippetExplicitIncludeBypassesExcludeSnippetFiles (line 303) | [Fact]
method FileSnippetWithHash (line 325) | [Fact]
method MixedCaseInclude (line 342) | [Fact]
method ExplicitFileInclude (line 360) | [Fact]
method ExplicitFileIncludeWithMergedSnippet (line 377) | [Fact]
method ExplicitFileIncludeWithSnippetAtEnd (line 396) | [Fact]
method UrlInclude (line 415) | [Fact]
method Convention (line 432) | [Fact]
method ConventionWithNestedDir (line 459) | [Fact]
method MustErrorByDefaultWhenIncludesAreMissing (line 484) | [Fact]
method MustNotErrorForMissingIncludesIfConfigured (line 498) | [Fact]
method MustErrorByDefaultWhenSnippetsAreMissing (line 513) | [Fact]
method MustNotErrorForMissingSnippetsIfConfigured (line 527) | [Fact]
method SnippetBuild (line 542) | static Snippet SnippetBuild(string key, string? path = null) =>
FILE: src/Tests/DownloaderTests.cs
class DownloaderTests (line 1) | public class DownloaderTests
method Valid (line 3) | [Fact]
method Missing (line 10) | [Fact]
FILE: src/Tests/FileExTests.cs
class FileExTests (line 1) | public class FileExTests
method MakeReadOnly_SetsReadOnlyAttribute (line 3) | [Fact]
method ClearReadOnly_RemovesReadOnlyAttribute (line 21) | [Fact]
method ClearReadOnly_DoesNothingIfFileDoesNotExist (line 41) | [Fact]
method MakeReadOnly_ThenClearReadOnly_RoundTrip (line 51) | [Fact]
method FixFileCapitalization_ReturnsActualCasing (line 70) | [Fact]
method FixFileCapitalization_WorksWhenCasingMatches (line 84) | [Fact]
FILE: src/Tests/GirRepoDirectoryFinderTests.cs
class GirRepoDirectoryFinderTests (line 1) | public class GirRepoDirectoryFinderTests
method CanFindGirRepoDir (line 3) | [Fact]
FILE: src/Tests/HeaderWriterTests.cs
class HeaderWriterTests (line 1) | public class HeaderWriterTests
method DefaultHeader (line 3) | [Fact]
method WriteHeaderDefaultHeader (line 7) | [Fact]
method WriteHeaderHeaderCustom (line 11) | [Fact]
FILE: src/Tests/IndexReaderTests.cs
class IndexReaderTests (line 1) | public class IndexReaderTests
method NewLineDetection (line 3) | [Theory]
FILE: src/Tests/LoopState/LoopStateTests.cs
class LoopStateTests (line 1) | public class LoopStateTests
method TrimIndentation (line 3) | [Fact]
method ExcludeEmptyPaddingLines (line 13) | [Fact]
method TrimIndentation_with_mis_match (line 23) | [Fact]
method ExcludeEmptyPaddingLines_empty_list (line 33) | [Fact]
method ExcludeEmptyPaddingLines_whitespace_list (line 40) | [Fact]
method TrimIndentation_no_initial_padding (line 49) | [Fact]
FILE: src/Tests/MarkdownProcessor/MarkdownProcessorTests.cs
class MarkdownProcessorTests (line 1) | public class MarkdownProcessorTests
method Missing_endInclude (line 3) | [Fact]
method WithEmptyMultiLineInclude_Overwrite (line 17) | [Fact]
method WithMultiLineInclude_Overwrite (line 42) | [Fact]
method WithSingleInclude_Overwrite (line 67) | [Fact]
method WithSingleInclude (line 89) | [Fact]
method WithMixedCaseInclude (line 111) | [Fact]
method WithSingleSnippet (line 135) | [Fact]
method WithMixedCaseSnippet (line 153) | [Fact]
method WithTwoLineSnippet (line 178) | [Fact]
method WithMultiLineSnippet (line 210) | [Fact]
method WithDoubleInclude (line 243) | [Fact]
method WithEmptyMultipleInclude (line 269) | [Fact]
method WithMultipleInclude (line 293) | [Fact]
method MissingInclude (line 317) | [Fact]
method SkipHeadingBeforeToc (line 332) | [Fact]
method Toc1 (line 351) | [Fact]
method Toc (line 372) | [Fact]
method TocRetainedIfNoHeadingsInFile (line 393) | [Fact]
method Missing_endToc (line 411) | [Fact]
method Empty_snippet_key (line 423) | [Fact]
method Whitespace_snippet_key (line 435) | [Fact]
method Toc_Overwrite (line 447) | [Fact]
method Simple_Overwrite (line 469) | [Fact]
method MixedNewlinesInFile (line 520) | [Fact]
method Simple (line 547) | [Fact]
method SnippetInInclude (line 582) | [Fact]
method TableInInclude (line 609) | [Fact]
method SnippetInIncludeLast (line 637) | [Fact]
method WithIndentedSnippet (line 665) | [Fact]
method WithIndentedSnippetMultipleSpaces (line 684) | [Fact]
method WithIndentedCommentSnippet (line 703) | [Fact]
method WithTabIndentedSnippet (line 724) | [Fact]
method WithIndentedWebSnippet (line 743) | [Fact]
method WithIndentedMultiLineSnippet (line 762) | [Fact]
method WithCommentWebSnippetUpdate (line 795) | [Fact]
method WithCommentWebSnippetWithViewUrl (line 817) | [Fact]
method WithInlineWebSnippetWithViewUrl (line 839) | [Fact]
method SnippetBuild (line 857) | static Snippet SnippetBuild(string language, string key) =>
FILE: src/Tests/MarkdownProcessor/SnippetKey_ExtractStartCommentSnippet.cs
class SnippetKey_ExtractStartCommentSnippet (line 1) | public class SnippetKey_ExtractStartCommentSnippet
method WithDashes (line 3) | [Fact]
method Simple (line 10) | [Fact]
method MissingClosingComment_Throws (line 17) | [Fact]
FILE: src/Tests/MarkdownProcessor/SnippetKey_ExtractStartCommentWebSnippet.cs
class SnippetKey_ExtractStartCommentWebSnippet (line 1) | public class SnippetKey_ExtractStartCommentWebSnippet
method Simple (line 3) | [Fact]
method MissingClosingComment_Throws (line 11) | [Fact]
FILE: src/Tests/MarkdownProcessor/SnippetKey_ExtractTransform.cs
class SnippetKey_ExtractTransform (line 1) | public class SnippetKey_ExtractTransform
method MissingSpaces (line 3) | [Fact]
method WithDashes (line 10) | [Fact]
method Simple (line 17) | [Fact]
method ExtraSpace (line 24) | [Fact]
FILE: src/Tests/MarkdownProcessor/TocBuilderTests.cs
class TocBuilderTests (line 1) | public class TocBuilderTests
method EmptyHeading (line 3) | [Fact]
method IgnoreTop (line 16) | [Fact]
method SanitizeLink (line 28) | [Fact]
method StripMarkdown (line 36) | [Fact]
method Exclude (line 47) | [Fact]
method Nested (line 59) | [Fact]
method Deep (line 73) | [Fact]
method StopAtLevel (line 87) | [Fact]
method Single (line 100) | [Fact]
method WithSpaces (line 111) | [Fact]
method DuplicateNested (line 122) | [Fact]
method Duplicates (line 135) | [Fact]
FILE: src/Tests/ModuleInitializer.cs
class ModuleInitializer (line 1) | public static class ModuleInitializer
method Initialize (line 3) | [ModuleInitializer]
FILE: src/Tests/MsBuildIntegrationTests.cs
class MsBuildIntegrationTests (line 24) | public class MsBuildIntegrationTests
method GetNugetsDir (line 26) | static string GetNugetsDir()
method DotnetBuild_UsesNetCoreTask (line 35) | [Fact]
method MsBuild_UsesNetFrameworkTask (line 55) | [Fact]
method SetupTestProject (line 82) | static async Task SetupTestProject(TempDirectory tempDir)
method GetLatestNugetVersion (line 154) | static string GetLatestNugetVersion()
method FindMsBuild (line 166) | static string? FindMsBuild()
method RunProcess (line 198) | static async Task<ProcessResult> RunProcess(string fileName, string ar...
type ProcessResult (line 219) | record ProcessResult(int ExitCode, string Output, string Error);
method MsBuild_AllLocalProjects_UsingMarkdownSnippets (line 221) | [Fact(Explicit = true)]
method FindProjectsUsingMarkdownSnippets (line 281) | static List<string> FindProjectsUsingMarkdownSnippets(string rootDir)
method RunMsBuildOnProject (line 316) | static Task<ProcessResult> RunMsBuildOnProject(string msbuildPath, str...
method ExtractErrorLines (line 357) | static string ExtractErrorLines(string output)
method GenerateMarkdownReport (line 400) | static Task GenerateMarkdownReport(string reportPath, int totalProject...
FILE: src/Tests/NewLineConfigReaderTests.cs
class NewLineConfigReaderTests (line 1) | public class NewLineConfigReaderTests
method GitAttributes_WildcardEolLf (line 3) | [Fact]
method GitAttributes_WildcardEolCrlf (line 13) | [Fact]
method GitAttributes_MdSpecificEolLf (line 22) | [Fact]
method GitAttributes_MdSpecificOverridesWildcard (line 31) | [Fact]
method GitAttributes_NoEolSetting_FallsBackToEnvironmentNewLine (line 45) | [Fact]
method GitAttributes_IgnoresComments (line 54) | [Fact]
method GitAttributes_InParentDirectory (line 68) | [Fact]
method EditorConfig_WildcardEndOfLineLf (line 79) | [Fact]
method EditorConfig_WildcardEndOfLineCrlf (line 93) | [Fact]
method EditorConfig_MdSpecificEndOfLine (line 107) | [Fact]
method EditorConfig_MdSpecificOverridesWildcard (line 121) | [Fact]
method EditorConfig_BracePattern (line 138) | [Fact]
method EditorConfig_IgnoresComments (line 152) | [Fact]
method GitAttributes_TakesPriorityOverEditorConfig (line 168) | [Fact]
method NoConfigFiles_FallsBackToEnvironmentNewLine (line 183) | [Fact]
method EditorConfig_FallbackWhenGitAttributesHasNoEol (line 191) | [Fact]
method DetectsNewLineFromMdFiles_Lf (line 206) | [Fact]
method DetectsNewLineFromMdFiles_Crlf (line 216) | [Fact]
method ConfigTakesPriorityOverMdFileDetection (line 226) | [Fact]
method MdFileDetection_PicksShortestFileFirst (line 237) | [Fact]
method MdFileDetection_SkipsFilesWithNoNewlines (line 249) | [Fact]
FILE: src/Tests/PathsTests.cs
class PathsTests (line 1) | public class PathsTests
method IsMdFile (line 3) | [Theory]
method IsSourceMdFile (line 14) | [Theory]
method IsIncludeMdFile (line 24) | [Theory]
FILE: src/Tests/ProcessResultConverter.cs
class ProcessResultConverter (line 1) | class ProcessResultConverter :
method WriteJson (line 4) | public override void WriteJson(JsonWriter writer, object value, JsonSe...
method ReadJson (line 15) | public override object ReadJson(JsonReader reader, Type objectType, ob...
method CanConvert (line 18) | public override bool CanConvert(Type objectType) =>
FILE: src/Tests/SimpleSnippetMarkdownHandlingTests.cs
class SimpleSnippetMarkdownHandlingTests (line 1) | public class SimpleSnippetMarkdownHandlingTests
method Append (line 3) | [Fact]
method ExpressiveCode (line 16) | [Fact]
FILE: src/Tests/SnippetConverter.cs
class SnippetConverter (line 1) | class SnippetConverter :
method WriteJson (line 4) | public override void WriteJson(JsonWriter writer, object value, JsonSe...
method ReadJson (line 26) | public override object ReadJson(JsonReader reader, Type objectType, ob...
method CanConvert (line 29) | public override bool CanConvert(Type objectType) =>
FILE: src/Tests/SnippetExtensionsTests.cs
class SnippetExtensionsTests (line 1) | public class SnippetExtensionsTests
method ToDictionary (line 3) | [Fact]
method ToDictionary_SameKey (line 14) | [Fact]
method SnippetBuild (line 26) | static Snippet SnippetBuild(string key, string? path) =>
FILE: src/Tests/SnippetExtractor/SnippetExtractorTests.cs
class SnippetExtractorTests (line 1) | public class SnippetExtractorTests
method AppendUrlAsSnippet (line 3) | [Fact]
method AppendUrlAsSnippetInline (line 11) | [Fact]
method AppendFileAsSnippet (line 19) | [Fact]
method CanReadFileWhileLockedByAnotherProcess (line 42) | [Fact]
method CanExtractWithInnerWhiteSpace (line 65) | [Fact]
method NestedBroken (line 82) | [Fact]
method NestedRegion (line 98) | [Fact]
method NestedMixed2 (line 115) | [Fact]
method RemoveDuplicateNewlines (line 131) | [Fact]
method NestedStartCode (line 163) | [Fact]
method NestedMixed1 (line 179) | [Fact]
method CanExtractFromXml (line 195) | [Fact]
method LanguageOverride (line 207) | [Fact]
method LanguageOverrideWithExpressiveCode (line 219) | [Fact]
method FromText (line 231) | static List<Snippet> FromText(string contents)
method UnClosedSnippet (line 237) | [Fact]
method UnClosedRegion (line 248) | [Fact]
method TooWide (line 260) | [Fact]
method MixedNewLines (line 273) | [Fact]
method CanExtractFromRegion (line 285) | [Fact]
method CanExtractWithNoTrailingCharacters (line 298) | [Fact]
method CanExtractWithMissingSpaces (line 311) | [Fact]
method CanExtractWithTrailingWhitespace (line 324) | [Fact]
method CanExtractWithExpressiveCode (line 337) | [Fact]
FILE: src/Tests/SnippetFileFinderTests.cs
class SnippetFileFinderTests (line 3) | public class SnippetFileFinderTests
method Hidden (line 5) | [Fact]
method Nested (line 15) | [Fact]
method Simple (line 24) | [Fact]
method ExcludeSnippetFiles (line 33) | [Fact]
method VerifyLambdasAreCalled (line 52) | [Fact]
FILE: src/Tests/SnippetMarkdownHandlingTests.cs
class SnippetMarkdownHandlingTests (line 1) | public class SnippetMarkdownHandlingTests
method Append (line 3) | [Fact]
method AppendOmitSourceLink (line 17) | [Fact]
method AppendOmitSnippetLinks (line 31) | [Fact]
method AppendPrefixed (line 45) | [Fact]
method AppendHashed (line 59) | [Fact]
method AppendWebSnippet (line 73) | [Fact]
method AppendWebSnippetWithViewUrl (line 94) | [Fact]
method Snippets (line 116) | static List<Snippet> Snippets() =>
FILE: src/Tests/SnippetVerifier.cs
class SnippetVerifier (line 1) | static class SnippetVerifier
method VerifyThrows (line 3) | public static Task VerifyThrows(
method BuildProcessor (line 18) | static MarkdownProcessor BuildProcessor(
method Verify (line 40) | public static async Task<string> Verify(
FILE: src/Tests/Snippets/Usage.cs
class Usage (line 4) | class Usage
method ReadingFiles (line 6) | static void ReadingFiles()
method DirectoryMarkdownProcessorRun (line 17) | static void DirectoryMarkdownProcessorRun()
method DirectoryMarkdownProcessorRunMaxWidth (line 31) | static void DirectoryMarkdownProcessorRunMaxWidth()
FILE: src/Tests/StartEndTester_IsBeginSnippetTests.cs
class StartEndTester_IsBeginSnippetTests (line 1) | public class StartEndTester_IsBeginSnippetTests
method CanExtractFromXml (line 3) | [Fact]
method ShouldThrowForNoKey (line 11) | [Fact]
method ShouldNotThrowForNoKeyWithNoSpace (line 15) | [Fact]
method CanExtractFromXmlWithMissingSpaces (line 19) | [Fact]
method CanExtractFromXmlWithExtraSpaces (line 27) | [Fact]
method CanExtractWithNoTrailingCharacters (line 35) | [Fact]
method CanExtractWithUnderScores (line 43) | [Fact]
method CanExtractWithDashes (line 51) | [Fact]
method ShouldThrowForKeyStartingWithSymbol (line 59) | [Fact]
method ShouldThrowForKeyEndingWithSymbol (line 64) | [Fact]
method CanExtractWithDifferentEndComments (line 69) | [Fact]
method CanExtractWithDifferentEndCommentsAndNoSpaces (line 77) | [Fact]
method CanExtractWithExpressiveCodeWithHtmlSnippet (line 85) | [Fact]
method CanExtractWithExpressiveCodeWithCsharpComment (line 94) | [Fact]
method CanExtractWithExpressiveCodeWithHtmlSnippetTrailingWhitespace (line 102) | [Fact]
method CanExtractWithExpressiveCodeWithCsharpCommentTrailingWhitespace (line 111) | [Fact]
method CanExtractLanguageOverride (line 120) | [Fact]
method CanExtractLanguageOverrideWithExpressiveCode (line 130) | [Fact]
method ShouldThrowForInvalidLanguageValue (line 140) | [Fact]
method ShouldThrowForEmptyLanguageValue (line 145) | [Fact]
FILE: src/Tests/StartEndTester_IsStartRegionTests.cs
class StartEndTester_IsStartRegionTests (line 1) | public class StartEndTester_IsStartRegionTests
method CanExtractFromXml (line 3) | [Fact]
method ShouldThrowForKeyStartingWithSymbol (line 10) | [Fact]
method WithSpaces (line 14) | [Fact]
method ShouldThrowForKeyEndingWithSymbol (line 18) | [Fact]
method ShouldIgnoreForNoKey (line 22) | [Fact]
method CanExtractFromXmlWithExtraSpaces (line 26) | [Fact]
method CanExtractWithNoTrailingCharacters (line 33) | [Fact]
method CanExtractWithUnderScores (line 40) | [Fact]
method CanExtractWithDashes (line 47) | [Fact]
FILE: src/Tests/WebSnippetTests.cs
class WebSnippetTests (line 1) | public class WebSnippetTests
method ExtractWebSnippet_ParsesCorrectly (line 3) | [Fact]
method ExtractWebSnippet_FailsWithoutHash (line 12) | [Fact]
Condensed preview — 431 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (569K chars).
[
{
"path": ".claude/settings.local.json",
"chars": 106,
"preview": "{\n \"permissions\": {\n \"allow\": [\n \"Bash(dotnet build:*)\",\n \"Bash(dotnet test:*)\"\n ]\n }\n}\n"
},
{
"path": ".editorconfig",
"chars": 15133,
"preview": "root = true\n\n[*]\nindent_style = space\nend_of_line = lf\ninsert_final_newline = true\n\n[*.cs]\nindent_size = 4\ncharset = utf"
},
{
"path": ".gitattributes",
"chars": 441,
"preview": "# Auto detect text files and normalize line endings to LF\n* text=auto eol=lf\n*.png binary\n*.snk binary\n\n*.verified.txt t"
},
{
"path": ".github/FUNDING.yml",
"chars": 19,
"preview": "github: SimonCropp\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 1862,
"preview": "---\nname: Bug fix\nabout: Create a bug fix to help us improve\n---\n\nNote: New issues raised, where it is clear the submitt"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 27,
"preview": "blank_issues_enabled: false"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 1528,
"preview": "---\nname: Feature request\nabout: How to raise feature requests\n---\n\n\nNote: New issues raised, where it is clear the subm"
},
{
"path": ".github/dependabot.yml",
"chars": 130,
"preview": "version: 2\nupdates:\n- package-ecosystem: nuget\n directory: \"/src\"\n schedule:\n interval: daily\n open-pull-requests-"
},
{
"path": ".github/stale.yml",
"chars": 792,
"preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 7\n# Number of days of inactivity before a s"
},
{
"path": ".github/workflows/merge-dependabot.yml",
"chars": 363,
"preview": "name: merge-dependabot\non:\n pull_request:\njobs:\n automerge:\n runs-on: ubuntu-latest\n if: github.actor == 'depend"
},
{
"path": ".github/workflows/milestone-release.yml",
"chars": 5606,
"preview": "name: milestone-release\n\non:\n push:\n tags:\n - '*'\n milestone:\n types: [created, edited, closed, opened]\n i"
},
{
"path": ".gitignore",
"chars": 121,
"preview": "*.suo\n*.user\nbin/\nobj/\n.vs/\n*.DotSettings.user\n.idea/\n*.received.*\nnugets/\nnul\n/src/Benchmarks/BenchmarkDotNet.Artifacts"
},
{
"path": "claude.md",
"chars": 3677,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "code_of_conduct.md",
"chars": 3353,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "docs/api.md",
"chars": 1080,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/config-file.md",
"chars": 3887,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/exclusion.md",
"chars": 5912,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/github-action.md",
"chars": 2144,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/header.md",
"chars": 1410,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/includes.md",
"chars": 1628,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/indentation.md",
"chars": 1242,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/max-width.md",
"chars": 2158,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/mdsource/api.source.md",
"chars": 435,
"preview": "# Library Usage\n\n\n## NuGet package\n\nhttps://nuget.org/packages/MarkdownSnippets/ [ and the [MSBuild Task](msbuild.md) support a config file conve"
},
{
"path": "docs/mdsource/doc-index.include.md",
"chars": 474,
"preview": " * Developer Information\n * [.net API](/docs/api.md)\n * [MsBuild Task](/docs/msbuild.md)\n * [Github Action](/d"
},
{
"path": "docs/mdsource/exclusion.source.md",
"chars": 1847,
"preview": "# Exclusions\n\n\n## Exclude directories from snippet and markdown discovery\n\nTo exclude directories use `-e` or `--exclude"
},
{
"path": "docs/mdsource/github-action.source.md",
"chars": 908,
"preview": "# GitHub Actions\n\nMarkdown snippets can be run inside a [GitHub Action](https://help.github.com/en/actions) by installin"
},
{
"path": "docs/mdsource/header.source.md",
"chars": 596,
"preview": "# Header\n\nWhen a .md file is written, a header is include. The default header is:\n\nsnippet: HeaderWriterTests.DefaultHea"
},
{
"path": "docs/mdsource/includes.source.md",
"chars": 1374,
"preview": "# Includes\n\n\n## Including full code files\n\nWhen snippets are read all source files are stored in a list. When searching "
},
{
"path": "docs/mdsource/indentation.source.md",
"chars": 985,
"preview": "# Code indentation\n\nThe code snippets will do smart trimming of snippet indentation.\n\nFor example given this snippet:\n\n<"
},
{
"path": "docs/mdsource/max-width.source.md",
"chars": 1381,
"preview": "# Max Width\n\nThe Max Width setting is used to control the maximum characters per line of a snippet. If any snippet has a"
},
{
"path": "docs/mdsource/msbuild.source.md",
"chars": 812,
"preview": "# MsBuild\n\nAn [MsBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task) for merging snippets in"
},
{
"path": "docs/mdsource/readme.source.md",
"chars": 35,
"preview": "# Documentation\n\ninclude: doc-index"
},
{
"path": "docs/mdsource/toc/tocAfter.txt",
"chars": 146,
"preview": "# Title\n\n<!-- toc -->\n## Contents\n\n * [Heading 1](#heading-1)\n * [Heading 2](#heading-2)\n<!-- endToc -->\n\n## Heading 1\n\n"
},
{
"path": "docs/mdsource/toc/tocBefore.txt",
"chars": 54,
"preview": "# Title\n\ntoc\n\n## Heading 1\n\nText1\n\n## Heading 1\n\nText2"
},
{
"path": "docs/mdsource/toc.source.md",
"chars": 692,
"preview": "# Table of contents\n\nIf a line is `toc` it will be replaced with a table of contents\n\nSo if a markdown document contains"
},
{
"path": "docs/msbuild.md",
"chars": 1065,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/on-push-do-docs.yml",
"chars": 717,
"preview": "name: on-push-do-docs\non:\n push:\njobs:\n docs:\n runs-on: windows-latest\n steps:\n - uses: actions/checkout@v4\n "
},
{
"path": "docs/readme.md",
"chars": 832,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "docs/toc.md",
"chars": 1648,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "license.txt",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 Simon Cropp\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "readme.md",
"chars": 17949,
"preview": "<!--\nGENERATED FILE - DO NOT EDIT\nThis file was generated by [MarkdownSnippets](https://github.com/SimonCropp/MarkdownSn"
},
{
"path": "readme.source.md",
"chars": 13631,
"preview": "# <img src=\"/src/icon.png\" height=\"30px\"> MarkdownSnippets\n\n[;\n"
},
{
"path": "src/ConfigReader/AssemblyInfo.cs",
"chars": 385,
"preview": "[assembly: InternalsVisibleTo(\"ConfigReader.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001"
},
{
"path": "src/ConfigReader/ConfigDefaults.cs",
"chars": 4976,
"preview": "public static class ConfigDefaults\n{\n public static ConfigResult Convert(ConfigInput? fileConfig, ConfigInput otherC"
},
{
"path": "src/ConfigReader/ConfigInput.cs",
"chars": 925,
"preview": "public class ConfigInput\n{\n public bool? ReadOnly { get; init; }\n public bool? ValidateContent { get; init; }\n "
},
{
"path": "src/ConfigReader/ConfigReader.cs",
"chars": 3439,
"preview": "public static class ConfigReader\n{\n public static (ConfigInput? config, string path) Read(string directory)\n {\n "
},
{
"path": "src/ConfigReader/ConfigReader.csproj",
"chars": 478,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFrameworks>netstandard2.0;netstandard2.1;net48;net8.0;ne"
},
{
"path": "src/ConfigReader/ConfigResult.cs",
"chars": 889,
"preview": "public class ConfigResult\n{\n public bool? ReadOnly { get; init; }\n public bool ValidateContent { get; init; }\n "
},
{
"path": "src/ConfigReader/ConfigSerialization.cs",
"chars": 900,
"preview": "public class ConfigSerialization\n{\n public bool? ReadOnly { get; set; }\n public bool? ValidateContent { get; set;"
},
{
"path": "src/ConfigReader/ConfigurationException.cs",
"chars": 71,
"preview": "class ConfigurationException(string message) :\n Exception(message);"
},
{
"path": "src/ConfigReader/ExcludeToFilterBuilder.cs",
"chars": 2028,
"preview": "static class ExcludeToFilterBuilder\n{\n public static ShouldIncludeDirectory ExcludesToFilter(List<string>? excludes) "
},
{
"path": "src/ConfigReader/LogBuilder.cs",
"chars": 4276,
"preview": "static class LogBuilder\n{\n public static string BuildConfigLogMessage(string targetDirectory, ConfigResult config, st"
},
{
"path": "src/ConfigReader/SharedGlobalUsings.cs",
"chars": 218,
"preview": "global using Polyfills;\nglobal using MarkdownSnippets;\nglobal using System.Runtime.Serialization;\nglobal using System.R"
},
{
"path": "src/ConfigReader.Tests/ConfigReader.Tests.csproj",
"chars": 1335,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>net9.0</TargetFramework>\n <OutputType>Exe</"
},
{
"path": "src/ConfigReader.Tests/ConfigReaderTests.BadJson.verified.txt",
"chars": 426,
"preview": "{\n Type: SnippetException,\n Message:\nFailed to deserialize configuration. Error: There was an error deserializing the"
},
{
"path": "src/ConfigReader.Tests/ConfigReaderTests.Empty.verified.txt",
"chars": 3,
"preview": "{}"
},
{
"path": "src/ConfigReader.Tests/ConfigReaderTests.Values.verified.txt",
"chars": 627,
"preview": "{\n ReadOnly: false,\n ValidateContent: true,\n OmitSnippetLinks: true,\n LinkFormat: Tfs,\n Convention: InPlaceOverwri"
},
{
"path": "src/ConfigReader.Tests/ConfigReaderTests.cs",
"chars": 1598,
"preview": "public class ConfigReaderTests\n{\n [Fact]\n public Task Empty()\n {\n var config = ConfigReader.Parse(\"{}\","
},
{
"path": "src/ConfigReader.Tests/GlobalUsings.cs",
"chars": 35,
"preview": "global using VerifyTests.DiffPlex;"
},
{
"path": "src/ConfigReader.Tests/InPlaceOverwrite.json",
"chars": 136,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json\",\n \"Convention\": \"InP"
},
{
"path": "src/ConfigReader.Tests/ModuleInitializer.cs",
"chars": 160,
"preview": "public static class ModuleInitializer\n{\n [ModuleInitializer]\n public static void Initialize() =>\n VerifyDi"
},
{
"path": "src/ConfigReader.Tests/SourceTransform.json",
"chars": 135,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json\",\n \"Convention\": \"Sou"
},
{
"path": "src/ConfigReader.Tests/allConfig.json",
"chars": 730,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json\",\n \"ReadOnly\": false,"
},
{
"path": "src/Directory.Build.props",
"chars": 1048,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project>\n <PropertyGroup>\n <NoWarn>CS1591;NU1608;NU1109</NoWarn>\n <Versio"
},
{
"path": "src/Directory.Packages.props",
"chars": 2026,
"preview": "<Project>\n <PropertyGroup>\n <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>\n <CentralPackag"
},
{
"path": "src/MarkdownSnippets/AssemblyInfo.cs",
"chars": 1519,
"preview": "[assembly: InternalsVisibleTo(\"Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e191859"
},
{
"path": "src/MarkdownSnippets/ContentValidation.cs",
"chars": 5568,
"preview": "static class ContentValidation\n{\n static FrozenDictionary<string, string> phrases = FrozenDictionary.Create<string, s"
},
{
"path": "src/MarkdownSnippets/ContentValidationException.cs",
"chars": 1110,
"preview": "namespace MarkdownSnippets;\n\npublic class ContentValidationException(IReadOnlyList<ValidationError> errors) :\n Snippe"
},
{
"path": "src/MarkdownSnippets/Downloader/Downloader.cs",
"chars": 2391,
"preview": "static class Downloader\n{\n static string cache = Path.Combine(Path.GetTempPath(), \"MarkdownSnippets\");\n\n static D"
},
{
"path": "src/MarkdownSnippets/Downloader/FileNameFromUrl.cs",
"chars": 561,
"preview": "static class FileNameFromUrl\n{\n static FrozenSet<char> invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInval"
},
{
"path": "src/MarkdownSnippets/Downloader/Timestamp.cs",
"chars": 1381,
"preview": "class Timestamp\n{\n static DateTime minFileDate = DateTime.FromFileTimeUtc(0);\n public DateTime? Expiry;\n publi"
},
{
"path": "src/MarkdownSnippets/Extensions.cs",
"chars": 3195,
"preview": "static class Extensions\n{\n public static bool TryFindNewline(this TextReader reader, [NotNullWhen(true)] out string? "
},
{
"path": "src/MarkdownSnippets/FileEx.cs",
"chars": 1722,
"preview": "static class FileEx\n{\n public static string FixFileCapitalization(string file)\n {\n var fileName = Path.Get"
},
{
"path": "src/MarkdownSnippets/GitRepoDirectoryFinder.cs",
"chars": 1910,
"preview": "namespace MarkdownSnippets;\n\npublic static class GitRepoDirectoryFinder\n{\n public static string FindForFilePath([Cal"
},
{
"path": "src/MarkdownSnippets/GlobalUsings.cs",
"chars": 217,
"preview": "global using System.Collections.Frozen;\nglobal using System.Diagnostics.CodeAnalysis;\nglobal using System.Net;\nglobal u"
},
{
"path": "src/MarkdownSnippets/Guard.cs",
"chars": 1836,
"preview": "static class Guard\n{\n public static void AgainstUpperCase(string value, string argumentName)\n {\n foreach (v"
},
{
"path": "src/MarkdownSnippets/InterpretErrors.cs",
"chars": 1617,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Extension method to convert various error cases.\n/// </summary>\npublic st"
},
{
"path": "src/MarkdownSnippets/KeyValidator.cs",
"chars": 368,
"preview": "static class KeyValidator\n{\n public static bool IsValidKey(CharSpan key)\n {\n if (key.Length == 0)\n "
},
{
"path": "src/MarkdownSnippets/MarkdownProcessingException.cs",
"chars": 554,
"preview": "namespace MarkdownSnippets;\n\npublic class MarkdownProcessingException :\n SnippetException\n{\n public string? File {"
},
{
"path": "src/MarkdownSnippets/MarkdownSnippets.csproj",
"chars": 873,
"preview": "\n<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFrameworks>netstandard2.0;netstandard2.1;net48;net8.0;ne"
},
{
"path": "src/MarkdownSnippets/MissingIncludesException.cs",
"chars": 322,
"preview": "namespace MarkdownSnippets;\n\npublic class MissingIncludesException(IReadOnlyList<MissingInclude> missing) :\n SnippetE"
},
{
"path": "src/MarkdownSnippets/MissingSnippetsException.cs",
"chars": 588,
"preview": "namespace MarkdownSnippets;\n\npublic class MissingSnippetsException(IReadOnlyList<MissingSnippet> missing) :\n SnippetE"
},
{
"path": "src/MarkdownSnippets/NewLineConfigReader.cs",
"chars": 6974,
"preview": "static class NewLineConfigReader\n{\n public static string ReadNewLine(string directory, IEnumerable<string> mdFiles)\n "
},
{
"path": "src/MarkdownSnippets/Paths.cs",
"chars": 562,
"preview": "static class Paths\n{\n public static bool IsMdFile(this string value) =>\n value.EndsWith(\".md\", StringCompariso"
},
{
"path": "src/MarkdownSnippets/Processing/AppendSnippetsToMarkdown.cs",
"chars": 145,
"preview": "namespace MarkdownSnippets;\n\npublic delegate void AppendSnippetsToMarkdown(string key, IEnumerable<Snippet> snippets, Ac"
},
{
"path": "src/MarkdownSnippets/Processing/DirectoryMarkdownProcessor.cs",
"chars": 11586,
"preview": "namespace MarkdownSnippets;\n\npublic class DirectoryMarkdownProcessor\n{\n DocumentConvention convention;\n bool writ"
},
{
"path": "src/MarkdownSnippets/Processing/DocumentConvention.cs",
"chars": 105,
"preview": "namespace MarkdownSnippets;\n\npublic enum DocumentConvention\n{\n SourceTransform,\n InPlaceOverwrite\n}"
},
{
"path": "src/MarkdownSnippets/Processing/HeaderWriter.cs",
"chars": 1264,
"preview": "static class HeaderWriter\n{\n static string[] defaultHeaderLines;\n\n internal const string DefaultHeader =\n "
},
{
"path": "src/MarkdownSnippets/Processing/IncludeProcessor.cs",
"chars": 8925,
"preview": "class IncludeProcessor\n{\n DocumentConvention convention;\n Dictionary<string, Include> includesLookup;\n IReadOnl"
},
{
"path": "src/MarkdownSnippets/Processing/Line.cs",
"chars": 1323,
"preview": "[DebuggerDisplay(\"Line={LineNumber}, Original={Original}, Current={Current}\")]\nclass Line\n{\n public Line(string origi"
},
{
"path": "src/MarkdownSnippets/Processing/Lines.cs",
"chars": 846,
"preview": "static class Lines\n{\n public static void RemoveUntil(\n this List<Line> lines,\n int index,\n stri"
},
{
"path": "src/MarkdownSnippets/Processing/LinkFormat.cs",
"chars": 124,
"preview": "namespace MarkdownSnippets;\n\npublic enum LinkFormat\n{\n GitHub,\n Tfs,\n Bitbucket,\n GitLab,\n DevOps,\n No"
},
{
"path": "src/MarkdownSnippets/Processing/Markdown.cs",
"chars": 277,
"preview": "static class Markdown\n{\n static Regex stripLinkRegex = new(@\"\\[(.*?)\\][\\[\\(].*?[\\]\\)]\", RegexOptions.Compiled);\n\n "
},
{
"path": "src/MarkdownSnippets/Processing/MarkdownProcessor.cs",
"chars": 15692,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Merges <see cref=\"Snippet\"/>s with an input file/text.\n/// </summary>\npub"
},
{
"path": "src/MarkdownSnippets/Processing/MissingInclude.cs",
"chars": 1430,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Part of <see cref=\"ProcessResult\"/>.\n/// </summary>\n[DebuggerDisplay(\"Key"
},
{
"path": "src/MarkdownSnippets/Processing/MissingSnippet.cs",
"chars": 1430,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Part of <see cref=\"ProcessResult\"/>.\n/// </summary>\n[DebuggerDisplay(\"Key"
},
{
"path": "src/MarkdownSnippets/Processing/ProcessResult.cs",
"chars": 2183,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// The result of <see cref=\"MarkdownProcessor\"/> Apply methods.\n/// </summar"
},
{
"path": "src/MarkdownSnippets/Processing/RelativeFile.cs",
"chars": 2201,
"preview": "static class RelativeFile\n{\n static bool InnerFind(IReadOnlyList<string> allFiles, string targetDirectory, string key"
},
{
"path": "src/MarkdownSnippets/Processing/SimpleSnippetMarkdownHandling.cs",
"chars": 781,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Simple markdown handling to be passed to <see cref=\"MarkdownProcessor\"/>."
},
{
"path": "src/MarkdownSnippets/Processing/SnippetKey.cs",
"chars": 5871,
"preview": "static class SnippetKey\n{\n public static bool ExtractStartCommentSnippet(Line line, [NotNullWhen(true)] out string? k"
},
{
"path": "src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs",
"chars": 5226,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Handling to be passed to <see cref=\"MarkdownProcessor\"/>.\n/// </summary>\n"
},
{
"path": "src/MarkdownSnippets/Processing/TocBuilder.cs",
"chars": 2801,
"preview": "static class TocBuilder\n{\n public static string BuildToc(List<Line> headerLines, int level, List<string> tocExcludes,"
},
{
"path": "src/MarkdownSnippets/Processing/ValidationError.cs",
"chars": 1580,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Part of <see cref=\"ProcessResult\"/>.\n/// </summary>\n[DebuggerDisplay(\"Err"
},
{
"path": "src/MarkdownSnippets/Reading/EndFunc.cs",
"chars": 37,
"preview": "delegate bool EndFunc(CharSpan line);"
},
{
"path": "src/MarkdownSnippets/Reading/Exclusions/DefaultDirectoryExclusions.cs",
"chars": 781,
"preview": "namespace MarkdownSnippets;\n\npublic static class DefaultDirectoryExclusions\n{\n public static bool ShouldExcludeDirec"
},
{
"path": "src/MarkdownSnippets/Reading/Exclusions/SnippetFileExclusions.cs",
"chars": 7737,
"preview": "namespace MarkdownSnippets;\n\npublic static class SnippetFileExclusions\n{\n public static bool IsBinary(string extensi"
},
{
"path": "src/MarkdownSnippets/Reading/FileFinder.cs",
"chars": 3564,
"preview": "class FileFinder(\n string targetDirectory,\n DocumentConvention convention,\n ShouldIncludeDirectory directoryIn"
},
{
"path": "src/MarkdownSnippets/Reading/FileSnippetExtractor.cs",
"chars": 8459,
"preview": "namespace MarkdownSnippets;\n\n/// <summary>\n/// Extracts <see cref=\"Snippet\"/>s from a given input.\n/// </summary>\npubli"
},
{
"path": "src/MarkdownSnippets/Reading/IContent.cs",
"chars": 85,
"preview": "namespace MarkdownSnippets;\n\npublic interface IContent\n{\n string? Path { get; }\n}"
},
{
"path": "src/MarkdownSnippets/Reading/Include.cs",
"chars": 2058,
"preview": "namespace MarkdownSnippets;\n\n[DebuggerDisplay(\"Key={Key}, Path={Path}, Error={Error}\")]\npublic class Include :\n ICon"
},
{
"path": "src/MarkdownSnippets/Reading/LineTooLongException.cs",
"chars": 100,
"preview": "class LineTooLongException(string line) :\n Exception\n{\n public string Line { get; } = line;\n}"
},
{
"path": "src/MarkdownSnippets/Reading/LoopStack.cs",
"chars": 890,
"preview": "[DebuggerDisplay(\"Depth={stack.Count}, IsInSnippet={IsInSnippet}\")]\nclass LoopStack\n{\n public bool IsInSnippet => st"
},
{
"path": "src/MarkdownSnippets/Reading/LoopState.cs",
"chars": 2214,
"preview": "[DebuggerDisplay(\"Key={Key}\")]\nclass LoopState(string key, EndFunc endFunc, int startLine, int maxWidth, string newLine"
},
{
"path": "src/MarkdownSnippets/Reading/ReadSnippets.cs",
"chars": 1176,
"preview": "namespace MarkdownSnippets;\n\n[DebuggerDisplay(\"Count={Snippets.Count}\")]\npublic class ReadSnippets :\n IEnumerable<Sn"
},
{
"path": "src/MarkdownSnippets/Reading/ShouldIncludeDirectory.cs",
"chars": 96,
"preview": "namespace MarkdownSnippets;\n\npublic delegate bool ShouldIncludeDirectory(string directoryPath);"
},
{
"path": "src/MarkdownSnippets/Reading/ShouldIncludeFile.cs",
"chars": 86,
"preview": "namespace MarkdownSnippets;\n\npublic delegate bool ShouldIncludeFile(string filePath);\n"
},
{
"path": "src/MarkdownSnippets/Reading/Snippet.cs",
"chars": 5225,
"preview": "namespace MarkdownSnippets;\n\n[DebuggerDisplay(\"Key={Key}, FileLocation={FileLocation}, Error={Error}\")]\npublic class Sn"
},
{
"path": "src/MarkdownSnippets/Reading/StartEndTester.cs",
"chars": 5930,
"preview": "static class StartEndTester\n{\n internal static bool IsStartOrEnd(CharSpan line)\n {\n var trimmedLine = line."
},
{
"path": "src/MarkdownSnippets/SnippetException.cs",
"chars": 100,
"preview": "namespace MarkdownSnippets;\n\npublic class SnippetException(string message) :\n Exception(message);"
},
{
"path": "src/MarkdownSnippets/SnippetExtensions.cs",
"chars": 938,
"preview": "static class SnippetExtensions\n{\n public static Dictionary<string, IReadOnlyList<Snippet>> ToDictionary(this IEnumera"
},
{
"path": "src/MarkdownSnippets/SnippetReadingException.cs",
"chars": 114,
"preview": "namespace MarkdownSnippets;\n\npublic class SnippetReadingException(string message) :\n SnippetException(message);"
},
{
"path": "src/MarkdownSnippets/StringBuilderCache.cs",
"chars": 1027,
"preview": "static class StringBuilderCache\n{\n const int MAX_BUILDER_SIZE = 360;\n\n [ThreadStatic]\n static StringBuilder? Ca"
},
{
"path": "src/MarkdownSnippets.MsBuild/DocoTask.cs",
"chars": 6613,
"preview": "using Microsoft.Build.Framework;\nusing Task = Microsoft.Build.Utilities.Task;\n\nnamespace MarkdownSnippets;\n\npublic class"
},
{
"path": "src/MarkdownSnippets.MsBuild/LoggingHelper.cs",
"chars": 278,
"preview": "using Microsoft.Build.Utilities;\n\nstatic class LoggingHelper\n{\n public static void LogFileError(this TaskLoggingHelpe"
},
{
"path": "src/MarkdownSnippets.MsBuild/MarkdownSnippets.MsBuild.csproj",
"chars": 2487,
"preview": "\n<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFrameworks>netstandard2.0;net10.0</TargetFrameworks>\n "
},
{
"path": "src/MarkdownSnippets.MsBuild/MarkdownSnippets.MsBuild.targets",
"chars": 1217,
"preview": "<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n <PropertyGroup>\n <Markdow"
},
{
"path": "src/MarkdownSnippets.Tool/AssemblyInfo.cs",
"chars": 395,
"preview": "[assembly: InternalsVisibleTo(\"MarkdownSnippets.Tool.Tests, PublicKey=0024000004800000940000000602000000240000525341310"
},
{
"path": "src/MarkdownSnippets.Tool/CommandLineException.cs",
"chars": 69,
"preview": "class CommandLineException(string message) :\n Exception(message);"
},
{
"path": "src/MarkdownSnippets.Tool/CommandRunner.cs",
"chars": 3927,
"preview": "static class CommandRunner\n{\n public static Task RunCommand(Invoke invoke, params string[] args)\n {\n if (a"
},
{
"path": "src/MarkdownSnippets.Tool/GlobalUsings.cs",
"chars": 81,
"preview": "global using CommandLine;\nglobal using MarkdownSnippets;\nglobal using Polyfills;"
},
{
"path": "src/MarkdownSnippets.Tool/Invoke.cs",
"chars": 67,
"preview": "public delegate Task Invoke(string directory, ConfigInput config);"
},
{
"path": "src/MarkdownSnippets.Tool/MarkdownSnippets.Tool.csproj",
"chars": 940,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net10.0</Targe"
},
{
"path": "src/MarkdownSnippets.Tool/Options.cs",
"chars": 4546,
"preview": "public class Options\n{\n [Option('t', \"target-directory\",\n Required = false,\n HelpText = \"The target di"
},
{
"path": "src/MarkdownSnippets.Tool/Program.cs",
"chars": 2374,
"preview": "var stopwatch = Stopwatch.StartNew();\ntry\n{\n await CommandRunner.RunCommand(Inner, args);\n}\ncatch (CommandLineExcept"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ConventionLong.verified.txt",
"chars": 97,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n Convention: InPlaceOverwrite\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ConventionShort.verified.txt",
"chars": 97,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n Convention: InPlaceOverwrite\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.Empty.verified.txt",
"chars": 61,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {}\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeLong.verified.txt",
"chars": 106,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ExcludeDirectories: [\n dir\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeMarkdownDirectoriesLong.verified.txt",
"chars": 114,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ExcludeMarkdownDirectories: [\n dir\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeMultiple.verified.txt",
"chars": 119,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ExcludeDirectories: [\n dir1,\n dir2\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeShort.verified.txt",
"chars": 106,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ExcludeDirectories: [\n dir\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ExcludeSnippetDirectoriesLong.verified.txt",
"chars": 113,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ExcludeSnippetDirectories: [\n dir\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.Header.verified.txt",
"chars": 87,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n Header: the header\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.LinkFormatLong.verified.txt",
"chars": 84,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n LinkFormat: Tfs\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.LinkFormatShort.verified.txt",
"chars": 84,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n LinkFormat: Tfs\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.MaxWidthLong.verified.txt",
"chars": 80,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n MaxWidth: 5\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.OmitSnippetLinks.verified.txt",
"chars": 91,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n OmitSnippetLinks: true\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ReadOnlyLong.verified.txt",
"chars": 84,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ReadOnly: false\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ReadOnlyShort.verified.txt",
"chars": 84,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ReadOnly: false\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.SingleUnNamedArg.verified.txt",
"chars": 46,
"preview": "{\n targetDirectory: dir,\n configInput: {}\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TargetDirectoryLong.verified.txt",
"chars": 65,
"preview": "{\n targetDirectory: {ProjectDirectory}bin/,\n configInput: {}\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TargetDirectoryShort.verified.txt",
"chars": 65,
"preview": "{\n targetDirectory: {ProjectDirectory}bin/,\n configInput: {}\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.TocLevelLong.verified.txt",
"chars": 80,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n TocLevel: 5\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlPrefix.verified.txt",
"chars": 90,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n UrlPrefix: the prefix\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsLong.verified.txt",
"chars": 102,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n UrlsAsSnippets: [\n url\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsMultiple.verified.txt",
"chars": 115,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n UrlsAsSnippets: [\n url1,\n url2\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.UrlsAsSnippetsShort.verified.txt",
"chars": 102,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n UrlsAsSnippets: [\n url\n ]\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ValidateContentLong.verified.txt",
"chars": 91,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ValidateContent: false\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.ValidateContentShort.verified.txt",
"chars": 91,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n ValidateContent: false\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.VerifyContentLong.verified.txt",
"chars": 0,
"preview": ""
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.VerifyContentShort.verified.txt",
"chars": 0,
"preview": ""
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.WriteHeader.verified.txt",
"chars": 87,
"preview": "{\n targetDirectory: {CurrentDirectory},\n configInput: {\n WriteHeader: false\n }\n}"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/CommandRunnerTests.cs",
"chars": 5414,
"preview": "public class CommandRunnerTests\n{\n string? targetDirectory;\n ConfigInput? configInput;\n\n [Fact]\n public asy"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/GlobalUsings.cs",
"chars": 66,
"preview": "global using MarkdownSnippets;\nglobal using VerifyTests.DiffPlex;"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessage.DotNet10_0.verified.txt",
"chars": 491,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: Tfs\n Convention: InPlaceOverwrite\n TocLevel:"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessage.DotNet9_0.verified.txt",
"chars": 519,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: Tfs\n Convention: InPlaceOverwrite\n TocLevel:"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageMinimal.DotNet10_0.verified.txt",
"chars": 310,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: GitHub\n Convention: SourceTransform\n TocLeve"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageMinimal.DotNet9_0.verified.txt",
"chars": 339,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: GitHub\n Convention: SourceTransform\n TocLeve"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet10_0.verified.txt",
"chars": 572,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: Tfs\n Convention: SourceTransform\n TocLevel: "
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.BuildConfigLogMessageSourceTransform.DotNet9_0.verified.txt",
"chars": 600,
"preview": "Config:\n TargetDirectory: theRoot\n UrlPrefix: \n LinkFormat: Tfs\n Convention: SourceTransform\n TocLevel: "
},
{
"path": "src/MarkdownSnippets.Tool.Tests/LogBuilderTests.cs",
"chars": 2012,
"preview": "public class LogBuilderTests\n{\n [Fact]\n public Task BuildConfigLogMessage()\n {\n var config = new Config"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/MarkdownSnippets.Tool.Tests.csproj",
"chars": 1029,
"preview": "\n<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <TargetFramework>net10.0</TargetFramework>\n <OutputType>Exe<"
},
{
"path": "src/MarkdownSnippets.Tool.Tests/ModuleInitializer.cs",
"chars": 279,
"preview": "public static class ModuleInitializer\n{\n [ModuleInitializer]\n public static void Initialize()\n {\n Verif"
},
{
"path": "src/MarkdownSnippets.slnx",
"chars": 753,
"preview": "<Solution>\n <Folder Name=\"/Solution Items/\">\n <File Path=\"../readme.source.md\" />\n <File Path=\"../schema.json\" />"
},
{
"path": "src/MarkdownSnippets.slnx.DotSettings",
"chars": 932,
"preview": "<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namesp"
},
{
"path": "src/Shared.sln.DotSettings",
"chars": 26057,
"preview": "<wpf:ResourceDictionary xml:space=\"preserve\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:s=\"clr-namesp"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWord.verified.txt",
"chars": 66,
"preview": "[\n {\n Item1: Invalid word detected: 'you',\n Item2: 1\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessage.verified.txt",
"chars": 344,
"preview": "[\n {\n Item1: No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcent"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordIndicatesAllViolationsInTheExceptionMessageIgnoringCase.verified.txt",
"chars": 407,
"preview": "[\n {\n Item1: No exclamation marks. If a statement is important make it bold. https://www.technicalcommunicationcent"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordSentenceEnd.verified.txt",
"chars": 66,
"preview": "[\n {\n Item1: Invalid word detected: 'you',\n Item2: 1\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordSentenceStart.verified.txt",
"chars": 52,
"preview": "[\n {\n Item1: Invalid word detected: 'you'\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordStringEnd.verified.txt",
"chars": 66,
"preview": "[\n {\n Item1: Invalid word detected: 'you',\n Item2: 4\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordWithComma.verified.txt",
"chars": 66,
"preview": "[\n {\n Item1: Invalid word detected: 'you',\n Item2: 1\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.CheckInvalidWordWithQuestionMark.verified.txt",
"chars": 66,
"preview": "[\n {\n Item1: Invalid word detected: 'you',\n Item2: 1\n }\n]"
},
{
"path": "src/Tests/ContentValidationTest.cs",
"chars": 1758,
"preview": "public class ContentValidationTest\n{\n [Fact]\n public Task CheckInvalidWord() => Verify(ContentValidation.Verify(\" "
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/BinaryFileSnippet/one.source.md",
"chars": 23,
"preview": "snippet: sourceFile.dot"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/BinaryFileSnippet/sourceFile.dot",
"chars": 16,
"preview": "From Source File"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/Convention/mdsource/two.source.md",
"chars": 17,
"preview": "snippet: snippet2"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/Convention/one.source.md",
"chars": 17,
"preview": "snippet: snippet1"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ConventionWithNestedDir/mdsource/Nested/one.source.md",
"chars": 17,
"preview": "snippet: snippet1"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude3.txt",
"chars": 18,
"preview": "The include text 3"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude4.txt",
"chars": 18,
"preview": "The include text 4"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude5.txt",
"chars": 18,
"preview": "The include text 5"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/Nested/fileToInclude6.txt",
"chars": 18,
"preview": "The include text 6"
},
{
"path": "src/Tests/DirectoryMarkdownProcessor/ExplicitFileInclude/fileToInclude1.txt",
"chars": 18,
"preview": "The include text 1"
}
]
// ... and 231 more files (download for full content)
About this extraction
This page contains the full source code of the SimonCropp/MarkdownSnippets GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 431 files (495.7 KB), approximately 132.6k tokens, and a symbol index with 613 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.