diff --git a/.github/mkdocs.yml b/.github/mkdocs.yml deleted file mode 100644 index df5e17a..0000000 --- a/.github/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: -{{ REPO_NAME }}- -theme: - name: material - language: en - font: - text: Roboto - code: Sono - logo: Assets/icon.png - favicon: Assets/icon.png - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/link - name: Switch to dark mode - # Palette toggle for dark mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - primary: black - accent: green - icon: material/toggle-switch-off-outline - name: Switch to light mode - # Palette toggle for light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - primary: indigo - accent: green - icon: material/toggle-switch - name: Switch to system preference - icon: - repo: material/github - features: - - navigation.instant - - navigation.instant.progress - - navigation.indexes - - navigation.top - - navigation.tracking - - navigation.expand - - search.suggest - - search.highlight - -repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- -repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- - -plugins: - - search - -markdown_extensions: - - toc: - permalink: true # Adds a link icon to headings - - attr_list - - admonition - - md_in_html - - pymdownx.details # Enables collapsible admonitions - -extra: - social: - - icon: fontawesome/brands/discord - link: https://discord.gg/jedJWCPAhD - name: -{{ REPO_OWNER }}- on Discord - - icon: fontawesome/brands/github - link: https://github.com/-{{ REPO_OWNER }}-/ - name: -{{ REPO_OWNER }}- on GitHub - consent: - title: Cookie consent - description: >- - We use cookies to recognize your repeated visits and preferences, as well - as to measure the effectiveness of our documentation and whether users - find what they're searching for. With your consent, you're helping us to - make our documentation better. - actions: - - accept - - reject diff --git a/.github/zensical.toml b/.github/zensical.toml new file mode 100644 index 0000000..a44ef7b --- /dev/null +++ b/.github/zensical.toml @@ -0,0 +1,121 @@ +# Zensical configuration +# https://zensical.org/docs/setup/basics/ +# Process-PSModule v8 copies this file into outputs/site and stages generated +# documentation under outputs/site/docs. + +[project] +site_name = "Fonts" +site_url = "https://psmodule.io/Fonts/" +docs_dir = "docs" +repo_name = "PSModule/Fonts" +repo_url = "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/PSModule/Fonts" +edit_uri = "edit/main/" +copyright = "Copyright © 2023 - 2026 PSModule" +extra_javascript = [ + "https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js", + # The v8 site assembly does not copy repository override directories. + "https://cdn.jsdelivr.net/gh/PSModule/Process-PSModule@59e056cab018777a8f158f88fcabdb57125494ef/docs/overrides/assets/javascripts/tablesort.js", +] +extra_css = [ + # Pin the current Process-PSModule navigation behavior used as the baseline. + "https://cdn.jsdelivr.net/gh/PSModule/Process-PSModule@59e056cab018777a8f158f88fcabdb57125494ef/docs/overrides/assets/stylesheets/navigation.css", +] +[project.theme] +language = "en" +font.text = "Mona Sans" +font.code = "Source Code Pro" +logo = "https://raw.githubusercontent.com/PSModule/Fonts/main/icon/icon.png" +favicon = "https://raw.githubusercontent.com/PSModule/Fonts/main/icon/icon.png" +features = [ + "announce.dismiss", + "content.code.annotate", + "content.code.copy", + "content.tooltips", + "navigation.expand", + "navigation.footer", + "navigation.indexes", + "navigation.instant", + "navigation.instant.prefetch", + "navigation.instant.preview", + "navigation.instant.progress", + "navigation.tabs", + "navigation.top", + "navigation.tracking", + "search.highlight", + "search.share", + "search.suggest", + "toc.follow", +] + +[project.theme.icon] +repo = "material/github" +link = "material/link-variant" + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "material/link" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "light blue" +toggle.icon = "material/toggle-switch-off-outline" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "black" +accent = "light blue" +toggle.icon = "material/toggle-switch" +toggle.name = "Switch to system preference" + +[project.plugins.meta] + +[project.plugins.search] + +[project.markdown_extensions.toc] +permalink = "#" + +[project.markdown_extensions.attr_list] + +[project.markdown_extensions.abbr] + +[project.markdown_extensions.admonition] + +[project.markdown_extensions.def_list] + +[project.markdown_extensions.footnotes] + +[project.markdown_extensions.md_in_html] + +[project.markdown_extensions.tables] + +[project.markdown_extensions.pymdownx.details] + +[project.markdown_extensions.pymdownx.superfences] +custom_fences = [ + { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" }, +] + +[project.markdown_extensions.pymdownx.tasklist] +custom_checkbox = true + +[project.markdown_extensions.pymdownx.snippets] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.gg/jedJWCPAhD" +name = "PSModule on Discord" + +[[project.extra.social]] +icon = "material/powershell" +link = "https://www.powershellgallery.com/packages/Fonts" +name = "Fonts on the PowerShell Gallery" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "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/PSModule/Fonts" +name = "Fonts on GitHub" diff --git a/README.md b/README.md index 22e2735..ed07171 100644 --- a/README.md +++ b/README.md @@ -84,5 +84,5 @@ Please see the issues tab on this project and submit a new issue that matches yo ### For Developers -If you do code, we'd love to have your contributions. Please read the [Contribution guidelines](CONTRIBUTING.md) for more information. -You can either help by picking up an existing issue or submit a new one if you have an idea for a new feature or improvement. +If you do code, we'd love to have your contributions. You can help by picking up an +[existing issue](https://github.com/PSModule/Fonts/issues) or submitting a new one if you have an idea for a feature or improvement. diff --git a/src/functions/public/Get-Font.ps1 b/src/functions/public/Get-Font.ps1 index 4b8d019..80616db 100644 --- a/src/functions/public/Get-Font.ps1 +++ b/src/functions/public/Get-Font.ps1 @@ -105,7 +105,7 @@ function Get-Font { $folderExists = Test-Path -Path $fontFolderPath Write-Verbose "[$functionName] - [$scopeName] - Folder exists: [$folderExists]" if (-not $folderExists) { - return $fonts + continue } $installedFonts = Get-ChildItem -Path $fontFolderPath -File $installedFontsCount = $($installedFonts.Count) diff --git a/src/functions/public/Install-Font.ps1 b/src/functions/public/Install-Font.ps1 index f39b1d2..56d9ea4 100644 --- a/src/functions/public/Install-Font.ps1 +++ b/src/functions/public/Install-Font.ps1 @@ -209,11 +209,11 @@ Please run the command again with elevated rights (Run as Administrator) or prov do { try { - $null = $fontFile.CopyTo($fontDestinationFilePath) + $null = $fontFile.CopyTo($fontDestinationFilePath, $Force.IsPresent) $fileCopied = $true } catch { $retryCount++ - if (-not $fileRemoved -and $retryCount -eq $maxRetries) { + if (-not $fileCopied -and $retryCount -eq $maxRetries) { Write-Error $_ Write-Error "Failed [$retryCount/$maxRetries] - Stopping" break diff --git a/src/functions/public/Uninstall-Font.ps1 b/src/functions/public/Uninstall-Font.ps1 index 8f5e56f..85e2783 100644 --- a/src/functions/public/Uninstall-Font.ps1 +++ b/src/functions/public/Uninstall-Font.ps1 @@ -71,7 +71,7 @@ function Uninstall-Font { if ($Scope -contains 'AllUsers' -and -not (IsAdmin)) { $errorMessage = @" -Administrator rights are required to uninstall fonts in [$($script:FontFolderPath['AllUsers'])]. +Administrator rights are required to uninstall fonts in [$($script:FontFolderPathMap[$script:OS]['AllUsers'])]. Please run the command again with elevated rights (Run as Administrator) or provide '-Scope CurrentUser' to your command. "@ throw $errorMessage diff --git a/tests/Fonts.Tests.ps1 b/tests/Fonts.Tests.ps1 index 7074ab6..777324b 100644 --- a/tests/Fonts.Tests.ps1 +++ b/tests/Fonts.Tests.ps1 @@ -26,6 +26,57 @@ Describe 'Fonts' { Write-Verbose ($fonts | Out-String) -Verbose } } + + It 'Should return matching fonts from the configured folder' { + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Get-Font' + $null = New-Item -Path $fontFolderPath -ItemType Directory + [System.IO.File]::WriteAllText((Join-Path -Path $fontFolderPath -ChildPath 'Alpha-Regular.ttf'), 'alpha') + [System.IO.File]::WriteAllText((Join-Path -Path $fontFolderPath -ChildPath 'Beta-Bold.otf'), 'beta') + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + } { + param($FontFolderPath) + + $originalOS = $script:OS + $originalFontFolderPath = $script:FontFolderPathMap['MacOS']['CurrentUser'] + try { + $script:OS = 'MacOS' + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $FontFolderPath + + $fonts = @(Get-Font -Name 'Alpha*', 'Beta*') + + $fonts.Count | Should-Be 2 + @($fonts.Name | Sort-Object) | Should-BeCollection @('Alpha-Regular', 'Beta-Bold') + @($fonts.Scope) | Should-BeCollection @('CurrentUser', 'CurrentUser') + } finally { + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $originalFontFolderPath + $script:OS = $originalOS + } + } + } + + It 'Should return no fonts when the configured folder does not exist' { + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Missing' + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + } { + param($FontFolderPath) + + $originalOS = $script:OS + $originalFontFolderPath = $script:FontFolderPathMap['MacOS']['CurrentUser'] + try { + $script:OS = 'MacOS' + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $FontFolderPath + + @(Get-Font).Count | Should-Be 0 + } finally { + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $originalFontFolderPath + $script:OS = $originalOS + } + } + } } Context 'Function: Install-Font' { @@ -38,6 +89,131 @@ Describe 'Fonts' { Write-Verbose "Installed font: 'CascadiaCodePL'" -Verbose Write-Verbose (Get-Font | Out-String) -Verbose } + + It 'Should overwrite an existing font when Force is specified' { + $fontPath = Join-Path -Path $PSScriptRoot -ChildPath 'Fonts/CascadiaCodePL.ttf' + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Fonts' + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + FontPath = $fontPath + } { + param($FontFolderPath, $FontPath) + + $originalFontFolderPath = $script:FontFolderPathMap[$script:OS]['CurrentUser'] + try { + $script:FontFolderPathMap[$script:OS]['CurrentUser'] = $FontFolderPath + Mock -CommandName New-ItemProperty + Mock -CommandName Start-Sleep + if ($IsLinux) { + Mock -CommandName fc-cache + } + + Install-Font -Path $FontPath -ErrorAction Stop + $installedFontPath = Join-Path -Path $FontFolderPath -ChildPath 'CascadiaCodePL.ttf' + [System.IO.File]::WriteAllText($installedFontPath, 'stale font data') + + Install-Font -Path $FontPath -Force -ErrorAction Stop + + $expectedHash = (Get-FileHash -LiteralPath $FontPath).Hash + $actualHash = (Get-FileHash -LiteralPath $installedFontPath).Hash + $actualHash | Should-Be $expectedHash + } finally { + $script:FontFolderPathMap[$script:OS]['CurrentUser'] = $originalFontFolderPath + } + } + } + + It 'Should install supported fonts recursively and skip unsupported files' { + $fontPath = Join-Path -Path $PSScriptRoot -ChildPath 'Fonts/CascadiaCodePL.ttf' + $sourceFolderPath = Join-Path -Path $TestDrive -ChildPath 'Install-Source' + $nestedFolderPath = Join-Path -Path $sourceFolderPath -ChildPath 'Nested' + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Install-Destination' + $null = New-Item -Path $nestedFolderPath -ItemType Directory -Force + Copy-Item -LiteralPath $fontPath -Destination (Join-Path -Path $sourceFolderPath -ChildPath 'Alpha.ttf') + Copy-Item -LiteralPath $fontPath -Destination (Join-Path -Path $nestedFolderPath -ChildPath 'Beta.otf') + [System.IO.File]::WriteAllText((Join-Path -Path $sourceFolderPath -ChildPath 'Readme.txt'), 'not a font') + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + SourceFolderPath = $sourceFolderPath + } { + param($FontFolderPath, $SourceFolderPath) + + $originalOS = $script:OS + $originalFontFolderPath = $script:FontFolderPathMap['MacOS']['CurrentUser'] + try { + $script:OS = 'MacOS' + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $FontFolderPath + + Install-Font -Path $SourceFolderPath -Recurse -ErrorAction Stop + + Test-Path -LiteralPath (Join-Path -Path $FontFolderPath -ChildPath 'Alpha.ttf') | Should-BeTrue + Test-Path -LiteralPath (Join-Path -Path $FontFolderPath -ChildPath 'Beta.otf') | Should-BeTrue + Test-Path -LiteralPath (Join-Path -Path $FontFolderPath -ChildPath 'Readme.txt') | Should-BeFalse + + $installedFontPath = Join-Path -Path $FontFolderPath -ChildPath 'Alpha.ttf' + [System.IO.File]::WriteAllText($installedFontPath, 'installed font remains unchanged') + Install-Font -Path (Join-Path -Path $SourceFolderPath -ChildPath 'Alpha.ttf') -ErrorAction Stop + [System.IO.File]::ReadAllText($installedFontPath) | Should-Be 'installed font remains unchanged' + } finally { + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $originalFontFolderPath + $script:OS = $originalOS + } + } + } + + It 'Should report a missing source path' { + $missingFontPath = Join-Path -Path $TestDrive -ChildPath 'Missing.ttf' + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Missing-Destination' + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + MissingFontPath = $missingFontPath + } { + param($FontFolderPath, $MissingFontPath) + + $originalOS = $script:OS + $originalFontFolderPath = $script:FontFolderPathMap['MacOS']['CurrentUser'] + try { + $script:OS = 'MacOS' + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $FontFolderPath + $errors = [System.Collections.Generic.List[object]]::new() + + Install-Font -Path $MissingFontPath -ErrorAction SilentlyContinue -ErrorVariable '+errors' + + $errors.Count | Should-Be 1 + $errors[0].ToString() | Should-BeLikeString '*Path not found*' + } finally { + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $originalFontFolderPath + $script:OS = $originalOS + } + } + } + + It 'Should reject an all-users install without administrator rights' { + $fontPath = Join-Path -Path $PSScriptRoot -ChildPath 'Fonts/CascadiaCodePL.ttf' + + InModuleScope Fonts -Parameters @{ + FontPath = $fontPath + } { + param($FontPath) + + Mock -CommandName IsAdmin -MockWith { $false } + $errorRecord = $null + try { + Install-Font -Path $FontPath -Scope AllUsers -ErrorAction Stop + } catch { + $errorRecord = $_ + } + + $errorRecord | Should-NotBeNull + $errorRecord.Exception.Message | Should-BeLikeString '*Administrator rights are required*' + $errorRecord.Exception.Message | + Should-BeLikeString "*$($script:FontFolderPathMap[$script:OS]['AllUsers'])*" + } + } + It "Should return the installed font 'CascadiaCodePL'" { $font = Get-Font -Name 'CascadiaCodePL' Write-Verbose ($font | Out-String) -Verbose @@ -57,6 +233,96 @@ Describe 'Fonts' { Write-Verbose ($font | Out-String) -Verbose $font | Should-BeNull } + + It 'Should retry a file removal that initially fails' { + $fontFolderPath = Join-Path -Path $TestDrive -ChildPath 'Uninstall-Retry' + $null = New-Item -Path $fontFolderPath -ItemType Directory + $installedFontPath = Join-Path -Path $fontFolderPath -ChildPath 'RetryFont.ttf' + [System.IO.File]::WriteAllText($installedFontPath, 'font') + + InModuleScope Fonts -Parameters @{ + FontFolderPath = $fontFolderPath + } { + param($FontFolderPath) + + $originalOS = $script:OS + $originalFontFolderPath = $script:FontFolderPathMap['MacOS']['CurrentUser'] + try { + $script:OS = 'MacOS' + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $FontFolderPath + $script:removeAttemptCount = 0 + Mock -CommandName Start-Sleep + Mock -CommandName Remove-Item -MockWith { + param($Path) + + $script:removeAttemptCount++ + if ($script:removeAttemptCount -eq 1) { + throw 'Font file is locked.' + } + [System.IO.File]::Delete($Path) + } + + Uninstall-Font -Name 'RetryFont' -ErrorAction Stop + + $script:removeAttemptCount | Should-Be 2 + Should-Invoke -CommandName Start-Sleep -Times 1 -Exactly + Test-Path -LiteralPath (Join-Path -Path $FontFolderPath -ChildPath 'RetryFont.ttf') | Should-BeFalse + } finally { + $script:FontFolderPathMap['MacOS']['CurrentUser'] = $originalFontFolderPath + $script:OS = $originalOS + } + } + } + + It 'Should warn when an installed font file is already missing' { + $missingFontPath = Join-Path -Path $TestDrive -ChildPath 'AlreadyMissing.ttf' + + InModuleScope Fonts -Parameters @{ + MissingFontPath = $missingFontPath + } { + param($MissingFontPath) + + $originalOS = $script:OS + try { + $script:OS = 'MacOS' + $script:missingFontPathForTest = $MissingFontPath + Mock -CommandName Get-Font -MockWith { + [PSCustomObject]@{ + Name = 'AlreadyMissing' + Path = $script:missingFontPathForTest + Scope = 'CurrentUser' + } + } + $warnings = [System.Collections.Generic.List[object]]::new() + + Uninstall-Font -Name 'AlreadyMissing' -WarningVariable '+warnings' + + $warnings.Count | Should-Be 1 + $warnings[0].ToString() | Should-BeLikeString '*does not exist*' + } finally { + Remove-Variable -Name missingFontPathForTest -Scope Script + $script:OS = $originalOS + } + } + } + + It 'Should reject an all-users uninstall without administrator rights' { + InModuleScope Fonts { + Mock -CommandName IsAdmin -MockWith { $false } + $errorRecord = $null + try { + Uninstall-Font -Name 'CascadiaCodePL' -Scope AllUsers -ErrorAction Stop + } catch { + $errorRecord = $_ + } + + $errorRecord | Should-NotBeNull + $errorRecord.Exception.Message | Should-BeLikeString '*Administrator rights are required*' + $errorRecord.Exception.Message | + Should-BeLikeString "*$($script:FontFolderPathMap[$script:OS]['AllUsers'])*" + } + } + It 'Should install and uninstall a font based on wildcard' { $fontPath = Join-Path -Path $PSScriptRoot -ChildPath 'Fonts/CascadiaCodePL.ttf' Install-Font -Path $fontPath -Verbose