diff --git a/README.md b/README.md index 8d185b40..a4762d8d 100644 --- a/README.md +++ b/README.md @@ -351,10 +351,62 @@ There are hooks for both Markdown and Changelog operations (but not for OpenApi) #### Markdown Hooks +##### **macros** + +Allows defining custom macros that can be used in the documentation. + +Macros are reusable pieces of text that can be injected into the documentation, +allowing you to define common pieces of text that you can use across multiple files. + +A common use case is injecting copyright or license information, without +having to copy-paste the same text across multiple classes, polluting your +source code. + +A macro can be defined in your documentation using the `{{macro_name}}` syntax. +In the configuration file, you can then define the macro behavior as a key-value pair, where the key is the name of the macro, and the value is a function that returns the text to inject in place of the macro. + +**Type** + +```typescript +type MacroSourceMetadata = { + type: 'apex' | 'customobject' | 'customfield' | 'custommetadata' | 'trigger'; + name: string; + filePath: string; +}; + +type MacroFunction = (metadata: MacroSourceMetadata) => string; +``` + +Notice that the `metadata` object contains information about the source of the file for which the macro is being injected. This allows you to optionally +return different text based on the source of the file. + +Example: Injecting a copyright notice + +```typescript +//... +macros: { + copyright: () => { + return `Copyright (c) ${new Date().getFullYear()} My Name`; + } +} +//... +``` + +And then in your source code, you can use the macro like this: + +```apex +/** + * {{copyright}} + * @description This is a class + */ +public class MyClass { + //... +} +``` + ##### **transformReferenceGuide** -Allows changing the Allows changing the frontmatter and content of the reference guide, or even if creating a reference -guide page should be skipped. +Allows changing the frontmatter and content of the reference guide, or if creating a reference guide page altogether should be skipped. **Type** diff --git a/examples/docsify/apexdocs.config.ts b/examples/docsify/apexdocs.config.ts index 2bf9616b..87a610c9 100644 --- a/examples/docsify/apexdocs.config.ts +++ b/examples/docsify/apexdocs.config.ts @@ -5,6 +5,9 @@ export default defineMarkdownConfig({ targetDir: 'docs', scope: ['public', 'global'], linkingStrategy: 'none', + macros: { + copyright: () => `@copyright All rights reserved. Cesar Parra ${new Date().getFullYear()}`, + }, transformReferenceGuide: () => { return { outputDocPath: 'README.md', diff --git a/examples/docsify/docs/README.md b/examples/docsify/docs/README.md index 8ea0e3c8..61e91f55 100644 --- a/examples/docsify/docs/README.md +++ b/examples/docsify/docs/README.md @@ -1,5 +1,11 @@ # Reference Guide +## Miscellaneous + +### [ASampleClass](miscellaneous/ASampleClass.md) + +This is a class description SomeDto . + ## Triggers ### [PaymentDeviceTrigger](triggers/PaymentDeviceTrigger.md) diff --git a/examples/docsify/docs/miscellaneous/ASampleClass.md b/examples/docsify/docs/miscellaneous/ASampleClass.md index 63bc4535..30881e17 100644 --- a/examples/docsify/docs/miscellaneous/ASampleClass.md +++ b/examples/docsify/docs/miscellaneous/ASampleClass.md @@ -1,6 +1,10 @@ # ASampleClass Class -This is a class description. +This is a class description SomeDto . + +**Copyright** + +All rights reserved. Cesar Parra 2025 **Mermaid** @@ -17,9 +21,9 @@ sequenceDiagram iframe->>iframe: render mermaid ``` -**See** [SomeDto](miscellaneous/SomeDto.md) +**See** SomeDto -**See** [SampleInterface](sample-interfaces/SampleInterface.md) +**See** SampleInterface ## Methods ### `getActiveSurveySettings(surveyType2)` diff --git a/examples/docsify/docs/triggers/PaymentDeviceTrigger.md b/examples/docsify/docs/triggers/PaymentDeviceTrigger.md index 40ffc9c7..0985bcd4 100644 --- a/examples/docsify/docs/triggers/PaymentDeviceTrigger.md +++ b/examples/docsify/docs/triggers/PaymentDeviceTrigger.md @@ -4,6 +4,6 @@ This trigger is used to handle the logic for the Payment Device object. -**Events** +**Run** * Before Insert * Before Update \ No newline at end of file diff --git a/examples/docsify/src/classes/ASampleClass.cls b/examples/docsify/src/classes/ASampleClass.cls index c667c32d..aa3f47b6 100644 --- a/examples/docsify/src/classes/ASampleClass.cls +++ b/examples/docsify/src/classes/ASampleClass.cls @@ -1,5 +1,6 @@ /** - * @description This is a class description. + * @description This is a class description <>. + * {{copyright}} * @see SomeDto * @see SampleInterface * @mermaid diff --git a/examples/docsify/src/classes/ASampleClass.cls-meta.xml b/examples/docsify/src/classes/ASampleClass.cls-meta.xml new file mode 100644 index 00000000..998805a8 --- /dev/null +++ b/examples/docsify/src/classes/ASampleClass.cls-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + Active + diff --git a/package.json b/package.json index eb0f5ee2..86c4b87c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cparra/apexdocs", - "version": "3.10.0", + "version": "3.11.0", "description": "Library with CLI capabilities to generate documentation for Salesforce Apex classes.", "keywords": [ "apex", diff --git a/src/core/markdown/generate-docs.ts b/src/core/markdown/generate-docs.ts index b6317c8d..e7708918 100644 --- a/src/core/markdown/generate-docs.ts +++ b/src/core/markdown/generate-docs.ts @@ -17,6 +17,7 @@ import { ParsedFile, UnparsedSourceBundle, TopLevelType, + MacroFunction, } from '../shared/types'; import { parsedFilesToRenderableBundle } from './adapters/renderable-bundle'; import { reflectApexSource } from '../reflection/apex/reflect-apex-source'; @@ -64,7 +65,8 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma } return pipe( - generateForApex(filterApexSourceFiles(unparsedBundles), config), + TE.right(replaceMacros(unparsedBundles, config.macros)), + TE.flatMap((unparsedBundles) => generateForApex(filterApexSourceFiles(unparsedBundles), config)), TE.chain((parsedApexFiles) => { return pipe( reflectCustomFieldsAndObjectsAndMetadataRecords( @@ -98,6 +100,31 @@ export function generateDocs(unparsedBundles: UnparsedSourceBundle[], config: Ma ); } +function replaceMacros( + unparsedBundles: UnparsedSourceBundle[], + macros: Record | undefined, +): UnparsedSourceBundle[] { + if (!macros) { + return unparsedBundles; + } + + return unparsedBundles.map((bundle) => { + return { + ...bundle, + content: Object.entries(macros).reduce((acc, [macroName, macroFunction]) => { + return acc.replace( + new RegExp(`{{${macroName}}}`, 'g'), + macroFunction({ + type: bundle.type, + name: bundle.name, + filePath: bundle.filePath, + }), + ); + }, bundle.content), + }; + }); +} + function generateForApex(apexBundles: UnparsedApexBundle[], config: MarkdownGeneratorConfig) { const filterOutOfScope = apply(filterScope, config.scope); const removeExcluded = apply(removeExcludedTags, config.excludeTags); diff --git a/src/core/shared/types.d.ts b/src/core/shared/types.d.ts index 4e4c7ac3..c4fd9bab 100644 --- a/src/core/shared/types.d.ts +++ b/src/core/shared/types.d.ts @@ -17,6 +17,14 @@ type LinkingStrategy = // No logic will be applied, the reference path will be used as is. | 'none'; +export type MacroSourceMetadata = { + type: 'apex' | 'customobject' | 'customfield' | 'custommetadata' | 'trigger'; + name: string; + filePath: string; +}; + +export type MacroFunction = (metadata: MacroSourceMetadata) => string; + export type CliConfigurableMarkdownConfig = { sourceDir: string; targetDir: string; @@ -232,6 +240,7 @@ export type PostHookDocumentationBundle = { * The configurable hooks that can be used to modify the output of the Markdown generator. */ export type MarkdownConfigurableHooks = { + macros: Record; transformReferenceGuide: TransformReferenceGuide; transformDocs: TransformDocs; transformDocPage: TransformDocPage; diff --git a/src/index.ts b/src/index.ts index 557aa5a4..1afe5ecc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,7 @@ import type { MarkdownConfigurableHooks, + MacroFunction, + MacroSourceMetadata, Skip, UserDefinedMarkdownConfig, ReferenceGuidePageData, @@ -67,6 +69,8 @@ function defineChangelogConfig(config: ConfigurableChangelogConfig): Partial