Browse Source

Merge branch 'windows' of gogsadmin/dotfiles into master

gogsadmin 1 month ago
parent
commit
ca802a385a
5 changed files with 243 additions and 1 deletions
  1. 18 0
      LICENSE
  2. 10 0
      Readme.md
  3. 1 1
      dot
  4. 108 0
      dot.ps1
  5. 106 0
      park.ps1

+ 18 - 0
LICENSE

@@ -0,0 +1,18 @@
+Copyright 2025 Daniel Sheffield
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 10 - 0
Readme.md

@@ -2,6 +2,15 @@
 
 
 Manage your personal dotfiles with [GNU Stow](https://www.gnu.org/software/stow/manual/stow.html).
 Manage your personal dotfiles with [GNU Stow](https://www.gnu.org/software/stow/manual/stow.html).
 
 
+GNU Stow can be used directly, but a helper script `./dot` is provided to easy
+the syntax and to support my custom package [Target](#targets) feature.
+
+This is a Linux first design, but there is rudimentry support for Windows.
+
+> Windows users should use the `./dot.ps1` script instead of the `./dot` script.
+>
+> As GNU Stow is currently unavailable on Windows, `./park.ps1` provides bare minimum functionality for the `./dot.ps1` Windows implementation.
+
 ## Usage
 ## Usage
 
 
 Keep your dotfiles in your git repo and symlink them into the correct place, allowing you to edit them without forgetting to copy those changes back into your repo.
 Keep your dotfiles in your git repo and symlink them into the correct place, allowing you to edit them without forgetting to copy those changes back into your repo.
@@ -89,6 +98,7 @@ Or, if the `vim` package has no other [targets](#targets), you can use GNU Stow
 ```
 ```
 stow -t "$HOME" vim
 stow -t "$HOME" vim
 ```
 ```
+> Windows users may use the `park.ps1` script as GNU Stow
 
 
 Multiple [targets](#targets) are supported in the case a given package has a mix of user config and system config.
 Multiple [targets](#targets) are supported in the case a given package has a mix of user config and system config.
 
 

+ 1 - 1
dot

@@ -40,7 +40,7 @@ _stow(){
     stow_package="$4"
     stow_package="$4"
     (
     (
         [ "$DEBUG" ] && set -x
         [ "$DEBUG" ] && set -x
-        $stow -d "${stow_dir}" -t "${stow_target_dir}" "${stow_action}" "${stow_package}"
+        $stow --no-folding -d "${stow_dir}" -t "${stow_target_dir}" "${stow_action}" "${stow_package}"
     )
     )
 }
 }
 
 

+ 108 - 0
dot.ps1

@@ -0,0 +1,108 @@
+#!/usr/bin/env pwsh
+[CmdletBinding()]
+param (
+    [Parameter(Mandatory = $true)][string]$Action,
+    [Parameter(Mandatory = $true)][string]$Package,
+    [string]$Target
+)
+
+$ErrorActionPreference = "Stop"
+
+if ($env:DEBUG) {
+    $stowArgs = @{
+        Simulate = $true
+        Verbose  = $true
+        NoFolding = $true
+    }
+} else {
+    $stowArgs = @{
+        NoFolding = $true
+    }
+}
+
+# Default to current directory if not set
+$DOTFILES_DIR = $env:DOTFILES_DIR
+if (-not $DOTFILES_DIR) {
+    $DOTFILES_DIR = (Get-Location).Path
+}
+
+function ActionToArg {
+    param ([string]$action)
+    switch ($action) {
+        "remove" { return @{ delete = $true }}
+        "update" { return @{ restow = $true }}
+        "apply"  { return @{ stow = $true }}
+        default  { return @{} }
+    }
+}
+
+function TargetToDest {
+    param ([string]$target)
+    switch ($target) {
+        "user"   {
+            $vhome = $env:HOME
+            if (-not $vhome) {
+                $vhome = [Environment]::GetFolderPath("UserProfile")
+            }
+            return @{ target = $vhome }
+        }
+        "system" { return @{ target = "/" }} # TODO: should be C:/ or something
+        default  { return @{ }}
+    }
+}
+
+$actionArg = ActionToArg $Action
+$stowArgs += $actionArg
+if ($actionArg.Count -eq 0) {
+    Write-Host "No such action: $Action"
+    exit 1
+}
+
+if ($Target) {
+    $packageTargetPath = Join-Path -Path "$DOTFILES_DIR/$Package" -ChildPath $Target
+    if (Test-Path $packageTargetPath -PathType Container) {
+        $stowArgs += @{
+            dir = "$DOTFILES_DIR/$Package"
+            package = "$Target"
+        }
+        $stowArgs += TargetToDest $Target
+        ./park.ps1 @stowArgs
+    }
+    elseif ($Target -ne "user") {
+        Write-Host "Target '$Target' does not exist for package '$Package'"
+        exit 1
+    }
+    else {
+        $stowArgs += @{
+            dir = "$DOTFILES_DIR"
+            package = "$Package"
+        }
+        $stowArgs += TargetToDest "user"
+        ./park.ps1 @stowArgs
+    }
+}
+else {
+    $any = $false
+    foreach ($t in "user", "system") {
+        $path = "$DOTFILES_DIR/$Package/$t"
+        if (Test-Path $path -PathType Container) {
+            $any = $true
+            $stowArgs += @{
+                dir = "$DOTFILES_DIR/$Package"
+                package = "$t"
+            }
+            $stowArgs += TargetToDest $t
+            ./park.ps1 @stowArgs
+        }
+    }
+
+    if (-not $any) {
+        $stowArgs += @{
+            dir = "$DOTFILES_DIR"
+            package = "$Package"
+        }
+        $stowArgs += TargetToDest "user"
+        ./park.ps1 @stowArgs
+    }
+}
+

+ 106 - 0
park.ps1

@@ -0,0 +1,106 @@
+#!/usr/bin/env pwsh
+# Copyright (c) Daniel Sheffield 2025
+# All rights reserved
+#
+# Park is inspired by GNU Stow.
+#
+# It can't stow your files properly, but it will park them. Hopefully that is good enough.
+#
+# Park is designed to be a "drop-in" replacement to GNU Stow, with the following
+# limitations:
+#   * Only long options are supported
+#   * All options are ignored except:
+#      * --stow
+#      * --delete
+#      * --restow
+#      * --verbose (bool not int!)
+#      * --simulate
+#   * No tree folding by default (regardless of --nofolding option)
+#
+
+[CmdletBinding()]
+param(
+    [Parameter(Mandatory = $true)][string]$Dir,
+    [Parameter(Mandatory = $true)][string]$Package,
+    [Parameter(Mandatory = $true)][string]$Target,
+    [string]$Ignore,
+    [string]$Defer,
+    [string]$Override,
+    [switch]$Stow,
+    [switch]$Restow,
+    [switch]$Delete,
+    [switch]$Simulate,
+    [switch]$NoFolding,
+    [switch]$Adopt
+)
+
+$SourcePath = (Resolve-Path $Dir).Path
+$SourceRoot = [System.IO.Path]::GetFullPath($SourcePath)
+$SourceRoot = Join-Path $SourceRoot $Package
+$TargetRoot = [System.IO.Path]::GetFullPath($Target)
+#Write-Host "Parameters:"
+#foreach ($param in $MyInvocation.MyCommand.Parameters.Keys) {
+#    $value = Get-Variable -Name $param -ValueOnly -ErrorAction SilentlyContinue
+#    Write-Host "$param = $value"
+#}
+
+function Is-Under($child, $parent) {
+    $child = [System.IO.Path]::GetFullPath($child)
+    $parent = [System.IO.Path]::GetFullPath($parent)
+    return $child.StartsWith($parent, [System.StringComparison]::OrdinalIgnoreCase)
+}
+
+if ($Delete -or $Restow) {
+    Get-ChildItem -Path $SourceRoot -Recurse -Force | Where-Object {
+        -not $_.PSIsContainer -or $_.Attributes -match 'ReparsePoint'
+    } | ForEach-Object {
+        $relativePath = $_.FullName.Substring($SourceRoot.Length).TrimStart('\', '/')
+        $targetFile = Join-Path $TargetRoot $relativePath
+        Write-Verbose "$targetFile"
+        if (Test-Path $targetFile){
+            $targetFile = Get-Item $targetFile -Force
+            if ($targetFile.LinkType -eq 'SymbolicLink') {
+                $linkTarget = (Get-Item $targetFile.FullName -Force).Target
+                if ($linkTarget -and (Is-Under $linkTarget $SourceRoot)) {
+                    Write-Verbose "Unlinking: $($targetFile.FullName) -> $linkTarget"
+                    if (-not $Simulate) {
+                        Remove-Item $targetFile.FullName -Force
+                    }
+                }
+            }
+        }
+    }
+    return
+}
+
+if ($Stow -or $Restow) {
+    Get-ChildItem -Path $SourceRoot -Recurse -Force | Where-Object {
+        -not $_.PSIsContainer -or $_.Attributes -match 'ReparsePoint'
+    } | ForEach-Object {
+        $relativePath = $_.FullName.Substring($SourceRoot.Length).TrimStart('\', '/')
+        $targetFile = Join-Path $TargetRoot $relativePath
+        $targetDir = Split-Path $targetFile
+        if (Test-Path $targetDir) {
+            if (-not (Get-Item $targetDir).PSIsContainer) {
+                Write-Verbose "Path '$targetDir' exists but is not a directory."
+                if (-not $Simulate) {
+                    throw "Path '$targetDir' exists but is not a directory."
+                }
+            }
+        } else {
+            Write-Verbose "Creating dir: $targetDir"
+            if (-not $Simulate) {
+                New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
+            }
+        }
+    
+        Write-Verbose "Linking: $targetFile -> $($_.FullName)"
+        if (-not $Simulate) {
+            #New-Item -ItemType SymbolicLink -Path $targetFile -Target $_.FullName | Out-Null
+            #Write-Verbose 'cmd /c mklink "$($_.FullName)" "$targetFile" | Out-Null'
+            # cmd mklink works in Developer Mode, but New-Item does not
+            cmd /c mklink "$targetFile" "$($_.FullName)" | Out-Null
+        }
+    }
+    return
+}