Markdown
diff --git a/README.md b/README.md
index 81233a7..f32269d 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,9 @@
## Greetings from MarkX!
+[](https://www.powershellgallery.com/packages/MarkX/)
+
+
### What Is MarkX?
MarkX is a useful little tool built around a useful little trick.
@@ -181,3 +184,126 @@ When we run this example, we get:
abc
def
12 10
+
+
+### Markdown Lexicons
+
+Since we can extra tables and data Markdown, we can also get any data of a particular known shape.
+
+The first special shape MarkX supports is an [at protocol lexicon](https://atproto.com/guides/lexicon)
+
+MarkX current supports lexicon type definitions. It will support query and procedure definitions in the future.
+
+A type definition consists of a namespace identifier, a description, and a series of properties.
+
+#### com.example.happy.birthday
+> An example lexicon to record birthday messages
+
+|Property|Type|Description|
+|-|-|-|
+|`$type` | `[string]` | The type of the object. Must be `com.example.happy.birthday` |
+|**`message`**| `[string]` | A birthday message |
+|`forUri` | `[uri]` | A link |
+|`birthday` | `[datetime]` | The birthday |
+|`createdAt` | `[datetime]` | The time the record was created |
+
+
+
+To extract out a lexicon from the text above, we can:
+
+~~~PowerShell
+$lexiconMarkdown.Lexicon | ConvertTo-Json -Depth 5
+~~~
+
+Which gives us:
+
+~~~json
+{
+ "lexicon": 1,
+ "id": "com.example.happy.birthday",
+ "defs": {
+ "main": {
+ "type": "record",
+ "description": "com.example.happy.birthday",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "A birthday message"
+ },
+ "forUri": {
+ "type": "uri",
+ "description": "A link"
+ },
+ "birthday": {
+ "type": "datetime",
+ "description": "The birthday"
+ },
+ "createdAt": {
+ "type": "datetime",
+ "description": "The time the record was created"
+ }
+ }
+ }
+ }
+}
+~~~
+
+As you can see, we can take rich data within Markdown and process it into lexicons (or anything else we might want)
+# Get-MarkX
+## Gets MarkX
+### Gets MarkX - Markdown as XML
+
+This allows us to query, extract, and customize markdown.
+
+'Hello World' In Markdown / MarkX
+~~~PowerShell
+'# Hello World' | MarkX
+~~~
+MarkX is aliased to Markdown
+'Hello World' as Markdown as XML
+~~~PowerShell
+'# Hello World' | Markdown | Select -Expand XML
+~~~
+We can generate tables by piping in objects
+~~~PowerShell
+@{n1=1;n2=2}, @{n1=2;n3=3} | MarkX
+~~~
+Make a TimesTable in MarkX
+~~~PowerShell
+@(
+ "#### TimesTable"
+ foreach ($rowN in 1..9) {
+ $row = [Ordered]@{}
+ foreach ($colN in 1..9) {
+ $row["$colN"] = $colN * $rowN
+ }
+ $row
+ }
+) | Get-MarkX
+~~~
+We can pipe a command into MarkX
+This will get the command help as Markdown
+~~~PowerShell
+Get-Command Get-MarkX | MarkX
+~~~
+We can pipe help into MarkX
+~~~PowerShell
+Get-Help Get-MarkX | MarkX
+~~~
+We can get code from markdown
+~~~PowerShell
+Get-Help Get-MarkX |
+ MarkX |
+ Select-Object -ExpandProperty Code
+~~~
+
+## In Summary
+
+MarkX is a simple and powerful tool.
+It allows us to turn many objects into Markdown, and turn Markdown into many objects.
+
+Please pay around and see what you can do.
+
diff --git a/Types/MarkX/Alias.psd1 b/Types/MarkX/Alias.psd1
index 3d378e6..e454738 100644
--- a/Types/MarkX/Alias.psd1
+++ b/Types/MarkX/Alias.psd1
@@ -2,4 +2,5 @@
DB = 'DataSet'
Tables = 'Table'
Link = 'Links'
+ Heading = 'Headings'
}
\ No newline at end of file
diff --git a/Types/MarkX/MarkX.format.ps1 b/Types/MarkX/MarkX.format.ps1
new file mode 100644
index 0000000..7dde8b0
--- /dev/null
+++ b/Types/MarkX/MarkX.format.ps1
@@ -0,0 +1 @@
+Write-FormatView -TypeName MarkX -Property Markdown -Wrap
diff --git a/Types/MarkX/Sync.ps1 b/Types/MarkX/Sync.ps1
index 82fa5ee..affd14d 100644
--- a/Types/MarkX/Sync.ps1
+++ b/Types/MarkX/Sync.ps1
@@ -1,3 +1,156 @@
+$currentRows = @()
+$allMarkdown = @(:nextInput foreach ($md in $this.Input) {
+ if ($md -isnot [string]) {
+ # If the markdown was a file
+ if ($md -is [IO.FileInfo] -and
+ # and it had the extension .md or markdown
+ $md.Extension -in '.md', '.markdown') {
+ $md = Get-Content -LiteralPath $md.Fullname -Raw
+ $md
+ continue
+ }
+
+ if ($md -is [Management.Automation.CommandInfo]) {
+ $cmdHelp = if (
+ $md -is [Management.Automation.FunctionInfo] -or
+ $md -is [Management.Automation.AliasInfo]
+ ) {
+ Get-Help -Name $md.Name
+ } elseif ($md -is [Management.Automation.ExternalScriptInfo]) {
+ Get-Help -Name $md.Source
+ } else {
+ continue nextInput
+ }
+ if ($cmdHelp) {
+ $md = $cmdHelp
+ }
+ }
+
+ if ($md.pstypenames -match 'HelpInfo') {
+ @(
+ if ($md.Name -match '[\\/]') {
+ "# $(@($md.Name -split '[\\/]')[-1] -replace '\.ps1')"
+ } else {
+ "# $($md.Name)"
+ }
+
+ if ($md.Synopsis) {
+ "## $($md.Synopsis)"
+ }
+ $description = $md.Description.text -join [Environment]::NewLine
+ if ($description) {
+ "### $($description)"
+ }
+
+ $md.alertset.alert.text -join [Environment]::NewLine
+
+ foreach ($example in $md.examples.example) {
+ $exampleNumber++
+
+ # Combine the code and remarks
+ $exampleLines =
+ @(
+ $example.Code
+ foreach ($remark in $example.Remarks.text) {
+ if (-not $remark) { continue }
+ $remark
+ }
+ ) -join ([Environment]::NewLine) -split '(?>\r\n|\n)' # and split into lines
+
+ # Anything until the first non-comment line is a markdown predicate to the example
+ $nonCommentLine = $false
+ $markdownLines = @()
+
+ # Go thru each line in the example as part of a loop
+ $codeBlock = @(foreach ($exampleLine in $exampleLines) {
+ # Any comments until the first uncommentedLine are markdown
+ if ($exampleLine -match '^\#' -and -not $nonCommentLine) {
+ $markdownLines += $exampleLine -replace '^\#' -replace '^\s+'
+ } else {
+ $nonCommentLine = $true
+ $exampleLine
+ }
+ }) -join [Environment]::NewLine
+
+ # Join all of our markdown lines together
+ $markdownLines -join [Environment]::NewLine
+ "~~~PowerShell"
+ $codeBlock
+ "~~~"
+ }
+ ) -join [Environment]::NewLine
+ continue nextInput
+ }
+ if ($md -is [ScriptBlock]) {
+ "$(
+ [Web.HttpUtility]::HtmlEncode(
+ "$md"
+ )
+ )"
+ continue nextInput
+ }
+ if ($md -is [Collections.IDictionary] -or
+ ($md.GetType -and
+ (-not $md.GetType().IsPrimitive)
+ )
+ ) {
+ $currentRows += $md
+ continue
+ }
+ }
+
+ if ($currentRows) {
+ $this.ToTable($currentRows)
+ $currentRows = @()
+ }
+
+ if ($md -match '(?>\.md|markdown)$' -and
+ (Test-Path $md -ErrorAction Ignore)
+ ) {
+ $md = Get-Content -Raw $md
+ }
+
+ $yamlheader = ''
+ if ($md -match '^---') {
+ $null, $yamlheader, $md = $in -split '---', 3
+ if ($yamlheader) {
+ $this | Add-Member NoteProperty '#YamlHeader' $yamlheader -Force
+ }
+ }
+
+ $md
+})
+
+if ($currentRows) {
+ $allMarkdown += $this.ToTable($currentRows)
+ $currentRows = @()
+}
+
+$yamlHeaders = @()
+$allMarkdown = @(foreach ($md in $allMarkdown) {
+ if ($md -match '^---') {
+ $null, $yamlheader, $restOfMakdown = $md -split '---', 3
+ if ($yamlheader) {
+ $yamlHeaders+= $yamlheader
+ }
+ $restOfMakdown
+ } else {
+ $md
+ }
+})
+
+if ($yamlHeaders) {
+ $yamlHeader = $yamlHeaders -join (
+ [Environment]::NewLine + '---' + [Environment]::NewLine
+ )
+ $this | Add-Member NoteProperty '#YamlHeader' $yamlHeader -Force
+}
+
+$markdown = $allMarkdown -join [Environment]::NewLine
+
+$this |
+ Add-Member NoteProperty '#Markdown' $Markdown -Force
+
$Markdown = $this.'#Markdown'
if (-not $Markdown) { return }
@@ -17,7 +170,6 @@ $this |
if (-not $this.'#XML') { return }
$tables = $this.'#XML' | Select-Xml //table
-if (-not $tables) { return }
filter innerText {
$in = $_
if ($in -is [string]) { "$in" }
diff --git a/Types/MarkX/ToString.ps1 b/Types/MarkX/ToString.ps1
index 12b6c8c..035c9d2 100644
--- a/Types/MarkX/ToString.ps1
+++ b/Types/MarkX/ToString.ps1
@@ -1,2 +1,18 @@
+if ($args) {
+ $anyOutput = foreach ($arg in $args) {
+ $thisArg = $this.$arg
+ if ($thisArg) {
+ if ($thisArg.XHTML.InnerXML) {
+ "$($thisArg.XHTML.InnerXML)" + [Environment]::NewLine
+ } else {
+ "$thisArg"
+ }
+ }
+ }
+ if ($anyOutput) {
+ return $anyOutput -join [Environment]::NewLine
+ }
+}
+
if (-not $this.XML.XHTML) { return '' }
return ("$($this.XML.XHTML.InnerXML)" + [Environment]::NewLine)
\ No newline at end of file
diff --git a/Types/MarkX/get_Code.ps1 b/Types/MarkX/get_Code.ps1
new file mode 100644
index 0000000..4f53108
--- /dev/null
+++ b/Types/MarkX/get_Code.ps1
@@ -0,0 +1,27 @@
+<#
+.SYNOPSIS
+ Gets code
+.DESCRIPTION
+ Gets code within Markdown / MarkX, grouped by language.
+.EXAMPLE
+ "
+ # Hello World in PowerShell
+ ~~~PowerShell
+ 'Hello World'
+ ~~~
+ " | markx | Select-Object -ExpandProperty Code
+#>
+$codeByLanguage = [Ordered]@{}
+
+foreach ($element in $this.XML | Select-Xml -XPath //code) {
+ $language = 'unknown'
+ if ($element.node.class -match 'language-(?\S+)?') {
+ $language = $matches.language
+ }
+ if (-not $codeByLanguage[$language]) {
+ $codeByLanguage[$language] = @()
+ }
+ $codeByLanguage[$language] += $element.node.InnerText
+}
+
+return $codeByLanguage
\ No newline at end of file
diff --git a/Types/MarkX/get_CodeBlock.ps1 b/Types/MarkX/get_CodeBlock.ps1
new file mode 100644
index 0000000..1a6f934
--- /dev/null
+++ b/Types/MarkX/get_CodeBlock.ps1
@@ -0,0 +1,27 @@
+<#
+.SYNOPSIS
+ Gets code blocks
+.DESCRIPTION
+ Gets code blocks within Markdown / MarkX
+.EXAMPLE
+ "
+ # Hello World in PowerShell
+ ~~~PowerShell
+ 'Hello World'
+ ~~~
+ " | markx | Select-Object -ExpandProperty CodeBlock
+#>
+$codeByLanguage = [Ordered]@{}
+
+foreach ($element in $this.XML | Select-Xml -XPath //code) {
+ $language = 'unknown'
+ if ($element.node.class -match 'language-(?\S+)?') {
+ $language = $matches.language
+ }
+ if (-not $codeByLanguage[$language]) {
+ $codeByLanguage[$language] = @()
+ }
+ $codeByLanguage[$language] += $element.node
+}
+
+return $codeByLanguage
\ No newline at end of file
diff --git a/Types/MarkX/get_Header.ps1 b/Types/MarkX/get_Header.ps1
new file mode 100644
index 0000000..782f37f
--- /dev/null
+++ b/Types/MarkX/get_Header.ps1
@@ -0,0 +1,9 @@
+if (-not $this.'#YamlHeader') { return }
+
+$convertFromYaml = $ExecutionContext.SessionState.InvokeCommand.GetCommand('ConvertFrom-Yaml', 'Alias,Cmdlet,Function')
+if (-not $convertFromYaml) {
+ Write-Warning "Cannot get header without ConvertFrom-Yaml"
+ return
+}
+
+return ($this.'#YamlHeader' | & $convertFromYaml)
\ No newline at end of file
diff --git a/Types/MarkX/get_Headings.ps1 b/Types/MarkX/get_Headings.ps1
new file mode 100644
index 0000000..d31ed45
--- /dev/null
+++ b/Types/MarkX/get_Headings.ps1
@@ -0,0 +1,12 @@
+<#
+.SYNOPSIS
+ Gets Markdown headings
+.DESCRIPTION
+ Gets any heading elements in the markdown
+#>
+$this.XML |
+ Select-Xml -XPath //* |
+ Where-Object {
+ $_.Node.LocalName -match 'h[1-6]'
+ } |
+ Select-Object -ExpandProperty Node
\ No newline at end of file
diff --git a/Types/MarkX/get_Input.ps1 b/Types/MarkX/get_Input.ps1
new file mode 100644
index 0000000..890e9e0
--- /dev/null
+++ b/Types/MarkX/get_Input.ps1
@@ -0,0 +1 @@
+return $this.'#input'
\ No newline at end of file
diff --git a/Types/MarkX/get_Lexicon.ps1 b/Types/MarkX/get_Lexicon.ps1
new file mode 100644
index 0000000..4b9fb65
--- /dev/null
+++ b/Types/MarkX/get_Lexicon.ps1
@@ -0,0 +1,87 @@
+<#
+.SYNOPSIS
+ Gets Lexicons from Markdown
+.DESCRIPTION
+ Gets At Protocol Lexicons defined in Markdown.
+
+ A lexicon table must have at least three columns:
+
+ * Property
+ * Type
+ * Description
+
+ It must also contain a row containing the property `$type`.
+
+ This will be considered the lexicon's type.
+
+ Any bolded or italic fields will be considered required.
+
+ Lexicon tables may also be preceeded by an element containing the description.
+#>
+
+$markdownData = $this.DataSet
+:nextTable foreach ($table in $markdownData.Tables) {
+ $isLexiconTable = $table.Columns['Property'] -and $table.Columns['Type'] -and $table.Columns['Description']
+
+ if (-not $isLexiconTable) { continue nextTable }
+
+ $hasType = $table.Select("Property='`$type'")
+ if (-not $hasType) {
+ Write-Warning "Missing `$type"
+ continue nextTable
+ }
+
+ $lexiconType = if ($hasType.Description -match '(?:[^\.\s]+\.){3}[^\.\s]+') {
+ $matches.0
+ }
+
+ if (-not $lexiconType) {
+ continue nextTable
+ }
+
+ $lexiconObject = [Ordered]@{
+ lexicon = 1
+ id = $lexiconType
+ defs = @{
+ main = [Ordered]@{
+ type = 'record'
+ description =
+ if ($table.ExtendedProperties.Description) {
+ $table.ExtendedProperties.Description
+ } else {
+ $lexiconType
+ }
+ required = @()
+ properties = [Ordered]@{}
+ }
+ }
+ }
+
+ foreach ($row in $table) {
+
+ if ($row.Property -eq '$type') { continue }
+ $lexProp = [Ordered]@{}
+ $lexProp.type =
+ switch -regex ($row.type) {
+ '\[\]' { 'array' }
+ 'object' { 'object' }
+ 'string' { 'string' }
+ 'bool|switch' { 'boolean' }
+ 'int|number|float|double' { 'number' }
+ 'date' { 'datetime' }
+ 'ur[il]' { 'uri'}
+ }
+
+ $lexProp.description = $row.Description
+ if ($row.Property -match '\*') {
+ $lexiconObject.defs.main.required += $row.Property -replace '\*'
+ }
+ elseif ($row.tr.td.outerxml -match '<(?>b|i|strong)>') {
+ $lexiconObject.defs.main.required += $row.Property -replace '\*'
+ }
+
+ $lexiconObject.defs.main.properties[$row.Property -replace '\*'] = $lexProp
+ }
+
+ $lexiconObject
+}
\ No newline at end of file
diff --git a/Types/MarkX/get_YamlHeader.ps1 b/Types/MarkX/get_YamlHeader.ps1
new file mode 100644
index 0000000..bbca11b
--- /dev/null
+++ b/Types/MarkX/get_YamlHeader.ps1
@@ -0,0 +1 @@
+return $this.'#YamlHeader'
\ No newline at end of file
diff --git a/Types/MarkX/set_Header.ps1 b/Types/MarkX/set_Header.ps1
new file mode 100644
index 0000000..cea3547
--- /dev/null
+++ b/Types/MarkX/set_Header.ps1
@@ -0,0 +1,3 @@
+param($header)
+
+$this.YamlHeader = $header
\ No newline at end of file
diff --git a/Types/MarkX/set_Input.ps1 b/Types/MarkX/set_Input.ps1
new file mode 100644
index 0000000..33478d2
--- /dev/null
+++ b/Types/MarkX/set_Input.ps1
@@ -0,0 +1,7 @@
+param(
+[PSObject[]]$InputObject
+)
+
+$this | Add-Member NoteProperty '#Input' $InputObject -Force
+
+$this.Sync()
\ No newline at end of file
diff --git a/Types/MarkX/set_Markdown.ps1 b/Types/MarkX/set_Markdown.ps1
index f164646..b7d51e7 100644
--- a/Types/MarkX/set_Markdown.ps1
+++ b/Types/MarkX/set_Markdown.ps1
@@ -2,50 +2,4 @@ param(
[PSObject[]]$Markdown
)
-$currentRows = @()
-$allMarkdown = @(foreach ($md in $Markdown) {
- if ($md -isnot [string]) {
- if ($md -is [ScriptBlock]) {
- $md = "$(
- [Web.HttpUtility]::HtmlEncode(
- "$md"
- )
- )"
- }
- if ($md -is [Collections.IDictionary] -or
- ($md.GetType -and -not $md.GetType().IsPrimitive)) {
- $currentRows += $md
- continue
- }
- }
-
- if ($currentRows) {
- $this.ToTable($currentRows)
- $currentRows = @()
- }
-
- if ($md -match '(?>\.md|markdown)$' -and
- (Test-Path $md -ErrorAction Ignore)
- ) {
- $md = Get-Content -Raw $md
- }
-
- $yamlheader = ''
- if ($md -match '^---') {
- $null, $yamlheader, $md = $in -split '---', 3
- }
-
- $md
-})
-
-if ($currentRows) {
- $allMarkdown += $this.ToTable($currentRows)
- $currentRows = @()
-}
-
-$markdown = $allMarkdown -join [Environment]::NewLine
-
-$this |
- Add-Member NoteProperty '#Markdown' $Markdown -Force
-
-$this.Sync()
\ No newline at end of file
+$this.Input = $Markdown
diff --git a/Types/MarkX/set_YamlHeader.ps1 b/Types/MarkX/set_YamlHeader.ps1
new file mode 100644
index 0000000..ab46613
--- /dev/null
+++ b/Types/MarkX/set_YamlHeader.ps1
@@ -0,0 +1,23 @@
+param($header)
+
+if ($header -is [string]) {
+ $this | Add-Member NoteProperty '#YamlHeader' $header -Force
+ return
+}
+
+$convertToYaml = $ExecutionContext.SessionState.InvokeCommand.GetCommand('ConvertTo-Yaml', 'Alias,Cmdlet,Function')
+if (-not $convertToYaml) {
+ Write-Warning "Cannot set yaml header without converter"
+ return
+}
+
+$convertParameters = @{}
+if ($convertToYaml.Parameters['Depth']) {
+ $convertParameters['Depth'] = $FormatEnumerationLimit
+}
+$toYaml = $header | & $convertToYaml @convertParameters
+if ($toYaml -is [string]) {
+ $this | Add-Member NoteProperty '#YamlHeader' $toYaml -Force
+}
+
+