Nice little directory browser :D
at d08f01b1b3d1f5562daa69eafa0149b8ac40c5ce 635 lines 18 kB view raw
1<# 2 This file is part of Utatane. 3 4 Utatane is free software: you can redistribute it and/or modify it under the 5 terms of the GNU Affero General Public License as published by the Free 6 Software Foundation, either version 3 of the License, or (at your option) 7 any later version. 8 9 Utatane is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for 12 more details. 13 14 You should have received a copy of the GNU Affero General Public License 15 along with Utatane. If not, see <http://www.gnu.org/licenses/>. 16#> 17 18Import-Module Pode 19Import-Module Mizumiya 20 21# WARNING: modifying this requires restarting the entire shell 22Add-Type -Namespace Native -Name LibC -MemberDefinition @' 23[DllImport("libc.so.6")] 24public static extern int euidaccess(string pathname, int mode); 25 26[DllImport("libc.so.6", SetLastError=true)] 27public static extern IntPtr readlink(string pathname, byte[] buf, UIntPtr bufsize); 28'@ 29 30function _can_r_x ([IO.FileSystemInfo] $Path) { 31 return ([Native.LibC]::euidaccess($Path.FullName, 5 <# R_OK -bor X_OK #>) -eq 0) 32} 33 34function _can_r__ ([IO.FileSystemInfo] $Path) { 35 return ([Native.LibC]::euidaccess($Path.FullName, 4 <# R_OK #>) -eq 0) 36} 37 38function _readlink { 39 param ( 40 [String] $PathName 41 ) 42 43 if ([String]::IsNullOrWhitespace($PathName)) { 44 throw [ArgumentException]::new("PathName cannot be null!") 45 } 46 47 $BufSize = 1024 48 $Buf = [Array]::CreateInstance([byte], $BufSize) 49 50 $BufMax = [Native.LibC]::readlink($PathName, $Buf, [UIntPtr]$BufSize).ToInt64() 51 52 if ($BufMax -lt 0) { 53 $errno = [System.Runtime.InteropServices.Marshal]::GetLastPInvokeError() 54 $strerror = [System.Runtime.InteropServices.Marshal]::GetLastPInvokeErrorMessage() 55 throw [Exception]::new("readlink failed with errno $errno`: $strerror") 56 } 57 58 return [System.Text.Encoding]::Default.GetString($Buf, 0, $BufMax) 59} 60 61function _log { 62 param( 63 [Parameter(ValueFromPipeline)] 64 $Message, 65 [ValidateSet('Request', 'Information', 'Warning', 'Fatal')] 66 $Type 67 ) 68 69 switch ($Type) { 70 'Request' { 71 $Style = $PSStyle.Foreground.BrightBlack 72 } 73 74 'Information' { 75 $Style = '' 76 } 77 78 'Warning' { 79 $Style = $PSStyle.Formatting.Warning 80 } 81 82 'Fatal' { 83 $Style = $PSStyle.Formatting.Error 84 } 85 } 86 87 Write-Host "$Style[Utatane/$Type] $Message$($PSStyle.Reset)" 88 if ($Type -eq 'Fatal') { throw $Message } 89} 90 91function _request { 92 param( 93 [Parameter(ValueFromPipeline)] 94 $Message 95 ) 96 97 _log -Type Request -Message $Message 98} 99 100function _info { 101 param( 102 [Parameter(ValueFromPipeline)] 103 $Message 104 ) 105 106 _log -Type Information -Message $Message 107} 108 109function _warn { 110 param( 111 [Parameter(ValueFromPipeline)] 112 $Message 113 ) 114 115 _log -Type Warning -Message $Message 116} 117 118function _fatal { 119 param ( 120 [Parameter(ValueFromPipeline)] 121 $Message 122 ) 123 124 _log -Type Fatal $Message 125} 126 127function ConvertFrom-Markdown2 { 128 param ( 129 $Path, 130 $Location, 131 $Root 132 ) 133 $Location = (gi $Location).FullName 134 135 $eee = ($Location -replace "^$Root", '' -split '/')[1..1024] # whatever 136 $LinkRoot = $Root 137 138 # try to find .git in 139 for ($i=$eee.count ; $i -gt 0 ; $i--) { 140 $TestPath = $eee[0..($i-1)] -join '/' 141 142 if (Test-Path -LiteralPath "$Root/$TestPath/.git") { 143 $LinkRoot = "/$TestPath" 144 break; 145 } 146 } 147 148 $Markdown = gc -lit $Path 149 150 # fix naked roots that only work on github by defining the link root 151 # surely this will not break anything 152 $Markdown = $Markdown -replace '\]\(\/', "]($Root/" 153 154 # look for a link with no colon (using this a proxy for having a protocol AKA not being a domain-relative path) 155 $Markdown = $Markdown -replace '(?<=\]\().*?(?=\))', { 156 $_ -notlike '*:*' ? "$LinkRoot/$_" : $_ 157 } 158 159 # look closer: some READMEs decide to use full html in them, so we have to detect that as well... 160 $Markdown = $Markdown -replace '(?<=href=\x22|src=\x22).*?(?=\x22)', { 161 $_ -like '*/*' ? "$LinkRoot/$_" : $_ 162 } 163 164 return (ConvertFrom-Markdown -InputObject ($Markdown -join "`n")).Html 165} 166 167# check if path is a file or a symlink pointing to a file test-path seems to 168# have an issue with strange file names, like "[̸D̶A̴T̷A̸ ̴E̸X̷P̷U̴N̸G̶E̶D̸]̷" 169# this is also an order of magnitude faster than test-path 170function _is_file ([IO.FileSystemInfo] $Path) { 171 return ( 172 # [IO.File]::Exists($Path) -or [IO.File]::Exists($Path.linktarget) 173 # $null -ne $Path -and $Path.GetType() -eq [IO.FileInfo] 174 $null -ne $Path -and -not $Path.PSIsContainer 175 ) 176} 177 178function _is_readable ([IO.FileSystemInfo] $Path) { 179 # directories can only be "read" if we also have x permission 180 return ((_is_file $Path) ? (_can_r__ $Path) : (_can_r_x $Path)) 181} 182 183# encode string as uri 184function _encode { 185 param ( 186 [String] $part 187 ) 188 189 return [URI]::EscapeDataString($part) 190} 191 192# seperately encode all parts of the url path to avoid encoding the slash or 193# something silly 194function _encode_path { 195 param ( 196 [String] $Path, 197 [Switch] $NoEnd 198 ) 199 200 if ($NoEnd) { 201 $FixedPath = (_encode_path (( $Path -split '/' | Select-Object -SkipLast 1) -Join '/')) 202 if ($FixedPath -eq [String]::Empty) { 203 return '/' 204 } else { 205 return $FixedPath 206 } 207 } else { 208 return ($Path -split '/' | % { _encode $_ }) -join '/' 209 } 210} 211 212function _get_splash { 213 (Get-PodeConfig).Utatane.Splashes | Get-Random | ConvertFrom-Markdown | % Html 214} 215 216function _format_size ([uint64] $size) { 217 switch ($Size) { 218 {$size -ge 1tb} { return '{0:n2} TiB' -f ($size/1tb) } 219 {$size -ge 1gb} { return '{0:n2} GiB' -f ($size/1gb) } 220 {$size -ge 1mb} { return '{0:n2} MiB' -f ($size/1mb) } 221 {$size -ge 1kb} { return '{0:n2} KiB' -f ($size/1kb) } 222 {$size -eq '-'} { return '0 B' } 223 default { return "$size B" } 224 } 225} 226 227filter _check_path_on_blacklist ($Path) { 228 ((Get-PodeConfig).Utatane.DoNotServe | ? { $Path -like $_ }).Count -gt 0 229} 230 231function _check_path ($Root, $FullPath) { 232 $Resolved = Get-Item -LiteralPath $FullPath -EA Ignore 233 234 return ( 235 $null -ne $Resolved ` 236 -and $Resolved.FullName.StartsWith($Root) ` 237 -and !(_check_path_on_blacklist $Resolved.Name) ` 238 ) 239} 240 241function _script_hx_hs_jq { 242 script -Src /.nhnd/_hyperscript.js 243 script -Src /.nhnd/_hs_tailwind.js 244 script -Src /.nhnd/htmx.js 245 script -Src /.nhnd/jquery.js 246 meta -Name htmx-config -Content (@{ scrollIntoViewOnBoost=$false; defaultHideShowStrategy='twDisplay' } | ConvertTo-Json -Compress) 247} 248 249function _footer { 250 footer -Class 'pb-10 pt-5' { 251 span -Class 'justify-center w-full flex text-current/75 gap-[1ch]' { 252 '🌟🌟🌟' 253 a -Href '/about' { 'running helpimnotdrowning/Utatane' } 254 '🌟🌟🌟' 255 } 256 } 257} 258 259function _icon ($Label, $Icon) { 260 return abbr -Title $Label { 261 $Icon 262 } 263} 264 265$MapExtensionToAbbr = @{ 266 '.avc' = ( _icon 'Video file' '🎞️' ) 267 '.flv' = ( _icon 'Video file' '🎞️' ) 268 '.mts' = ( _icon 'Video file' '🎞️' ) 269 '.m2ts' = ( _icon 'Video file' '🎞️' ) 270 '.m4v' = ( _icon 'Video file' '🎞️' ) 271 '.mkv' = ( _icon 'Video file' '🎞️' ) 272 '.mov' = ( _icon 'Video file' '🎞️' ) 273 '.mp4' = ( _icon 'Video file' '🎞️' ) 274 '.ts' = ( _icon 'Video file' '🎞️' ) 275 '.webm' = ( _icon 'Video file' '🎞️' ) 276 '.wmv' = ( _icon 'Video file' '🎞️' ) 277 278 '.aac' = ( _icon 'Audio file' '🔊' ) 279 '.alac' = ( _icon 'Audio file' '🔊' ) 280 '.flac' = ( _icon 'Audio file' '🔊' ) 281 '.m4a' = ( _icon 'Audio file' '🔊' ) 282 '.mp3' = ( _icon 'Audio file' '🔊' ) 283 '.opus' = ( _icon 'Audio file' '🔊' ) 284 '.wav' = ( _icon 'Audio file' '🔊' ) 285 '.ogg' = ( _icon 'Audio file' '🔊' ) 286 '.mus' = ( _icon 'Audio file' '🔊' ) 287 288 '.avif' = ( _icon 'Image file' '🖼️' ) 289 '.bmp' = ( _icon 'Image file' '🖼️' ) 290 '.gif' = ( _icon 'Image file' '🖼️' ) 291 '.ico' = ( _icon 'Image file' '🖼️' ) 292 '.heic' = ( _icon 'Image file' '🖼️' ) 293 '.heif' = ( _icon 'Image file' '🖼️' ) 294 '.jpe?g' = ( _icon 'Image file' '🖼️' ) 295 '.jfif' = ( _icon 'Image file' '🖼️' ) 296 '.jxl' = ( _icon 'Image file' '🖼️' ) 297 '.j2c' = ( _icon 'Image file' '🖼️' ) 298 '.jp2' = ( _icon 'Image file' '🖼️' ) 299 '.a?png' = ( _icon 'Image file' '🖼️' ) 300 '.svg' = ( _icon 'Image file' '🖼️' ) 301 '.tiff?' = ( _icon 'Image file' '🖼️' ) 302 '.webp' = ( _icon 'Image file' '🖼️' ) 303 '.pdn' = ( _icon 'Image file' '🖼️' ) 304 '.psd' = ( _icon 'Image file' '🖼️' ) 305 '.xcf' = ( _icon 'Image file' '🖼️' ) 306 307 '.ass' = ( _icon 'Subtitle file' '💬' ) 308 '.lrc' = ( _icon 'Subtitle file' '💬' ) 309 '.srt' = ( _icon 'Subtitle file' '💬' ) 310 '.srv3' = ( _icon 'Subtitle file' '💬' ) 311 '.ssa' = ( _icon 'Subtitle file' '💬' ) 312 '.vtt' = ( _icon 'Subtitle file' '💬' ) 313 314 '.bat' = ( _icon 'Windows script file' '📜' ) 315 '.cmd' = ( _icon 'Windows script file' '📜' ) 316 '.htm' = ( _icon 'HTML file' '📜' ) 317 '.html' = ( _icon 'HTML file' '📜' ) 318 '.xhtml' = ( _icon 'XHTML file' '📜' ) 319 '.bash' = ( _icon 'Shell script' '📜' ) 320 '.zsh' = ( _icon 'Shell script' '📜' ) 321 '.sh' = ( _icon 'Shell script' '📜' ) 322 '.cpp' = ( _icon 'C++ source file' '📜' ) 323 '.cxx' = ( _icon 'C++ source file' '📜' ) 324 '.cc' = ( _icon 'C++ source file' '📜' ) 325 '.hpp' = ( _icon 'C++ header file' '📜' ) 326 '.hxx' = ( _icon 'C++ header file' '📜' ) 327 '.hh' = ( _icon 'C++ header file' '📜' ) 328 329 '.py' = ( _icon 'Python script' '📜' ) 330 '.pyc' = ( _icon 'Compiled Python bytecode' '📜' ) 331 '.pyo' = ( _icon 'Compiled Python bytecode' '📜' ) 332 '.psm1' = ( _icon 'PowerShell module file' '📜' ) 333 '.psd1' = ( _icon 'PowerShell data file' '📜' ) 334 '.ps1' = ( _icon 'PowerShell script' '📜' ) 335 '.js' = ( _icon 'JavaScript source code' '📜' ) 336 '.css' = ( _icon 'CSS style sheet' '📜' ) 337 '.cs' = ( _icon 'C# source file' '📜' ) 338 '.c' = ( _icon 'C source file' '📜' ) 339 '.h' = ( _icon 'C header file' '📜' ) 340 '.java' = ( _icon 'Java source file' '📜' ) 341 342 '.json' = ( _icon 'Data/config file' '📜' ) 343 '.json5' = ( _icon 'Data/config file' '📜' ) 344 '.xml' = ( _icon 'Data/config file' '📜' ) 345 '.yaml' = ( _icon 'Data/config file' '📜' ) 346 '.yml' = ( _icon 'Data/config file' '📜' ) 347 '.ini' = ( _icon 'Data/config file' '📜' ) 348 '.toml' = ( _icon 'Data/config file' '📜' ) 349 '.cfg' = ( _icon 'Data/config file' '📜' ) 350 '.conf' = ( _icon 'Data/config file' '📜' ) 351 '.plist' = ( _icon 'Data/config file' '📜' ) 352 '.csv' = ( _icon 'Data/config file' '📜' ) 353 354 '.tar' = ( _icon 'File archive' '📦' ) 355 '.ar' = ( _icon 'File archive' '📦' ) 356 '.7z' = ( _icon 'File archive' '📦' ) 357 '.arc' = ( _icon 'File archive' '📦' ) 358 '.cab' = ( _icon 'File archive' '📦' ) 359 '.rar' = ( _icon 'File archive' '📦' ) 360 '.zip' = ( _icon 'File archive' '📦' ) 361 '.bz2' = ( _icon 'File archive' '📦' ) 362 '.gz' = ( _icon 'File archive' '📦' ) 363 '.lz' = ( _icon 'File archive' '📦' ) 364 '.lzma' = ( _icon 'File archive' '📦' ) 365 '.lzo' = ( _icon 'File archive' '📦' ) 366 '.xz' = ( _icon 'File archive' '📦' ) 367 '.Z' = ( _icon 'File archive' '📦' ) 368 '.zst' = ( _icon 'File archive' '📦' ) 369 370 '.apk' = ( _icon 'Android package' '📦' ) 371 '.deb' = ( _icon 'Debian package' '📦' ) 372 '.rpm' = ( _icon 'RPM package' '📦' ) 373 '.ipa' = ( _icon 'iOS/iPadOS package' '📦' ) 374 '.AppImage' = ( _icon 'AppImage bundle' '📦' ) 375 '.jar' = ( _icon 'Java archive' '☕' ) 376 377 '.dmg' = ( _icon 'Disk image' '💿' ) 378 '.iso' = ( _icon 'Disk image' '💿' ) 379 '.img' = ( _icon 'Disk image' '💿' ) 380 '.wim' = ( _icon 'Disk image' '💿' ) 381 '.esd' = ( _icon 'Disk image' '💿' ) 382 383 384 '.docx' = ( _icon 'Document' '📃' ) 385 '.doc' = ( _icon 'Document' '📃' ) 386 '.odt' = ( _icon 'Document' '📃' ) 387 '.pptx' = ( _icon 'Presentation' '📃' ) 388 '.ppt' = ( _icon 'Presentation' '📃' ) 389 '.odp' = ( _icon 'Presentation' '📃' ) 390 '.xslx' = ( _icon 'Spreadsheet' '📃' ) 391 '.xsl' = ( _icon 'Spreadsheet' '📃' ) 392 '.ods' = ( _icon 'Spreadsheet' '📃' ) 393 '.pdf' = ( _icon 'PDF' '📃' ) 394 '.md' = ( _icon 'Markdown document' '📃' ) 395 '.rst' = ( _icon 'reStructuredText document' '📃' ) 396 '.epub' = ( _icon 'EPUB e-book file' '📃' ) 397 '.log' = ( _icon 'Log file' '📃' ) 398 '.txt' = ( _icon 'Text file' '📃' ) 399 400 '.tff' = ( _icon 'Font file' '🗛' ) 401 '.otf' = ( _icon 'Font file' '🗛' ) 402 '.woff' = ( _icon 'Font file' '🗛' ) 403 '.woff2' = ( _icon 'Font file' '🗛' ) 404 405 '.mpls' = ( _icon 'Playlist file' '🎶' ) 406 '.m3u' = ( _icon 'Playlist file' '🎶' ) 407 '.m3u8' = ( _icon 'Playlist file' '🎶' ) 408 409 '.exe' = ( _icon 'Generic executable' '🔳' ) 410 '.elf' = ( _icon 'Generic executable' '🔳' ) 411 '.msi' = ( _icon 'Generic executable' '🔳' ) 412 '.msix' = ( _icon 'Generic executable' '🔳' ) 413 '.msixbundle' = ( _icon 'Generic executable' '🔳' ) 414 '.appx' = ( _icon 'Generic executable' '🔳' ) 415 '.appxbundle' = ( _icon 'Generic executable' '🔳' ) 416 417 '.dll' = ( _icon 'Dynamic library' '⚙️' ) 418 '.so' = ( _icon 'Dynamic library' '⚙️' ) 419 '.dylib' = ( _icon 'Dynamic library' '⚙️' ) 420} 421function _get_symbol_for_file ([IO.FileSystemInfo] $FSO) { 422 if (-not (_is_file $FSO)) { 423 _icon 'Directory' '📁' 424 } else { 425 $Icon = $MapExtensionToAbbr[$FSO.Extension] 426 427 if ([String]::IsNullOrEmpty($Icon)) { 428 if (-not $IsWindows -and (_can_r_x $FSO)) { 429 return _icon 'Generic executable (+x)' '🔳' 430 } else { 431 return _icon 'File' '📄' 432 } 433 } 434 return $Icon 435 } 436} 437 438function _create_table_row ([IO.FileSystemInfo] $Item, [String] $CurrentPath) { 439 $IsFile = _is_file $Item 440 441 try { 442 # if Item is a symlink, canonicalize it 443 if ($null -ne $Item.LinkTarget) { 444 # ResolvedTarget is broken, see https://github.com/PowerShell/PowerShell/issues/25724 445 $LinkDestination = Get-Item -LiteralPath (readlink $Item.FullName -m) -ErrorAction Ignore 446 447 if ($null -eq $LinkDestination) { 448 throw [System.NullReferenceException]::new("Broken symbolic link at $($Item.FullName)") 449 } 450 451 # size of symlinks shows the size of the link, not the file iself! 452 $Size = $LinkDestination.Size 453 } else { 454 $Size = $Item.Size 455 } 456 457 if (-not (_is_readable $Item)) { 458 return ( 459 tr { 460 td 461 462 td { _icon "Server does not have permission to read this $($IsFile ? 'file' : 'directory')" '⚠️' } 463 464 td -Class "file-name" -Attributes @{ "data-order" = $Item.Name } { 465 s -Class 'text-stone-500' { $IsFile ? $Item.Name : $Item.Name + '/' } 466 } 467 468 td -Class 'file-size' -Attributes @{ 'data-order' = ($IsFile ? $Size : 0) } { 469 if ($IsFile) { s { _format_size ([uint64] $Size) } } 470 } 471 472 td -Class 'file-date' -Attributes @{ 'data-order' = ( Get-Date $Item.LastWriteTime -UFormat "%s" -asutc ) } { 473 s { 474 time { 475 Get-Date $Item.LastWriteTime -Format "yyyy-MM-dd HH:mm" -AsUTC 476 } 477 } 478 } 479 } 480 ) 481 } 482 483 # Mizumiya can be slow when tens of thousands of elements are needed 484 # (case: ~39,100 to generate ~3900 rows of file information), so 485 # templating is used here. this achieves a 2x speedup (12s -> 5s) in 486 # exchange for readability. 487 488 $FilePath = _encode_path (Join-Path ($CurrentPath ? $CurrentPath : '/') $Item.Name) 489 return ( 490 @" 491<tr class="fsobject $( $IsFile ? "file" : "directory" )"> 492 <td>$( if ($IsFile) { @" 493 <a 494 download 495 class="file-dl clickable" 496 href="$FilePath" 497 aria-label="Download file"></a> 498"@ } )</td> 499 <td>$( _get_symbol_for_file $Item )</td> 500 <td class=file-name data-order="$( AttributeEncode $Item.Name )"> 501 $( if ($IsFile) { @" 502 <a 503 href="$FilePath" 504 hx-boost=false 505 >$( HTMLEncode { $Item.Name } )</a> 506"@ } else { @" 507 <a 508 href="$FilePath/" 509 hx-boost=true 510 $(<# string are added bc using a ; seperator will join with a space #>) 511 $(<# this will not #>) 512 >$( (HTMLEncode $Item.Name) + (span -Class 'dir-slash' -InnerHTML '/') )</a> 513"@ } ) 514 </td> 515 <td 516 class=file-size 517 data-order=$($IsFile ? $Size : -1) 518 >$( $IsFile ? (_format_size ([uint64] $Size)) : '' )</td> 519 <td 520 class=file-date 521 data-order=$( Get-Date $Item.LastWriteTime -UFormat "%s" -AsUTC ) 522 > 523 <time> 524 $( Get-Date $Item.LastWriteTime -Format "yyyy-MM-dd HH:mm" -AsUTC ) 525 </time> 526 </td> 527</tr> 528"@ 529 ) 530 } catch { 531 _warn "api/files.html: Row generation for $($Item.FullName) failed!!" 532 _warn $_.Exception 533 534 return ( 535 tr { 536 td 537 538 td { _icon 'Error occured while generating this row' '⚠️' } 539 540 td -Class "file-name" -Attributes @{ "data-order" = $Item.Name } { 541 s { $Item.Name } 542 } 543 544 td 545 546 td 547 } 548 ) 549 } 550} 551 552function Get-PathParent { 553 [CmdletBinding()] 554 param ( 555 [String] $Path 556 ) 557 558 $Parent = $Path | % split '/' | Select-Object -SkipLast 1 | Join-String -Separator '/' 559 560 return ($Parent ? '/' : $Parent) 561} 562 563function ConvertFrom-AcceptHeader { 564 param ( 565 [Parameter(ValueFromPipeline)] 566 [String] $AcceptString 567 ) 568 569 if ([String]::IsNullOrWhiteSpace($AcceptString)) { 570 return [PSCustomObject]@{ 571 Quality = 1d 572 MediaType = '*/*' 573 } 574 } 575 576 return $AcceptString -split ',' | % { 577 try { 578 $Accept = [System.Net.Http.Headers.MediaTypeWithQualityHeaderValue]::Parse($_) 579 return [PSCustomObject]@{ 580 Quality = ($null -eq $Accept.Quality) ? 1d : $Accept.Quality 581 MediaType = $Accept.MediaType 582 } 583 } catch { 584 return; 585 } 586 } | Sort-Object -Descending -Property Quality 587} 588 589function _mime_to_ext { 590 param( 591 [String] $Mime 592 ) 593 594 switch ($Mime) { 595 'text/html' { 'html' } 596 'application/json' { 'json' } 597 'application/xml' { 'xml' } 598 'text/xml' { 'xml' } 599 'application/xhtml+xml' { 'xhtml' } 600 'application/atom+xml' { 'atom' } 601 default { 'html' } 602 } 603} 604 605function _render_view { 606 param( 607 [String] $Page, 608 $Data 609 ) 610 611 :loop foreach ($Accept in $WebEvent.RequestAccept) { 612 # SEE `Set-PodeViewEngine -Type Mizumiya` for the ACTUAL work being done 613 switch ( _mime_to_ext $Accept.MediaType ) { 614 'json' { 615 Write-PodeViewResponse -Path "$Page.json.ps1" -Data $Data 616 break loop 617 } 618 619 'html' { 620 Write-PodeViewResponse -Path "$Page.html.ps1" -Data $Data 621 break loop 622 } 623 624 'xml' { 625 Write-PodeViewResponse -Path "$Page.xml.ps1" -Data $Data 626 break loop 627 } 628 629 'atom' { 630 Write-PodeViewResponse -Path "$Page.atom.ps1" -Data $Data 631 break loop 632 } 633 } 634 } 635}