aboutsummaryrefslogtreecommitdiff
path: root/validators/powershell
diff options
context:
space:
mode:
Diffstat (limited to 'validators/powershell')
-rw-r--r--validators/powershell/README.md46
-rw-r--r--validators/powershell/Validate-GitInfo.ps1248
2 files changed, 294 insertions, 0 deletions
diff --git a/validators/powershell/README.md b/validators/powershell/README.md
new file mode 100644
index 0000000..822659e
--- /dev/null
+++ b/validators/powershell/README.md
@@ -0,0 +1,46 @@
+# PowerShell Validator
+
+A PowerShell script for validating `.gitinfo` files.
+
+## Requirements
+
+- PowerShell 5.1+ (Windows) or PowerShell Core 7+ (cross-platform)
+
+## Usage
+
+```powershell
+# Validate .gitinfo in current directory
+.\Validate-GitInfo.ps1
+
+# Validate a specific file
+.\Validate-GitInfo.ps1 -Path "path/to/.gitinfo"
+```
+
+## Features
+
+- Parses JSONC (strips `//` and `/* */` comments)
+- Validates against the gitinfo JSON Schema
+- Checks types, formats (URI, email), and patterns
+- Enforces `additionalProperties: false`
+- Returns exit code 0 on success, 1 on failure
+- Color-coded output (green for success, red for errors)
+
+## Example Output
+
+```
+✓ .gitinfo is valid
+```
+
+```
+Validation failed for .gitinfo:
+ - .root: invalid URI "not-a-url"
+ - root: unknown property "invalid_field"
+```
+
+## Notes
+
+On Windows, you may need to adjust the execution policy:
+
+```powershell
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+```
diff --git a/validators/powershell/Validate-GitInfo.ps1 b/validators/powershell/Validate-GitInfo.ps1
new file mode 100644
index 0000000..e84606c
--- /dev/null
+++ b/validators/powershell/Validate-GitInfo.ps1
@@ -0,0 +1,248 @@
+<#
+.SYNOPSIS
+ Validates .gitinfo files against the gitinfo JSON Schema.
+
+.DESCRIPTION
+ A PowerShell validator for .gitinfo files. Parses JSONC (strips comments)
+ and validates against the schema.
+
+.PARAMETER Path
+ Path to the .gitinfo file. Defaults to ".gitinfo" in the current directory.
+
+.EXAMPLE
+ .\Validate-GitInfo.ps1
+ Validates .gitinfo in the current directory.
+
+.EXAMPLE
+ .\Validate-GitInfo.ps1 -Path "path/to/.gitinfo"
+ Validates a specific .gitinfo file.
+#>
+
+param(
+ [Parameter(Position = 0)]
+ [string]$Path = ".gitinfo"
+)
+
+$ErrorActionPreference = "Stop"
+
+# Schema path (two levels up from validators/powershell/)
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$SchemaPath = Join-Path $ScriptDir "..\..\gitinfo.schema.json"
+
+function Remove-JsonComments {
+ param([string]$Jsonc)
+
+ $result = New-Object System.Text.StringBuilder
+ $i = 0
+ $inString = $false
+ $stringChar = $null
+
+ while ($i -lt $Jsonc.Length) {
+ $char = $Jsonc[$i]
+ $next = if ($i + 1 -lt $Jsonc.Length) { $Jsonc[$i + 1] } else { $null }
+
+ # Track string state
+ if (-not $inString -and ($char -eq '"' -or $char -eq "'")) {
+ $inString = $true
+ $stringChar = $char
+ [void]$result.Append($char)
+ $i++
+ continue
+ }
+
+ if ($inString) {
+ if ($char -eq '\' -and $i + 1 -lt $Jsonc.Length) {
+ [void]$result.Append($char)
+ [void]$result.Append($Jsonc[$i + 1])
+ $i += 2
+ continue
+ }
+ if ($char -eq $stringChar) {
+ $inString = $false
+ $stringChar = $null
+ }
+ [void]$result.Append($char)
+ $i++
+ continue
+ }
+
+ # Single-line comment
+ if ($char -eq '/' -and $next -eq '/') {
+ while ($i -lt $Jsonc.Length -and $Jsonc[$i] -ne "`n") {
+ $i++
+ }
+ continue
+ }
+
+ # Multi-line comment
+ if ($char -eq '/' -and $next -eq '*') {
+ $i += 2
+ while ($i -lt $Jsonc.Length - 1 -and -not ($Jsonc[$i] -eq '*' -and $Jsonc[$i + 1] -eq '/')) {
+ $i++
+ }
+ $i += 2
+ continue
+ }
+
+ [void]$result.Append($char)
+ $i++
+ }
+
+ return $result.ToString()
+}
+
+function Test-Uri {
+ param([string]$Value)
+ try {
+ $uri = [System.Uri]::new($Value)
+ return $uri.Scheme -eq "http" -or $uri.Scheme -eq "https"
+ }
+ catch {
+ return $false
+ }
+}
+
+function Test-Email {
+ param([string]$Value)
+ return $Value -match '^[^\s@]+@[^\s@]+\.[^\s@]+$'
+}
+
+function Test-Schema {
+ param(
+ $Data,
+ $Schema,
+ [string]$JsonPath = ""
+ )
+
+ $errors = @()
+
+ if ($Schema.type -eq "object") {
+ if ($Data -isnot [System.Collections.IDictionary] -and $Data.GetType().Name -ne "PSCustomObject") {
+ $errors += "$(if ($JsonPath) { $JsonPath } else { 'root' }): expected object"
+ return $errors
+ }
+
+ # Convert PSCustomObject to hashtable for easier handling
+ $dataHash = @{}
+ $Data.PSObject.Properties | ForEach-Object { $dataHash[$_.Name] = $_.Value }
+
+ # Check additionalProperties
+ if ($Schema.additionalProperties -eq $false -and $Schema.properties) {
+ $allowed = $Schema.properties.PSObject.Properties.Name
+ foreach ($key in $dataHash.Keys) {
+ if ($key -notin $allowed) {
+ $errors += "$(if ($JsonPath) { $JsonPath } else { 'root' }): unknown property `"$key`""
+ }
+ }
+ }
+
+ # Validate each property
+ if ($Schema.properties) {
+ foreach ($prop in $Schema.properties.PSObject.Properties) {
+ $key = $prop.Name
+ $propSchema = $prop.Value
+ if ($dataHash.ContainsKey($key)) {
+ $errors += Test-Schema -Data $dataHash[$key] -Schema $propSchema -JsonPath "$JsonPath.$key"
+ }
+ }
+ }
+ }
+ elseif ($Schema.type -eq "array") {
+ if ($Data -isnot [System.Array]) {
+ $errors += "${JsonPath}: expected array"
+ return $errors
+ }
+
+ if ($Schema.items) {
+ for ($i = 0; $i -lt $Data.Count; $i++) {
+ $itemSchema = if ($Schema.items -is [System.Array]) { $Schema.items[$i] } else { $Schema.items }
+ if ($itemSchema) {
+ $errors += Test-Schema -Data $Data[$i] -Schema $itemSchema -JsonPath "$JsonPath[$i]"
+ }
+ }
+
+ if ($Schema.minItems -and $Data.Count -lt $Schema.minItems) {
+ $errors += "${JsonPath}: expected at least $($Schema.minItems) items"
+ }
+ if ($Schema.maxItems -and $Data.Count -gt $Schema.maxItems) {
+ $errors += "${JsonPath}: expected at most $($Schema.maxItems) items"
+ }
+ }
+ }
+ elseif ($Schema.type -eq "string") {
+ if ($Data -isnot [string]) {
+ $errors += "${JsonPath}: expected string"
+ return $errors
+ }
+
+ if ($Schema.minLength -and $Data.Length -lt $Schema.minLength) {
+ $errors += "${JsonPath}: string too short (min $($Schema.minLength))"
+ }
+
+ if ($Schema.format -eq "uri") {
+ if (-not (Test-Uri $Data)) {
+ $errors += "${JsonPath}: invalid URI `"$Data`""
+ }
+ }
+
+ if ($Schema.format -eq "email") {
+ if (-not (Test-Email $Data)) {
+ $errors += "${JsonPath}: invalid email `"$Data`""
+ }
+ }
+
+ if ($Schema.pattern) {
+ if ($Data -notmatch $Schema.pattern) {
+ $errors += "${JsonPath}: does not match pattern $($Schema.pattern)"
+ }
+ }
+ }
+
+ return $errors
+}
+
+# Main
+if (-not (Test-Path $Path)) {
+ Write-Error "Error: File not found: $Path"
+ exit 1
+}
+
+if (-not (Test-Path $SchemaPath)) {
+ Write-Error "Error: Schema not found: $SchemaPath"
+ exit 1
+}
+
+# Read and parse schema
+try {
+ $schemaContent = Get-Content -Path $SchemaPath -Raw
+ $schema = $schemaContent | ConvertFrom-Json
+}
+catch {
+ Write-Error "Error parsing schema: $_"
+ exit 1
+}
+
+# Read and parse .gitinfo file
+try {
+ $content = Get-Content -Path $Path -Raw
+ $stripped = Remove-JsonComments -Jsonc $content
+ $data = $stripped | ConvertFrom-Json
+}
+catch {
+ Write-Error "Error parsing JSONC: $_"
+ exit 1
+}
+
+# Validate against schema
+$errors = Test-Schema -Data $data -Schema $schema
+
+if ($errors.Count -gt 0) {
+ Write-Host "Validation failed for ${Path}:" -ForegroundColor Red
+ foreach ($error in $errors) {
+ Write-Host " - $error" -ForegroundColor Red
+ }
+ exit 1
+}
+
+Write-Host "✓ $Path is valid" -ForegroundColor Green
+exit 0