diff --git a/buildtools/doc-build.proj b/buildtools/doc-build.proj index 009736bc7bc0..671490c17ff3 100644 --- a/buildtools/doc-build.proj +++ b/buildtools/doc-build.proj @@ -16,6 +16,11 @@ $(MSBuildProjectDirectory)\..\DocDeployment\docs + + + $(DocGeneratorPath)\..\..\example_meta.json + + $(MSBuildProjectDirectory)\..\docgenerator @@ -38,7 +43,7 @@ - + diff --git a/docgenerator/SDKDocGenerator.UnitTests/ExampleMetadataParserTests.cs b/docgenerator/SDKDocGenerator.UnitTests/ExampleMetadataParserTests.cs new file mode 100644 index 000000000000..c8fe37f46d71 --- /dev/null +++ b/docgenerator/SDKDocGenerator.UnitTests/ExampleMetadataParserTests.cs @@ -0,0 +1,199 @@ +using System; +using System.IO; +using System.Linq; +using Xunit; + +namespace SDKDocGenerator.UnitTests +{ + public class ExampleMetadataParserTests + { + private readonly string _testMetaJsonPath; + + public ExampleMetadataParserTests() + { + _testMetaJsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "SDKDocGenerator", "example_meta.json"); + } + + [Fact] + public void GenerateExampleFragments_WithValidFile_ShouldSetFragmentsPath() + { + ExampleMetadataParser.GenerateExampleFragments(_testMetaJsonPath); + + try + { + Assert.False(string.IsNullOrEmpty(ExampleMetadataParser.ExampleFragmentsFullPath)); + Assert.True(Directory.Exists(ExampleMetadataParser.ExampleFragmentsFullPath)); + } + finally + { + ExampleMetadataParser.CleanupExampleFragments(); + } + } + + [Fact] + public void GenerateExampleFragments_WithValidFile_ShouldCreateControlTowerFragment() + { + ExampleMetadataParser.GenerateExampleFragments(_testMetaJsonPath); + try + { + var fragmentPath = Path.Combine(ExampleMetadataParser.ExampleFragmentsFullPath, "ControlTower.fragment.html"); + Assert.True(File.Exists(fragmentPath)); + + var content = File.ReadAllText(fragmentPath); + Assert.Contains("AWS SDK Code Examples Code Library", content); + Assert.Contains("

Api

", content); + Assert.Contains("Use DisableBaseline", content); + Assert.Contains("Use DisableControl", content); + Assert.Contains("Use EnableBaseline", content); + Assert.Contains("Use EnableControl", content); + Assert.Contains("Use GetBaselineOperation", content); + Assert.Contains("Use GetControlOperation", content); + Assert.Contains("Use ListBaselines", content); + Assert.Contains("Use ListEnabledBaselines", content); + Assert.Contains("Use ListEnabledControls", content); + Assert.Contains("Use ListLandingZones", content); + Assert.Contains("Use ResetEnabledBaseline", content); + } + finally + { + ExampleMetadataParser.CleanupExampleFragments(); + } + } + + [Fact] + public void GenerateExampleFragments_WithNonExistentFile_ShouldNotThrow() + { + var nonExistentPath = Path.Combine(Path.GetTempPath(), "nonexistent.json"); + try + { + var exception = Record.Exception(() => ExampleMetadataParser.GenerateExampleFragments(nonExistentPath)); + Assert.Null(exception); + } + finally + { + ExampleMetadataParser.CleanupExampleFragments(); + } + } + + [Fact] + public void CleanupExampleFragments_ShouldRemoveDirectory() + { + ExampleMetadataParser.GenerateExampleFragments(_testMetaJsonPath); + var fragmentsPath = ExampleMetadataParser.ExampleFragmentsFullPath; + try + { + Assert.True(Directory.Exists(fragmentsPath)); + } + finally + { + ExampleMetadataParser.CleanupExampleFragments(); + } + + Assert.False(Directory.Exists(fragmentsPath)); + } + + [Fact] + public void CleanupExampleFragments_WithNullPath_ShouldNotThrow() + { + var exception = Record.Exception(() => ExampleMetadataParser.CleanupExampleFragments()); + Assert.Null(exception); + } + + [Fact] + public void SanitizeStringForClassName_ShouldRemoveAWSAndAmazon() + { + var result = ExampleMetadataParser.SanitizeStringForClassName("AWS Control Tower"); + Assert.Equal("ControlTower", result); + + result = ExampleMetadataParser.SanitizeStringForClassName("Amazon S3"); + Assert.Equal("S3", result); + } + + [Fact] + public void ToUpperFirstCharacter_ShouldCapitalizeFirstLetter() + { + var result = ExampleMetadataParser.ToUpperFirstCharacter("controlTower"); + Assert.Equal("ControlTower", result); + + result = ExampleMetadataParser.ToUpperFirstCharacter("s"); + Assert.Equal("S", result); + } + + [Fact] + public void GenerateExampleFragments_WithInvalidJson_ShouldCreateFailureFile() + { + var tempJsonFile = Path.GetTempFileName(); + var failureFile = Path.GetTempFileName(); + + try + { + File.WriteAllText(tempJsonFile, "{ \"bad file\": \"test\"}"); + + if (File.Exists(failureFile)) + File.Delete(failureFile); + + ExampleMetadataParser.GenerateExampleFragments(tempJsonFile, failureFile); + + Assert.True(File.Exists(failureFile)); + var content = File.ReadAllText(failureFile); + Assert.Contains("Failed to generate example fragments: System.Exception: Examples could not be found.", content); + } + finally + { + if (File.Exists(tempJsonFile)) + File.Delete(tempJsonFile); + if (File.Exists(failureFile)) + File.Delete(failureFile); + ExampleMetadataParser.CleanupExampleFragments(); + } + } + + [Fact] + public void GenerateExampleFragments_WithNullPath_ShouldCreateFailureFile() + { + var failureFile = Path.GetTempFileName(); + + try + { + if (File.Exists(failureFile)) + File.Delete(failureFile); + + ExampleMetadataParser.GenerateExampleFragments(null, failureFile); + + Assert.True(File.Exists(failureFile)); + var content = File.ReadAllText(failureFile); + Assert.Contains("Example metadata file has not been specified.", content); + } + finally + { + if (File.Exists(failureFile)) + File.Delete(failureFile); + ExampleMetadataParser.CleanupExampleFragments(); + } + } + + [Fact] + public void GenerateExampleFragments_WithEmptyPath_ShouldCreateFailureFile() + { + var failureFile = Path.GetTempFileName(); + + try + { + if (File.Exists(failureFile)) + File.Delete(failureFile); + + ExampleMetadataParser.GenerateExampleFragments("", failureFile); + + Assert.True(File.Exists(failureFile)); + var content = File.ReadAllText(failureFile); + Assert.Contains("Example metadata file has not been specified.", content); + } + finally + { + if (File.Exists(failureFile)) + File.Delete(failureFile); + ExampleMetadataParser.CleanupExampleFragments(); + } + } + } +} \ No newline at end of file diff --git a/docgenerator/SDKDocGenerator/CommandLineParser.cs b/docgenerator/SDKDocGenerator/CommandLineParser.cs index 7b4f12da2672..0412c01d73d8 100644 --- a/docgenerator/SDKDocGenerator/CommandLineParser.cs +++ b/docgenerator/SDKDocGenerator/CommandLineParser.cs @@ -238,7 +238,23 @@ class ArgDeclaration HasValue = true, Parse = (arguments, argValue) => arguments.ParsedOptions.OutputFolder = argValue, HelpText = "The root folder beneath which the generated documentation will be placed." - } + }, + new ArgDeclaration + { + OptionName = "examplemetajson", + ShortName = "emj", + HasValue = true, + Parse = (arguments, argValue) => arguments.ParsedOptions.ExampleMetaJson = argValue, + HelpText = "The path to the example_meta.json file." + }, + new ArgDeclaration + { + OptionName = "exampleserrorfile", + ShortName = "eef", + HasValue = true, + Parse = (arguments, argValue) => arguments.ParsedOptions.ExamplesErrorFile = argValue, + HelpText = "The path and filename for the examples error file. Defaults to 'examples_failure.txt'." + }, }; static readonly char[] ArgumentPrefixes = { '-', '/' }; diff --git a/docgenerator/SDKDocGenerator/example_meta.json b/docgenerator/SDKDocGenerator/example_meta.json new file mode 100644 index 000000000000..52e294ed118d --- /dev/null +++ b/docgenerator/SDKDocGenerator/example_meta.json @@ -0,0 +1,1426 @@ +{ + "entities": { + "&CTowerlong;": "AWS Control Tower", + "&CTower;": "AWS Control Tower", + "&NETlong;": "AWS SDK for .NET", + "&NET;": "SDK for .NET" + }, + "categories": { + "Hello": { + "key": "Hello", + "display": "Hello", + "defaults": null, + "overrides": { + "title": "Hello {{.ServiceEntity.Short}}", + "title_abbrev": "Hello {{.ServiceEntity.Short}}", + "synopsis": "get started using {{.ServiceEntity.Short}}.", + "title_suffixes": {} + }, + "description": null, + "synopsis_prefix": null, + "more_info": null + }, + "Actions": { + "key": "Actions", + "display": "Actions", + "defaults": null, + "overrides": { + "title": "Use {{.Action}}", + "title_abbrev": "{{.Action}}", + "synopsis": "use {{.Action}}.", + "title_suffixes": { + "cli": " with a CLI", + "sdk": " with an &AWS; SDK", + "sdk_cli": " with an &AWS; SDK or CLI" + } + }, + "description": "are code excerpts from larger programs and must be run in context. While actions show you how to call individual service functions, you can see actions in context in their related scenarios.", + "synopsis_prefix": null, + "more_info": null + }, + "Basics": { + "key": "Basics", + "display": "Basics", + "defaults": { + "title": "Learn the basics of {{.ServiceEntity.Short}} with an AWS SDK", + "title_abbrev": "Learn the basics", + "synopsis": null, + "title_suffixes": {} + }, + "overrides": null, + "description": "are code examples that show you how to perform the essential operations within a service.", + "synopsis_prefix": null, + "more_info": null + }, + "Scenarios": { + "key": "Scenarios", + "display": "Scenarios", + "defaults": null, + "overrides": null, + "description": "are code examples that show you how to accomplish specific tasks by calling multiple functions within a service or combined with other AWS services.", + "synopsis_prefix": null, + "more_info": null + }, + "TributaryLite": { + "key": "TributaryLite", + "display": "AWS community contributions", + "defaults": null, + "overrides": null, + "description": "are examples that were created and are maintained by multiple teams across AWS. To provide feedback, use the mechanism provided in the linked repositories.", + "synopsis_prefix": null, + "more_info": null + }, + "IAMPolicy": { + "key": "IAMPolicy", + "display": "IAM policies", + "defaults": null, + "overrides": null, + "description": "are example policies that demonstrate how to specify permissions to AWS resources.", + "synopsis_prefix": { + "one": "", + "many": "" + }, + "more_info": "For more information on IAM policies, see Managed policies and inline policies." + } + }, + "services": { + "controltower": { + "long": "AWS Control Tower", + "short": "AWS Control Tower", + "sort": "Control Tower", + "sdk_id": "ControlTower", + "version": "controltower-2018-05-10", + "expanded": { + "long": "AWS Control Tower", + "short": "AWS Control Tower" + }, + "api_ref": "controltower/latest/APIReference/Welcome.html", + "blurb": "enables you to enforce and manage governance rules for security, operations, and compliance at scale across all your organizations and accounts.", + "bundle": null, + "caveat": null, + "guide": { + "subtitle": "User Guide", + "url": "controltower/latest/userguide/what-is-control-tower.html" + }, + "tags": { + "product_categories": { + "__set__": [ + "Management & Governance" + ] + } + } + } + }, + "examples": { + "controltower_Hello": { + "id": "controltower_Hello", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.Hello" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.HelloControlTower" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Hello AWS Control Tower", + "title_abbrev": "Hello AWS Control Tower", + "synopsis": "get started using AWS Control Tower.", + "category": "Hello", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ListBaselines" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_Hello_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#scenarios" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#scenarios" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_ListBaselines": { + "id": "controltower_ListBaselines", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.ListBaselines" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.ListBaselines" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use ListBaselines", + "title_abbrev": "ListBaselines", + "synopsis": "use ListBaselines.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ListBaselines" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_ListBaselines_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_ListEnabledBaselines": { + "id": "controltower_ListEnabledBaselines", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.ListEnabledBaselines" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.ListEnabledBaselines" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use ListEnabledBaselines", + "title_abbrev": "ListEnabledBaselines", + "synopsis": "use ListEnabledBaselines.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ListEnabledBaselines" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_ListEnabledBaselines_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_EnableBaseline": { + "id": "controltower_EnableBaseline", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.EnableBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.EnableBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use EnableBaseline", + "title_abbrev": "EnableBaseline", + "synopsis": "use EnableBaseline.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "EnableBaseline" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_EnableBaseline_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_ResetEnabledBaseline": { + "id": "controltower_ResetEnabledBaseline", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.ResetEnabledBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.ResetEnabledBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use ResetEnabledBaseline", + "title_abbrev": "ResetEnabledBaseline", + "synopsis": "use ResetEnabledBaseline.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ResetEnabledBaseline" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_ResetEnabledBaseline_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_DisableBaseline": { + "id": "controltower_DisableBaseline", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.DisableBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.DisableBaseline" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use DisableBaseline", + "title_abbrev": "DisableBaseline", + "synopsis": "use DisableBaseline.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "DisableBaseline" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_DisableBaseline_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_ListEnabledControls": { + "id": "controltower_ListEnabledControls", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.ListEnabledControls" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.ListEnabledControls" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use ListEnabledControls", + "title_abbrev": "ListEnabledControls", + "synopsis": "use ListEnabledControls.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ListEnabledControls" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_ListEnabledControls_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_EnableControl": { + "id": "controltower_EnableControl", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.EnableControl" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.EnableControl" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use EnableControl", + "title_abbrev": "EnableControl", + "synopsis": "use EnableControl.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "EnableControl" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_EnableControl_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_GetControlOperation": { + "id": "controltower_GetControlOperation", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.GetControlOperation" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.GetControlOperation" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use GetControlOperation", + "title_abbrev": "GetControlOperation", + "synopsis": "use GetControlOperation.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "GetControlOperation" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_GetControlOperation_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_DisableControl": { + "id": "controltower_DisableControl", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.DisableControl" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.DisableControl" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use DisableControl", + "title_abbrev": "DisableControl", + "synopsis": "use DisableControl.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "DisableControl" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_DisableControl_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_ListLandingZones": { + "id": "controltower_ListLandingZones", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.ListLandingZones" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.ListLandingZones" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use ListLandingZones", + "title_abbrev": "ListLandingZones", + "synopsis": "use ListLandingZones.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "ListLandingZones" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_ListLandingZones_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_GetBaselineOperation": { + "id": "controltower_GetBaselineOperation", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "python.example_code.controltower.ControlTowerWrapper.decl", + "python.example_code.controltower.GetBaselineOperation" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": null, + "snippet_tags": [ + "ControlTower.dotnetv4.GetBaselineOperation" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Use GetBaselineOperation", + "title_abbrev": "GetBaselineOperation", + "synopsis": "use GetBaselineOperation.", + "category": "Api", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "GetBaselineOperation" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_GetBaselineOperation_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#actions" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#actions" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [], + "source_key": null, + "suppress_publish": false + }, + "controltower_Scenario": { + "id": "controltower_Scenario", + "file": "controltower_metadata.yaml", + "languages": { + "Python": { + "name": "Python", + "property": "python", + "versions": [ + { + "sdk_version": 3, + "block_content": null, + "excerpts": [ + { + "description": "Run an interactive scenario demonstrating AWS Control Tower features.", + "snippet_tags": [ + "python.example_code.controltower.ControlTowerScenario", + "python.example_code.controltower.ControlTowerWrapper.class" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "python/example_code/controltower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + }, + ".NET": { + "name": ".NET", + "property": "csharp", + "versions": [ + { + "sdk_version": 4, + "block_content": null, + "excerpts": [ + { + "description": "Run an interactive scenario demonstrating AWS Control Tower features.", + "snippet_tags": [ + "ControlTower.dotnetv4.ControlTowerBasics" + ], + "snippet_files": [], + "genai": "none" + }, + { + "description": "Wrapper methods that are called by the scenario to manage Aurora actions.", + "snippet_tags": [ + "ControlTower.dotnetv4.ControlTowerWrapper" + ], + "snippet_files": [], + "genai": "none" + } + ], + "github": "dotnetv4/ControlTower", + "sdkguide": null, + "more_info": [], + "authors": [], + "owner": null, + "source": null + } + ] + } + }, + "title": "Learn the basics of AWS Control Tower with an AWS SDK", + "title_abbrev": "Learn the basics", + "synopsis": "AWS Control Tower DisableControl", + "category": "Basics", + "guide_topic": null, + "service_main": null, + "service_sdk_id": "ControlTower", + "services": { + "controltower": { + "__set__": [ + "DisableControl", + "ListLandingZones", + "DisableBaseline", + "EnableBaseline", + "ResetEnabledBaseline", + "GetLandingZoneOperation", + "EnableControl", + "ListEnabledControls", + "ListEnabledBaselines", + "DeleteLandingZone", + "GetControlOperation", + "ListBaselines", + "CreateLandingZone" + ] + } + }, + "doc_filenames": { + "service_pages": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/controltower_example_controltower_Scenario_section.html" + }, + "sdk_pages": { + "python": { + "3": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/python_3_controltower_code_examples.html#scenarios" + }, + "cross_service": null + } + }, + "csharp": { + "4": { + "actions_scenarios": { + "controltower": "https://docs.aws.amazon.com/code-library/latest/ug/csharp_4_controltower_code_examples.html#scenarios" + }, + "cross_service": null + } + } + } + }, + "synopsis_list": [ + "List landing zones.", + "List, enable, get, reset, and disable baselines.", + "List, enable, get, and disable controls." + ], + "source_key": null, + "suppress_publish": false + } + } +} \ No newline at end of file diff --git a/docgenerator/SDKDocGeneratorLib/ExampleMetadataParser.cs b/docgenerator/SDKDocGeneratorLib/ExampleMetadataParser.cs new file mode 100644 index 000000000000..e69387b5ebce --- /dev/null +++ b/docgenerator/SDKDocGeneratorLib/ExampleMetadataParser.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace SDKDocGenerator +{ + public class ExampleMetadataParser + { + private const string FragmentOutputDirectory = "ExampleFragments"; + private const string BaseCodeLibraryUrl = "https://docs.aws.amazon.com/code-library/latest/ug/dotnet_4"; + public static string ExampleFragmentsFullPath { get; private set; } = string.Empty; + + public static void GenerateExampleFragments(string examplesMetaJsonFile, string examplesErrorFile = "examples_failure.txt") + { + string tempPath = Path.GetTempPath(); + ExampleFragmentsFullPath = Path.Combine(tempPath, $"{FragmentOutputDirectory}-{Guid.NewGuid().ToString()}"); + + try + { + if (string.IsNullOrEmpty(examplesMetaJsonFile)) + { + throw new Exception($"Example metadata file has not been specified."); + } + + if (!File.Exists(examplesMetaJsonFile)) + { + throw new Exception($"Example metadata file not found at {examplesMetaJsonFile}."); + } + + Directory.CreateDirectory(ExampleFragmentsFullPath); + Console.WriteLine($"Created temporary directory for example fragments: {ExampleFragmentsFullPath}"); + + Console.WriteLine($"Reading example metadata from {examplesMetaJsonFile}"); + + var jsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + PropertyNameCaseInsensitive = true + }; + + ExampleMeta meta; + using (var stream = File.OpenRead(examplesMetaJsonFile)) + { + meta = JsonSerializer.Deserialize(stream, jsonOptions); + } + + VerifyExampleMetaData(meta); + + (var examplesBySdkId, var serviceNameBySdkIdLookup) = ParseExamples(meta); + WriteFragments(examplesBySdkId, serviceNameBySdkIdLookup, ExampleFragmentsFullPath, meta); + } + catch (Exception ex) + { + // If anything goes wrong creating example fragments do not allow the doc generation to + // fail. Clean up the example fragments temporary directory and write out a file + // indicating that Code Example generation failed. This will allow the calling system + // to process the failure without the doc generation failing. This is the desired output + // because any errors in the example_meta.json file must be corrected by the owners of + // the file. + Console.WriteLine($"Continuing without examples. Failed to generate example fragments: {ex.Message}"); + File.WriteAllText(examplesErrorFile, $"Failed to generate example fragments: {ex}"); + CleanupExampleFragments(); + } + } + + private static void VerifyExampleMetaData(ExampleMeta meta) + { + if (meta == null) + { + throw new Exception("Failed to parse example metadata."); + } + + if (meta.Examples == null || meta.Examples.Count == 0) + { + throw new Exception("Examples could not be found."); + } + + if (meta.Services == null || meta.Services.Count == 0) + { + throw new Exception("Example services could not be found."); + } + } + + private static (SortedDictionary>, Dictionary) ParseExamples(ExampleMeta meta) + { + var examplesBySdkId = new SortedDictionary>(); + var serviceNameBySdkIdLookup = new Dictionary(); + + foreach (var example in meta.Examples.Values) + { + if (!IsDotNetExample(example)) + continue; + + foreach (var serviceName in example.Services.Keys) + { + var sdkId = meta.GetSdkId(serviceName); + if (string.IsNullOrEmpty(sdkId)) + continue; + + var sanitizedSdkId = SanitizeStringForClassName(sdkId); + + if (!example.DocFilenames.ServicePages.TryGetValue(serviceName, out var link)) + continue; + + var exampleDoc = new ExampleDoc + { + SdkId = sanitizedSdkId, + Category = example.Category ?? "Other", + Title = SanitizeTitle(example.Title), + Link = link + }; + + if (!examplesBySdkId.ContainsKey(sanitizedSdkId)) + examplesBySdkId[sanitizedSdkId] = new List(); + + examplesBySdkId[sanitizedSdkId].Add(exampleDoc); + + if(!serviceNameBySdkIdLookup.ContainsKey(sanitizedSdkId)) + { + serviceNameBySdkIdLookup[sanitizedSdkId] = serviceName; + } + } + } + + return (examplesBySdkId, serviceNameBySdkIdLookup); + } + + private static bool IsDotNetExample(Example example) + { + if (example.Languages == null) + return false; + + return example.Languages.Values.Any(lang => + lang != null && ( + lang.Name.Equals(".NET", StringComparison.OrdinalIgnoreCase) || + lang.Name.Equals("C#", StringComparison.OrdinalIgnoreCase) || + lang.Name.Equals("CSharp", StringComparison.OrdinalIgnoreCase))); + } + + /// + /// Capitalizes the first character of a string, used to create proper naming for services, attributes, and operations + /// + public static string ToUpperFirstCharacter(string s) + { + var txt = s.Substring(0, 1).ToUpperInvariant(); + if (s.Length > 1) + txt += s.Substring(1); + return txt; + } + + public static string SanitizeStringForClassName(string name) + { + if (null == name) + return null; + + string className = name; + className = className.Replace("AWS", ""); + className = className.Replace("Amazon", ""); + + // concatenate all the words by removing whitespace. + className = System.Text.RegularExpressions.Regex.Replace(className, @"[^a-zA-Z0-9]", ""); + + return ToUpperFirstCharacter(className); + } + + + private static string SanitizeTitle(string title) + { + return title.Replace("", "").Replace("", ""); + } + + private static void WriteFragments(SortedDictionary> examplesBySdkId, + Dictionary serviceNameBySdkIdLookup, + string fragmentDir, ExampleMeta meta) + { + foreach (var kvp in examplesBySdkId) + { + var sdkId = kvp.Key; + var examples = kvp.Value; + var serviceName = serviceNameBySdkIdLookup[sdkId]; + + var fragmentPath = Path.Combine(fragmentDir, $"{sdkId}.fragment.html"); + + using (var writer = new StreamWriter(fragmentPath)) + { + WriteServiceExamples(writer, sdkId, serviceName, examples, meta); + } + + Console.WriteLine($"Wrote {examples.Count} examples for {sdkId}"); + } + } + + public static void CleanupExampleFragments() + { + if (!string.IsNullOrEmpty(ExampleFragmentsFullPath) && Directory.Exists(ExampleFragmentsFullPath)) + { + try + { + Directory.Delete(ExampleFragmentsFullPath, true); + } + catch(Exception ex) + { + Console.WriteLine($"Could not delete the {ExampleFragmentsFullPath} directory to clean up the [ServiceName].fragment.html files. Message={ex.Message}"); + } + } + } + + private static void WriteServiceExamples(StreamWriter writer, string sdkId, string serviceName, + List examples, ExampleMeta meta) + { + var rootLink = $"{BaseCodeLibraryUrl}_{serviceName}_code_examples.html"; + + writer.WriteLine("
"); + writer.WriteLine($"

Explore code examples in the AWS SDK Code Examples Code Library.

"); + + var categorizedExamples = examples.GroupBy(e => e.Category).ToList(); + + var singleExamples = categorizedExamples + .Where(g => g.Count() == 1) + .SelectMany(g => g) + .ToList(); + + if (singleExamples.Any()) + { + writer.WriteLine("
    "); + foreach (var example in singleExamples.OrderBy(e => e.Title)) + { + writer.WriteLine($"
  • {example.Title}

  • "); + } + writer.WriteLine("
"); + } + + foreach (var category in categorizedExamples.Where(g => g.Count() > 1)) + { + writer.WriteLine($"

{category.Key}

"); + writer.WriteLine("
    "); + foreach (var example in category.OrderBy(e => e.Title)) + { + writer.WriteLine($"
  • {example.Title}

  • "); + } + writer.WriteLine("
"); + } + + writer.WriteLine("
"); + } + } + + public class ExampleMeta + { + [JsonPropertyName("services")] + public Dictionary Services { get; set; } + + [JsonPropertyName("examples")] + public Dictionary Examples { get; set; } + + public string GetSdkId(string serviceName) + { + return Services.TryGetValue(serviceName, out var service) ? service.SdkId : null; + } + } + + public class Service + { + [JsonPropertyName("sdk_id")] + public string SdkId { get; set; } + } + + public class Example + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("languages")] + public Dictionary Languages { get; set; } + + [JsonPropertyName("category")] + public string Category { get; set; } + + [JsonPropertyName("services")] + public Dictionary Services { get; set; } + + [JsonPropertyName("doc_filenames")] + public DocFilenames DocFilenames { get; set; } + } + + public class Language + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("versions")] + public List Versions { get; set; } + } + + public class LanguageVersion + { + [JsonPropertyName("sdk_version")] + public int SdkVersion { get; set; } + } + + public class ExampleService + { + [JsonPropertyName("__set__")] + public List Operations { get; set; } + } + + public class DocFilenames + { + [JsonPropertyName("service_pages")] + public Dictionary ServicePages { get; set; } + } + + public class ExampleDoc + { + public string SdkId { get; set; } + public string Category { get; set; } + public string Title { get; set; } + public string Link { get; set; } + } +} \ No newline at end of file diff --git a/docgenerator/SDKDocGeneratorLib/GeneratorOptions.cs b/docgenerator/SDKDocGeneratorLib/GeneratorOptions.cs index 896f27b6686f..a07793cc1049 100644 --- a/docgenerator/SDKDocGeneratorLib/GeneratorOptions.cs +++ b/docgenerator/SDKDocGeneratorLib/GeneratorOptions.cs @@ -44,6 +44,17 @@ public class GeneratorOptions /// public string SDKAssembliesRoot { get; set; } + /// + /// The path to the example_meta.json file if example links are to be included. + /// + public string ExampleMetaJson { get; set; } + + /// + /// The path and filename for the examples error file. Defaults to "examples_failure.txt". + /// + public string ExamplesErrorFile { get; set; } = "examples_failure.txt"; + + /// /// The path to _sdk-versions.json /// diff --git a/docgenerator/SDKDocGeneratorLib/SDKDocGeneratorLib.csproj b/docgenerator/SDKDocGeneratorLib/SDKDocGeneratorLib.csproj index e8be2a07bd4f..128786ebb953 100644 --- a/docgenerator/SDKDocGeneratorLib/SDKDocGeneratorLib.csproj +++ b/docgenerator/SDKDocGeneratorLib/SDKDocGeneratorLib.csproj @@ -107,5 +107,6 @@ + \ No newline at end of file diff --git a/docgenerator/SDKDocGeneratorLib/SdkDocGenerator.cs b/docgenerator/SDKDocGeneratorLib/SdkDocGenerator.cs index 7e0fa0535b68..ff0413355359 100644 --- a/docgenerator/SDKDocGeneratorLib/SdkDocGenerator.cs +++ b/docgenerator/SDKDocGeneratorLib/SdkDocGenerator.cs @@ -84,6 +84,8 @@ public int Execute(GeneratorOptions options) Info("...Platform: {0}", Options.Platform); Info("...Services: {0}", string.Join(",", Options.Services)); Info("...CodeSamplesRootFolder: {0}", Options.CodeSamplesRootFolder); + Info("...ExampleMetaJson: {0}", Options.ExampleMetaJson); + Info("...ExamplesErrorFile: {0}", Options.ExamplesErrorFile); Info(""); } @@ -101,6 +103,10 @@ public int Execute(GeneratorOptions options) GenerationManifest coreManifest = null; DeferredTypesProvider deferredTypes = new DeferredTypesProvider(null); + // Generate the Code Examples fragments for all services + ExampleMetadataParser.GenerateExampleFragments(options.ExampleMetaJson, options.ExamplesErrorFile); + + // Process the service manifests foreach (var m in manifests) { if (m.ServiceName.Equals("Core", StringComparison.InvariantCultureIgnoreCase)) @@ -140,6 +146,9 @@ public int Execute(GeneratorOptions options) SDKDocRedirectWriter.Write(stream); } + // Remove example fragments + ExampleMetadataParser.CleanupExampleFragments(); + return 0; } diff --git a/docgenerator/SDKDocGeneratorLib/Writers/ClassWriter.cs b/docgenerator/SDKDocGeneratorLib/Writers/ClassWriter.cs index 20fed2df74b1..f1feb688ce8d 100644 --- a/docgenerator/SDKDocGeneratorLib/Writers/ClassWriter.cs +++ b/docgenerator/SDKDocGeneratorLib/Writers/ClassWriter.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using SDKDocGenerator.Syntax; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Xml.Linq; -using SDKDocGenerator.Syntax; - namespace SDKDocGenerator.Writers { public class ClassWriter : BaseWriter @@ -56,6 +56,7 @@ protected override string GetMemberType() protected override void WriteContent(TextWriter writer) { AddSummaryDocumentation(writer); + AddPageTOC(writer); AddInheritanceHierarchy(writer); AddNamespace(writer, this._versionType.Namespace, this._versionType.ManifestModuleName); @@ -78,9 +79,94 @@ protected override void WriteContent(TextWriter writer) } AddRemarksDocumentation(writer); + + // Add Code Examples section before Version Information + AddCodeExamples(writer); + AddVersionInformation(writer, this._versionType); } + private void AddPageTOC(TextWriter writer) + { + writer.WriteLine("
"); + writer.WriteLine("

In this article

"); + writer.WriteLine("
    "); + + writer.WriteLine("
  • Inheritance Hierarchy
  • "); + writer.WriteLine("
  • Syntax
  • "); + + if (!this._versionType.IsEnum) + { + var constructors = this._versionType.GetConstructors().Where(x => x.IsPublic); + if (constructors.Any()) + writer.WriteLine("
  • Constructors
  • "); + + var properties = this._versionType.GetProperties(); + if (properties.Any()) + writer.WriteLine("
  • Properties
  • "); + + var methods = this._versionType.GetMethodsToDocument(); + if (methods.Any()) + writer.WriteLine("
  • Methods
  • "); + + var events = this._versionType.GetEvents(); + if (events.Any()) + writer.WriteLine("
  • Events
  • "); + + var fields = this._versionType.GetFields(); + if (fields.Any()) + writer.WriteLine("
  • Fields
  • "); + + var serviceId = GetServiceIdFromNamespace(); + var fragmentPath = Path.Combine(ExampleMetadataParser.ExampleFragmentsFullPath, $"{serviceId}.fragment.html"); + if (File.Exists(fragmentPath)) + writer.WriteLine("
  • Code Examples
  • "); + } + else + { + var enumNames = this._versionType.GetEnumNames(); + if (enumNames.Any()) + writer.WriteLine("
  • Members
  • "); + } + + + writer.WriteLine("
  • Version Information
  • "); + writer.WriteLine("
"); + writer.WriteLine("
"); + } + + private void AddCodeExamples(TextWriter writer) + { + var serviceId = GetServiceIdFromNamespace(); + var fragmentPath = Path.Combine(ExampleMetadataParser.ExampleFragmentsFullPath,$"{serviceId}.fragment.html" + ); + + if (!File.Exists(fragmentPath)) + return; + + writer.WriteLine("
"); + writer.WriteLine("
"); + writer.WriteLine("

Code Examples

"); + writer.WriteLine("
"); + writer.WriteLine("
"); + writer.WriteLine("
"); + + var fragmentContent = File.ReadAllText(fragmentPath); + writer.WriteLine(fragmentContent); + + writer.WriteLine("
"); + } + + private string GetServiceIdFromNamespace() + { + if (string.IsNullOrEmpty(this._versionType.Namespace)) + return string.Empty; + + var namespaceParts = this._versionType.Namespace.Split('.'); + var serviceId = namespaceParts[namespaceParts.Length - 1]; + return serviceId; + } + protected override XElement GetSummaryDocumentation() { var element = NDocUtilities.FindDocumentation(this._versionType, TypeProvider); diff --git a/docgenerator/SDKDocGeneratorLib/output-files/resources/pagescript.js b/docgenerator/SDKDocGeneratorLib/output-files/resources/pagescript.js index 478cfc0847da..e225255ad576 100644 --- a/docgenerator/SDKDocGeneratorLib/output-files/resources/pagescript.js +++ b/docgenerator/SDKDocGeneratorLib/output-files/resources/pagescript.js @@ -156,3 +156,29 @@ } }); })(); + +function toggleTOC() { + var tocList = document.getElementById('tocList'); + var tocToggle = document.getElementById('tocToggle'); + + if (tocList && tocToggle) { + if (tocList.style.display === 'none') { + tocList.style.display = 'block'; + tocToggle.innerHTML = '▼'; + } else { + tocList.style.display = 'none'; + tocToggle.innerHTML = '▶'; + } + } +} +window.addEventListener('scroll', function() { + var toc = document.getElementById('pageTOC'); + var tocList = document.getElementById('tocList'); + var tocToggle = document.getElementById('tocToggle'); + + if (toc && tocList && tocToggle && window.scrollY > 200) { + toc.classList.add('collapsed'); + tocList.style.display = 'none'; + tocToggle.innerHTML = '▶'; + } +}); \ No newline at end of file diff --git a/docgenerator/SDKDocGeneratorLib/output-files/resources/sdkstyle.css b/docgenerator/SDKDocGeneratorLib/output-files/resources/sdkstyle.css index 67e401cd1c92..8ab21eead5a1 100644 --- a/docgenerator/SDKDocGeneratorLib/output-files/resources/sdkstyle.css +++ b/docgenerator/SDKDocGeneratorLib/output-files/resources/sdkstyle.css @@ -216,3 +216,62 @@ span.parameter { height:16px; } +/* + * Collapsible TOC + */ +#pageTOC { + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; + padding: 5px; + margin: 15px 0; + transition: all 0.3s ease; +} + + #pageTOC h2 { + margin: 5px 0 5px 0; + font-size: 1.1em; + cursor: pointer; + user-select: none; + color: #495057; + } + + #pageTOC h2:hover { + color: #007bff; + } + + #pageTOC ul { + list-style: none; + margin: 0; + padding: 0; + } + + #pageTOC li { + margin: 5px 0; + } + + #pageTOC a { + color: #007bff; + text-decoration: none; + font-size: 0.9em; + } + + #pageTOC a:hover { + text-decoration: underline; + } + + #pageTOC.collapsed { + position: fixed; + top: 0px; + right: 6px; + width: 150px; + z-index: 1000; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + } + +#tocToggle { + float: right; + font-size: 0.8em; + padding-top: 2px; +} +