1+ <#
2+ . SYNOPSIS
3+ README.md.ps1
4+ . DESCRIPTION
5+ README.md.ps1 makes README.md
6+
7+ This is a simple and helpful scripting convention for writing READMEs.
8+
9+ `./README.md.ps1 > ./README.md`
10+
11+ Feel free to copy and paste this code.
12+
13+ Please document your parameters, and add NOTES.
14+ . NOTES
15+ This README.md.ps1 is used to generate help for a module.
16+
17+ It:
18+
19+ * Outputs the name and description
20+ * Provides installation instructions
21+ * Lists commands
22+ * Lists parameters
23+ * Lists examples
24+ * Lists Extended Types
25+ . EXAMPLE
26+ ./README.md.ps1 > ./README.md
27+ . EXAMPLE
28+ Get-Help ./README.md.ps1
29+ #>
30+ param (
31+ # The name of the module
32+ [string ]$ModuleName = $ ($PSScriptRoot | Split-Path - Leaf),
33+
34+ # The domains that serve git repositories.
35+ # If the project uri links to this domain,
36+ # installation instructions will show how to import the module locally.
37+ [string []]
38+ $GitDomains = @ (
39+ ' github.com' , ' tangled.org' , ' tangled.sh' , ' codeberg.org'
40+ ),
41+
42+ # A list of types the module exposes
43+ [Alias (' ModuleTypeNames' , ' ModuleTypes' )]
44+ [string []]
45+ $ModuleTypeName = @ (
46+ Get-ChildItem - Path $PSScriptRoot - Filter * .types.ps1xml |
47+ Select-Xml / Types/ Type/ Name |
48+ Foreach-Object {$_.Node.InnerText } |
49+ Sort-Object
50+ ),
51+
52+ # The name of the root directory containing types.
53+ [string ]
54+ $TypeRoot = ' Types' ,
55+
56+ # If set, we don't need no badges.
57+ [switch ]
58+ $NoBadge ,
59+
60+ # If set, will not display gallery instructions or badges
61+ [switch ]
62+ $NotOnGallery
63+ )
64+
65+ Push-Location $PSScriptRoot
66+
67+ # Import the module
68+ $module = Import-Module " ./$ModuleName .psd1" - PassThru
69+
70+ # And output a header
71+ " # $module "
72+
73+ if (-not $NoBadge ) {
74+ # If it is on the gallery, show the downloads badge.
75+ if (-not $NotOnGallery ) {
76+ @ (
77+ " [!"
78+ " [$ModuleName ](https://img.shields.io/powershellgallery/dt/$ModuleName )"
79+ " ](https://www.powershellgallery.com/packages/$ModuleName /)"
80+ ) -join ' '
81+ }
82+ }
83+
84+ # Show the module description
85+ " ## $ ( $module.Description ) "
86+
87+ # Show any intro section defined in the manifest
88+ $module.PrivateData.PSData.PSIntro
89+
90+ # region Boilerplate installation instructions
91+ if (-not $NotOnGallery ) {
92+ @"
93+
94+ ## Installing and Importing
95+
96+ You can install $ModuleName from the [PowerShell gallery](https://powershellgallery.com/)
97+
98+ ~~~PowerShell
99+ Install-Module $ ( $ModuleName ) -Scope CurrentUser -Force
100+ ~~~
101+
102+ Once installed, you can import the module with:
103+
104+ ~~~PowerShell
105+ Import-Module $ModuleName -PassThru
106+ ~~~
107+
108+ "@
109+ }
110+ # endregion Gallery installation instructions
111+
112+ # region Git installation instructions
113+ $projectUri = $module.PrivateData.PSData.ProjectURI -as [uri ]
114+
115+ if ($projectUri.DnsSafeHost -in $GitDomains ) {
116+ @"
117+
118+ You can also clone the repo and import the module locally:
119+
120+ ~~~PowerShell
121+ git clone $projectUri
122+ cd ./$ModuleName
123+ Import-Module ./ -PassThru
124+ ~~~
125+
126+ "@
127+ }
128+ # endregion Git installation instructions
129+
130+ # region Exported Functions
131+ $exportedFunctions = $module.ExportedFunctions
132+ if ($exportedFunctions ) {
133+
134+ " ## Functions"
135+
136+ " $ ( $ModuleName ) has $ ( $exportedFunctions.Count ) function$ (
137+ if ($exportedFunctions.Count -gt 1 ) { " s" }
138+ ) "
139+
140+ foreach ($export in $exportedFunctions.Keys | Sort-Object ) {
141+ # Get help if it there is help to get
142+ $help = Get-Help $export
143+ # If the help is a string,
144+ if ($help -is [string ]) {
145+ # make it preformatted text
146+ " ~~~"
147+ " $export "
148+ " ~~~"
149+ } else {
150+ # Otherwise, add list the export
151+ " ### $ ( $export ) "
152+
153+ # And make it's synopsis a header
154+ " #### $ ( $help.SYNOPSIS ) "
155+
156+ # put the description below that
157+ " $ ( $help.Description.text -join [Environment ]::NewLine) "
158+
159+ # Make a table of parameters
160+ if ($help.parameters.parameter ) {
161+ " ##### Parameters"
162+
163+ " "
164+
165+ " |Name|Type|Description|"
166+ " |-|-|-|"
167+ foreach ($parameter in $help.Parameters.Parameter ) {
168+ " |$ ( $parameter.Name ) |$ ( $parameter.type.name ) |$ (
169+ $parameter.description.text -replace ' (?>\r\n|\n)' , ' <br/>'
170+ ) |"
171+ }
172+
173+ " "
174+ }
175+
176+ # Show our examples
177+ " ##### Examples"
178+
179+ $exampleNumber = 0
180+ foreach ($example in $help.examples.example ) {
181+ $markdownLines = @ ()
182+ $exampleNumber ++
183+ $nonCommentLine = $false
184+ " ###### Example $exampleNumber "
185+
186+ # Combine the code and remarks
187+ $exampleLines =
188+ @ (
189+ $example.Code
190+ foreach ($remark in $example.Remarks.text ) {
191+ if (-not $remark ) { continue }
192+ $remark
193+ }
194+ ) -join ([Environment ]::NewLine) -split ' (?>\r\n|\n)' # and split into lines
195+
196+ # Go thru each line in the example as part of a loop
197+ $codeBlock = @ (foreach ($exampleLine in $exampleLines ) {
198+ # Any comments until the first uncommentedLine are markdown
199+ if ($exampleLine -match ' ^\#' -and -not $nonCommentLine ) {
200+ $markdownLines += $exampleLine -replace ' ^\#\s{0,1}'
201+ } else {
202+ $nonCommentLine = $true
203+ $exampleLine
204+ }
205+ }) -join [Environment ]::NewLine
206+
207+ $markdownLines
208+ " ~~~PowerShell"
209+ $CodeBlock
210+ " ~~~"
211+ }
212+
213+ $relatedUris = foreach ($link in $help.relatedLinks.navigationLink ) {
214+ if ($link.uri ) {
215+ $link.uri
216+ }
217+ }
218+ if ($relatedUris ) {
219+ " #### Links"
220+ foreach ($related in $relatedUris ) {
221+ " * [$related ]($related )"
222+ }
223+ }
224+ }
225+ }
226+ }
227+ # endregion Exported Functions
228+
229+ # region Exported Types
230+ if ($ModuleTypeName ) {
231+ $typeData = Get-TypeData - TypeName $ModuleTypeName
232+
233+ if ($typeData ) {
234+ " ## Types"
235+ }
236+
237+ foreach ($typeInfo in $typeData ) {
238+ $memberDirectory = " ./Types/$ ( $typeInfo.TypeName ) "
239+ if (-not (Test-Path $memberDirectory )) { continue }
240+ $psTypeNames = @ (Get-ChildItem - Path $memberDirectory |
241+ Where-Object Name -match ' PSTypeNames?\.txt$' |
242+ Get-Content )
243+ if (-not $psTypeNames ) {
244+ $psTypeNames = @ ($typeInfo.TypeName )
245+ }
246+ " ### $ ( $typeInfo.TypeName ) "
247+
248+ if ($psTypeNames.Count -gt 1 ) {
249+ " > Also Known As:"
250+ $psTypeNames |
251+ Where-Object { $_ -ne $typeInfo.TypeName } |
252+ Foreach-Object { " * $_ " }
253+ }
254+
255+
256+ " #### Members"
257+ " |Name|MemberType|"
258+ " |-|-|"
259+
260+ foreach ($memberName in $typeInfo.Members.Keys ) {
261+ " |$ (
262+ $memberPath = " ./Types/$ ( $typeInfo.TypeName ) /$memberName .ps1"
263+ if (Test-Path $memberPath ) {
264+ " [$memberName ]($ ( $memberPath -replace ' ^\./' ) )"
265+ }
266+ else {
267+ $getPath = " ./Types/$ ( $typeInfo.TypeName ) /get_$memberName .ps1"
268+ $setPath = " ./Types/$ ( $typeInfo.TypeName ) /set_$memberName .ps1"
269+ if (
270+ (Test-Path $getPath ) -and
271+ (Test-Path $setPath )) {
272+ " [get]($ (
273+ $getPath -replace ' ^\./'
274+ ) )/[set]($ (
275+ $setPath -replace ' ^\./'
276+ ) )_[$memberName ]($ (
277+ $getPath -replace ' ^\./'
278+ ) )"
279+ }
280+ elseif (Test-Path $getPath ) {
281+ " [get_$memberName ]($ (
282+ $getPath -replace ' ^\./'
283+ ) )"
284+ } else {
285+ $memberName
286+ }
287+ }
288+ ) |$ (
289+ $typeInfo.Members [$memberName ].GetType().Name -replace ' Data$'
290+ ) |"
291+ }
292+ }
293+ }
294+ # endregion Exported Types
295+
296+ # region Copyright Notice
297+ if ($module.Copyright ) {
298+ " > $ ( $module.Copyright ) "
299+ }
300+
301+ if ($module.PrivateData.PSData.LicenseUri ) {
302+ " "
303+ " > [LICENSE]($ ( $module.PrivateData.PSData.LicenseUri ) )"
304+ }
305+ # endregion Copyright Notice
306+
307+ Pop-Location
0 commit comments