From d8f4bc22dc9ba4d901317de83b11cfe75d66d8ac Mon Sep 17 00:00:00 2001 From: sunasrd-byte Date: Fri, 31 Jul 2026 15:39:32 +0200 Subject: [PATCH] Fix PowerShell 5.1 parse failure in install scripts The PowerShell install scripts are UTF-8 without a BOM. Windows PowerShell 5.1 therefore decodes them as ANSI (CP1252), where the UTF-8 bytes for U+2713 (E2 9C 93) become U+00E2 U+0153 U+201C. That trailing U+201C is a left double quotation mark, which 5.1 honours as a string delimiter, so the enclosing string never terminates. The parse then desyncs and reports errors far below the real cause. In examples.ps1 the break is at line 76, but the first error surfaces at line 102 as "Array index expression is missing or not valid", which made the ${WHITE}[Enter]${NC} construct look responsible. That construct is valid on 5.1; parsing it in isolation produces no errors. Build the affected glyphs from their code points so the sources stay pure ASCII, matching the existing $ESC = [char]27 idiom: - examples.ps1: $CHECK, $CROSS - install.ps1: $CHECK, $ROCKET - walkthrough.ps1: $TRI plus box-drawing parts and $BOX_TOP/$BOX_BOTTOM A BOM would fix file execution but break `irm ... | iex`, since 5.1 decodes a charset-less text/* body as ISO-8859-1 and the BOM arrives as literal text. Also add a windows-latest CI job that parses every install script with both 5.1 and 7 via [Parser]::ParseFile, and rejects non-ASCII bytes and BOMs so this cannot regress. Fixes #264 --- .github/workflows/lint-powershell.yml | 124 +++++++++++++++++++++ install/powershell/examples.ps1 | 16 ++- install/powershell/install.ps1 | 34 +++--- install/powershell/walkthrough.ps1 | 155 ++++++++++++++------------ 4 files changed, 239 insertions(+), 90 deletions(-) create mode 100644 .github/workflows/lint-powershell.yml diff --git a/.github/workflows/lint-powershell.yml b/.github/workflows/lint-powershell.yml new file mode 100644 index 00000000..c12e1ada --- /dev/null +++ b/.github/workflows/lint-powershell.yml @@ -0,0 +1,124 @@ +name: PowerShell Install Scripts + +on: + push: + paths: + - 'install/powershell/*.ps1' + - '.github/workflows/lint-powershell.yml' + pull_request: + paths: + - 'install/powershell/*.ps1' + - '.github/workflows/lint-powershell.yml' + workflow_dispatch: + +jobs: + parse-windows-powershell-51: + name: Parse under Windows PowerShell 5.1 + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + # The install scripts are fetched over HTTP and executed on stock Windows, + # where powershell.exe (5.1) is the default shell. 5.1 reads BOM-less files + # as ANSI (CP1252), so a non-ASCII glyph such as U+2713 decodes into a smart + # quote that silently terminates the enclosing string. The resulting parse + # errors point at unrelated lines far below the real cause, so parse every + # script with 5.1 explicitly rather than relying on PowerShell 7. + - name: Parse each script with 5.1 + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + Write-Host "PowerShell $($PSVersionTable.PSVersion) / ANSI codepage $([System.Text.Encoding]::Default.WebName)" + + $failed = $false + Get-ChildItem 'install/powershell' -Filter *.ps1 | Sort-Object Name | ForEach-Object { + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, [ref]$null, [ref]$errors) | Out-Null + + if ($errors -and $errors.Count -gt 0) { + $failed = $true + Write-Host "FAIL $($_.Name): $($errors.Count) parse error(s)" + $errors | Sort-Object { $_.Extent.StartLineNumber } | ForEach-Object { + Write-Host " L$($_.Extent.StartLineNumber): $($_.Message)" + } + } else { + Write-Host "OK $($_.Name)" + } + } + + if ($failed) { exit 1 } + + # A BOM would fix file-based parsing but break `irm ... | iex`: PowerShell 5.1 + # decodes an HTTP text/* body with no charset as ISO-8859-1, turning the BOM + # into literal "i>>?" text and failing with + # "The term 'i>>?$ErrorActionPreference' is not recognized". + # Keeping the sources pure ASCII is what makes both paths safe, so enforce it. + - name: Require pure ASCII and no BOM + shell: powershell + run: | + $ErrorActionPreference = 'Stop' + $failed = $false + + Get-ChildItem 'install/powershell' -Filter *.ps1 | Sort-Object Name | ForEach-Object { + $fileBad = $false + $bytes = [System.IO.File]::ReadAllBytes($_.FullName) + + if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { + $failed = $true + $fileBad = $true + Write-Host "FAIL $($_.Name): has a UTF-8 BOM (breaks 'irm | iex'); remove it" + } + + # Report line/column of any byte outside ASCII so the fix is obvious. + $text = [System.Text.Encoding]::UTF8.GetString($bytes) + $lineNo = 0 + foreach ($line in ($text -split "`r?`n")) { + $lineNo++ + for ($i = 0; $i -lt $line.Length; $i++) { + if ([int]$line[$i] -gt 127) { + $failed = $true + $fileBad = $true + $cp = "U+{0:X4}" -f [int]$line[$i] + Write-Host "FAIL $($_.Name) L${lineNo} C$($i + 1): non-ASCII $cp" + Write-Host " build it from its code point instead, e.g. `$CHECK = [char]0x2713" + break + } + } + } + + if (-not $fileBad) { Write-Host "OK $($_.Name)" } + } + + if ($failed) { exit 1 } + + parse-powershell-7: + name: Parse under PowerShell 7 + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Parse each script with 7 + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + Write-Host "PowerShell $($PSVersionTable.PSVersion)" + + $failed = $false + Get-ChildItem 'install/powershell' -Filter *.ps1 | Sort-Object Name | ForEach-Object { + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, [ref]$null, [ref]$errors) | Out-Null + + if ($errors -and $errors.Count -gt 0) { + $failed = $true + Write-Host "FAIL $($_.Name): $($errors.Count) parse error(s)" + $errors | Sort-Object { $_.Extent.StartLineNumber } | ForEach-Object { + Write-Host " L$($_.Extent.StartLineNumber): $($_.Message)" + } + } else { + Write-Host "OK $($_.Name)" + } + } + + if ($failed) { exit 1 } diff --git a/install/powershell/examples.ps1 b/install/powershell/examples.ps1 index d4cb1b3d..7e64c85c 100644 --- a/install/powershell/examples.ps1 +++ b/install/powershell/examples.ps1 @@ -9,6 +9,12 @@ if (-not $env:GRAY) { $GRAY = "$([char]27)[38;2;128;128;128m" } els if (-not $env:BOLD) { $BOLD = "$([char]27)[1m" } else { $BOLD = $env:BOLD } if (-not $env:NC) { $NC = "$([char]27)[0m" } else { $NC = $env:NC } +# Symbols built from code points so this file stays pure ASCII. Windows PowerShell 5.1 +# reads BOM-less files as ANSI (CP1252), where a literal U+2713 decodes to a smart quote +# that silently terminates the enclosing string and breaks parsing. +$CHECK = [char]0x2713 +$CROSS = [char]0x2717 + # Examples configuration $EXAMPLES_FOLDER_NAME = "plainlang-examples" $EXAMPLES_DOWNLOAD_URL = "https://codeplain.ai/examples/windows" @@ -40,7 +46,7 @@ if (-not (Test-Path $EXTRACT_PATH -PathType Container)) { try { New-Item -ItemType Directory -Path $EXTRACT_PATH -Force | Out-Null } catch { - Write-Host " ${RED}✗ Failed to create directory: ${EXTRACT_PATH}${NC}" + Write-Host " ${RED}${CROSS} Failed to create directory: ${EXTRACT_PATH}${NC}" Write-Host " ${GRAY}Skipping example download.${NC}" $SKIP_DOWNLOAD = $true } @@ -73,7 +79,7 @@ if (-not $SKIP_DOWNLOAD) { Clear-Host Write-Host "" - Write-Host " ${GREEN}✓ Examples downloaded successfully!${NC}" + Write-Host " ${GREEN}${CHECK} Examples downloaded successfully!${NC}" Write-Host "" Write-Host " ${GRAY}Examples are in: ${EXTRACTED_DIR}${NC}" Write-Host "" @@ -86,15 +92,15 @@ if (-not $SKIP_DOWNLOAD) { Write-Host " ${GRAY}See hello-world/python/README.md for details.${NC}" Write-Host "" } catch { - Write-Host " ${RED}✗ Failed to extract examples.${NC}" + Write-Host " ${RED}${CROSS} Failed to extract examples.${NC}" } Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue } else { - Write-Host " ${RED}✗ Failed to download examples.${NC}" + Write-Host " ${RED}${CROSS} Failed to download examples.${NC}" } } catch { - Write-Host " ${RED}✗ Failed to download examples.${NC}" + Write-Host " ${RED}${CROSS} Failed to download examples.${NC}" Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue } diff --git a/install/powershell/install.ps1 b/install/powershell/install.ps1 index 0ac18e80..81daf7db 100644 --- a/install/powershell/install.ps1 +++ b/install/powershell/install.ps1 @@ -32,6 +32,12 @@ $GRAY_LIGHT = "$ESC[38;2;211;211;211m" # #D3D3D3 $BOLD = "$ESC[1m" $NC = "$ESC[0m" # No Color / Reset +# Symbols built from code points so this file stays pure ASCII. Windows PowerShell 5.1 +# reads BOM-less files as ANSI (CP1252), where a literal U+2713 decodes to a smart quote +# that silently terminates the enclosing string and breaks parsing. +$CHECK = [char]0x2713 +$ROCKET = [System.Char]::ConvertFromUtf32(0x1F680) + # Export colors for child scripts (as environment variables) $env:YELLOW = $YELLOW $env:GREEN = $GREEN @@ -92,11 +98,11 @@ function Test-ApiKey { if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { Write-Host "${GRAY}uv is not installed.${NC}" Install-Uv - Write-Host "${GREEN}✓${NC} uv installed successfully" + Write-Host "${GREEN}${CHECK}${NC} uv installed successfully" Write-Host "" } -Write-Host "${GREEN}✓${NC} uv detected" +Write-Host "${GREEN}${CHECK}${NC} uv detected" Write-Host "" try { @@ -125,16 +131,16 @@ if ($codeplainLine) { $newLine = @(uv tool list 2>$null) | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1 $newVersion = ($newLine -replace 'codeplain v', '').Trim() if ($currentVersion -eq $newVersion) { - Write-Host "${GREEN}✓${NC} codeplain is already up to date (${newVersion})" + Write-Host "${GREEN}${CHECK}${NC} codeplain is already up to date (${newVersion})" } else { - Write-Host "${GREEN}✓${NC} codeplain upgraded from ${currentVersion} to ${newVersion}!" + Write-Host "${GREEN}${CHECK}${NC} codeplain upgraded from ${currentVersion} to ${newVersion}!" } } else { Write-Host "Installing codeplain...${NC}" Write-Host "" uv tool install codeplain if (-not $nonInteractive) { Clear-Host } - Write-Host "${GREEN}✓ codeplain installed successfully!${NC}" + Write-Host "${GREEN}${CHECK} codeplain installed successfully!${NC}" } # Ensure uv tool bin directory is on user PATH permanently (so codeplain is available) @@ -146,12 +152,12 @@ if ($userPath) { $newPath = ($userPath.TrimEnd(';') + ';' + $uvBinDir) [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') $env:Path = $uvBinDir + ';' + $env:Path - Write-Host "${GREEN}✓${NC} added $uvBinDir to your user PATH" + Write-Host "${GREEN}${CHECK}${NC} added $uvBinDir to your user PATH" } } else { [Environment]::SetEnvironmentVariable('Path', $uvBinDir, 'User') $env:Path = $uvBinDir + ';' + $env:Path - Write-Host "${GREEN}✓${NC} added $uvBinDir to your user PATH" + Write-Host "${GREEN}${CHECK}${NC} added $uvBinDir to your user PATH" } Write-Host "" @@ -181,7 +187,7 @@ if ($env:CODEPLAIN_API_KEY) { Write-Host "${GRAY}Verifying your existing API key...${NC}" $existingResult = Test-ApiKey $env:CODEPLAIN_API_KEY if ($existingResult -eq "valid") { - Write-Host "${GREEN}✓${NC} Using existing API key." + Write-Host "${GREEN}${CHECK}${NC} Using existing API key." $skipApiKeySetup = $true $apiKeyVerified = $true } elseif ($existingResult -eq "invalid") { @@ -225,7 +231,7 @@ if (-not $skipApiKeySetup) { Write-Host "${GRAY}Verifying your API key...${NC}" $result = Test-ApiKey $apiKey if ($result -eq "valid") { - Write-Host "${GREEN}✓${NC} API key verified." + Write-Host "${GREEN}${CHECK}${NC} API key verified." Write-Host "" $apiKeyVerified = $true break @@ -252,7 +258,7 @@ if ($skipApiKeySetup) { # Persist as user environment variable (survives reboots) [Environment]::SetEnvironmentVariable('CODEPLAIN_API_KEY', $apiKey, 'User') - Write-Host "${GREEN}✓ API key saved to user environment variables${NC}" + Write-Host "${GREEN}${CHECK} API key saved to user environment variables${NC}" } # ASCII Art Welcome @@ -268,7 +274,7 @@ Write-Host @' Write-Host "" # Only claim success when a verified API key is actually configured. if ($apiKeyVerified) { - Write-Host "${GREEN}✓ Sign in successful.${NC}" + Write-Host "${GREEN}${CHECK} Sign in successful.${NC}" Write-Host "" } Write-Host " ${WHITE}Welcome to *codeplain!${NC}" @@ -392,7 +398,7 @@ $plynInstalled = $false if ($editorCmds.Count -gt 0 -and $installPlyn -notmatch '^[Nn]$') { foreach ($editor in $editorCmds) { & $editor.Cmd --install-extension Codeplain.plyn - Write-Host "${GREEN}✓${NC} plyn installed for $($editor.Name)" + Write-Host "${GREEN}${CHECK}${NC} plyn installed for $($editor.Name)" } $plynInstalled = $true Write-Host "" @@ -435,7 +441,7 @@ if ($env:CODEPLAIN_API_KEY) { $verifyOk = $false } if ($verifyOk) { - Write-Host "${GREEN}✓${NC} Installation verified." + Write-Host "${GREEN}${CHECK}${NC} Installation verified." } else { Write-Host "${RED}Something went wrong during installation.${NC}" Write-Host "${GRAY}Output of 'codeplain --status':${NC}" @@ -473,7 +479,7 @@ Write-Host " ${WHITE}${BOLD}codeplain your-project.plain${NC}" Write-Host "" Write-Host " ${GRAY}Discord: https://discord.gg/cgbynb9hFq Docs: https://plainlang.org/${NC}" Write-Host "" -Write-Host " ${GRAY}Happy development!${NC} 🚀" +Write-Host " ${GRAY}Happy development!${NC} ${ROCKET}" Write-Host "" # Refresh environment for this session diff --git a/install/powershell/walkthrough.ps1 b/install/powershell/walkthrough.ps1 index 2fcbe93e..a8216958 100644 --- a/install/powershell/walkthrough.ps1 +++ b/install/powershell/walkthrough.ps1 @@ -8,6 +8,19 @@ if (-not $env:GRAY) { $GRAY = "$([char]27)[38;2;128;128;128m" } els if (-not $env:BOLD) { $BOLD = "$([char]27)[1m" } else { $BOLD = $env:BOLD } if (-not $env:NC) { $NC = "$([char]27)[0m" } else { $NC = $env:NC } +# Box-drawing and symbol characters built from code points so this file stays pure ASCII. +# Windows PowerShell 5.1 reads BOM-less files as ANSI (CP1252), where these glyphs decode +# to smart quotes that silently terminate the enclosing string and break parsing. +$TRI = [char]0x25B2 +$TL = [char]0x250C # top-left corner +$TR = [char]0x2510 # top-right corner +$BL = [char]0x2514 # bottom-left corner +$BR = [char]0x2518 # bottom-right corner +$V = [char]0x2502 # vertical line +$HR = ([string][char]0x2500) * 56 # horizontal rule +$BOX_TOP = "${TL}${HR}${TR}" +$BOX_BOTTOM = "${BL}${HR}${BR}" + # Onboarding Step 1: Introduction to Plain Clear-Host Write-Host "" @@ -19,14 +32,14 @@ Write-Host " Write specs in natural language extended with additional syntax ba Write-Host "" Write-Host " A ***plain file has these key sections:" Write-Host "" -Write-Host "${GRAY} ┌────────────────────────────────────────────────────────┐${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}***definitions***${NC} - key concepts in your app ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}***implementation reqs***${NC} - implementation details ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}***test reqs***${NC} - testing requirements ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}***functional specs***${NC} - what the app should do ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} └────────────────────────────────────────────────────────┘${NC}" +Write-Host "${GRAY} ${BOX_TOP}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}***definitions***${NC} - key concepts in your app ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}***implementation reqs***${NC} - implementation details ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}***test reqs***${NC} - testing requirements ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}***functional specs***${NC} - what the app should do ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${BOX_BOTTOM}${NC}" Write-Host "" Write-Host " Let's see each section in a `"hello, world`" example." Write-Host "" @@ -42,27 +55,27 @@ Write-Host "" Write-Host " Define ${WHITE}${BOLD}reusable concepts${NC} using the ${WHITE}${BOLD}:Concept:${NC} notation." Write-Host " These become building blocks you can reference anywhere." Write-Host "" -Write-Host "${GRAY} ┌────────────────────────────────────────────────────────┐${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}${BOLD}***definitions***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${WHITE}${BOLD}- :App: is a console application.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***implementation reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :Implementation: should be in Python.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***test reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :ConformanceTests: should use pytest.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***functional specs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :App: should display `"hello, world`".${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} └────────────────────────────────────────────────────────┘${NC}" -Write-Host "" -Write-Host " ${GREEN}▲${NC} The ${WHITE}:App:${NC} concept is defined once and used throughout the specs." +Write-Host "${GRAY} ${BOX_TOP}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}${BOLD}***definitions***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${WHITE}${BOLD}- :App: is a console application.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***implementation reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :Implementation: should be in Python.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***test reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :ConformanceTests: should use pytest.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***functional specs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :App: should display `"hello, world`".${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${BOX_BOTTOM}${NC}" +Write-Host "" +Write-Host " ${GREEN}${TRI}${NC} The ${WHITE}:App:${NC} concept is defined once and used throughout the specs." Write-Host " Concepts help keep your specs consistent and clear." Write-Host "" Read-Host " Press ${WHITE}[Enter]${NC} to continue..." @@ -77,27 +90,27 @@ Write-Host "" Write-Host " Specify ${WHITE}${BOLD}implementation details${NC} and ${WHITE}${BOLD}testing requirements${NC}." Write-Host " This guides how the code should be generated and verified." Write-Host "" -Write-Host "${GRAY} ┌────────────────────────────────────────────────────────┐${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***definitions***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :App: is a console application.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}${BOLD}***implementation reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${WHITE}${BOLD}- :Implementation: should be in Python.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}${BOLD}***test reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${WHITE}${BOLD}- :ConformanceTests: should use pytest.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***functional specs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :App: should display `"hello, world`".${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} └────────────────────────────────────────────────────────┘${NC}" -Write-Host "" -Write-Host " ${GREEN}▲${NC} ${WHITE}${BOLD}Implementation reqs${NC} define the language and frameworks." +Write-Host "${GRAY} ${BOX_TOP}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***definitions***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :App: is a console application.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}${BOLD}***implementation reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${WHITE}${BOLD}- :Implementation: should be in Python.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}${BOLD}***test reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${WHITE}${BOLD}- :ConformanceTests: should use pytest.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***functional specs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :App: should display `"hello, world`".${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${BOX_BOTTOM}${NC}" +Write-Host "" +Write-Host " ${GREEN}${TRI}${NC} ${WHITE}${BOLD}Implementation reqs${NC} define the language and frameworks." Write-Host " ${WHITE}${BOLD}Test reqs${NC} ensure the generated code is verified." Write-Host "" Read-Host " Press ${WHITE}[Enter]${NC} to continue..." @@ -112,27 +125,27 @@ Write-Host "" Write-Host " This is where you describe ${WHITE}what your app should do${NC}," Write-Host " written in natural language. No code, just requirements." Write-Host "" -Write-Host "${GRAY} ┌────────────────────────────────────────────────────────┐${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***definitions***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :App: is a console application.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***implementation reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :Implementation: should be in Python.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}***test reqs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}- :ConformanceTests: should use pytest.${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GREEN}${BOLD}***functional specs***${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${WHITE}${BOLD}- :App: should display `"hello, world`".${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} │${NC} ${GRAY}│${NC}" -Write-Host "${GRAY} └────────────────────────────────────────────────────────┘${NC}" -Write-Host "" -Write-Host " ${GREEN}▲${NC} The ${WHITE}${BOLD}functional spec${NC} describes ${WHITE}${BOLD}what the app does${NC}." +Write-Host "${GRAY} ${BOX_TOP}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***definitions***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :App: is a console application.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***implementation reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :Implementation: should be in Python.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}***test reqs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}- :ConformanceTests: should use pytest.${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GREEN}${BOLD}***functional specs***${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${WHITE}${BOLD}- :App: should display `"hello, world`".${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${V}${NC} ${GRAY}${V}${NC}" +Write-Host "${GRAY} ${BOX_BOTTOM}${NC}" +Write-Host "" +Write-Host " ${GREEN}${TRI}${NC} The ${WHITE}${BOLD}functional spec${NC} describes ${WHITE}${BOLD}what the app does${NC}." Write-Host " Here, it simply displays `"hello, world`"." Write-Host "" Read-Host " Press ${WHITE}[Enter]${NC} to continue..."