Explorar el Código

Merge branch 'windows' of gogsadmin/dotfiles into master

gogsadmin hace 1 mes
padre
commit
ca802a385a
Se han modificado 5 ficheros con 243 adiciones y 1 borrados
  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).
 
+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
 
 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
 ```
+> 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.
 

+ 1 - 1
dot

@@ -40,7 +40,7 @@ _stow(){
     stow_package="$4"
     (
         [ "$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
+}