Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 91 additions & 13 deletions install/bash/examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,70 @@ NC="${NC:-\033[0m}"
EXAMPLES_FOLDER_NAME="plainlang-examples"
EXAMPLES_DOWNLOAD_URL="https://codeplain.ai/examples/unix"

# Prompt the user, reading from the terminal when there is one. Falling back to
# stdin keeps the script usable in non-interactive environments instead of
# dying on an unreadable /dev/tty.
prompt_user() {
local prompt="$1"
local varname="$2"

# Test that /dev/tty can actually be opened: it exists but fails to open
# when the process has no controlling terminal.
if { : < /dev/tty; } 2>/dev/null; then
read -r -p "$prompt" "$varname" < /dev/tty || true
else
read -r -p "$prompt" "$varname" || true
fi
}

# Make the user acknowledge that the examples were not installed. The
# installation continues either way, but the failure must not scroll past
# unseen - the installer clears the screen in its next step.
confirm_continue_after_failure() {
echo ""
echo -e " ${YELLOW}The examples were not installed.${NC} ${GRAY}The installation will continue.${NC}"
echo ""
prompt_user "$(printf '%b' " Continue? Press ${WHITE}[Enter]${NC} to acknowledge: ")" _ACKNOWLEDGED
echo ""
}

# Tell the user how to get unzip on their system, then let the installer move on.
report_missing_unzip_tool() {
echo -e " ${RED}✗ Cannot extract the examples: no 'unzip' command found.${NC}"
echo ""
echo -e " ${GRAY}Install unzip and re-run the installer to get the examples:${NC}"
echo ""
case "$OSTYPE" in
darwin*)
echo -e " ${WHITE}${BOLD}brew install unzip${NC}"
;;
*)
echo -e " ${WHITE}${BOLD}apt-get install unzip${NC} ${GRAY}(Debian/Ubuntu)${NC}"
echo -e " ${WHITE}${BOLD}dnf install unzip${NC} ${GRAY}(Fedora/RHEL)${NC}"
echo -e " ${WHITE}${BOLD}apk add unzip${NC} ${GRAY}(Alpine)${NC}"
;;
esac
echo ""
echo -e " ${GRAY}The examples are also available at:${NC}"
echo -e " ${WHITE}https://github.com/Codeplain-ai/plainlang-examples${NC}"
echo ""
}

# Checked before anything is downloaded, so a missing unzip is reported up
# front instead of leaving the user with an installer that just stops.
if ! command -v unzip &> /dev/null; then
report_missing_unzip_tool
confirm_continue_after_failure
exit 0
fi

# Show current directory and ask for extraction path
CURRENT_DIR=$(pwd)
echo -e " Current folder: ${WHITE}${CURRENT_DIR}${NC}"
echo ""
echo -e " Extract examples here, or enter a different path:"
echo ""
read -r -p " [Enter for current, or type path]: " EXTRACT_PATH < /dev/tty
prompt_user " [Enter for current, or type path]: " EXTRACT_PATH
echo ""

# Use current directory if empty
Expand All @@ -37,36 +94,49 @@ SKIP_DOWNLOAD=false
# Check if directory exists, create if not
if [ ! -d "$EXTRACT_PATH" ]; then
echo -e " ${GRAY}Creating directory...${NC}"
mkdir -p "$EXTRACT_PATH" 2>/dev/null
if [ $? -ne 0 ]; then
if ! mkdir -p "$EXTRACT_PATH" 2>/dev/null; then
echo -e " ${RED}✗ Failed to create directory: ${EXTRACT_PATH}${NC}"
echo -e " ${GRAY}Skipping example download.${NC}"
confirm_continue_after_failure
SKIP_DOWNLOAD=true
fi
fi

EXAMPLES_INSTALLED=false

if [ "$SKIP_DOWNLOAD" = false ]; then
echo -e " ${GRAY}Downloading examples...${NC}"

# Download the zip file
TEMP_ZIP=$(mktemp)
curl -L -s -o "$TEMP_ZIP" "$EXAMPLES_DOWNLOAD_URL"
DOWNLOAD_OK=false
DOWNLOAD_ERROR="Could not download ${EXAMPLES_DOWNLOAD_URL}"
if curl -L -s -o "$TEMP_ZIP" "$EXAMPLES_DOWNLOAD_URL" && [ -s "$TEMP_ZIP" ]; then
DOWNLOAD_OK=true
fi

# A server error page is a successful download of the wrong thing, so check
# for the zip magic bytes rather than handing unzip an HTML page.
if [ "$DOWNLOAD_OK" = true ] && [ "$(head -c 2 "$TEMP_ZIP")" != "PK" ]; then
DOWNLOAD_OK=false
DOWNLOAD_ERROR="The downloaded file is not a zip archive."
fi

if [ $? -eq 0 ] && [ -s "$TEMP_ZIP" ]; then
if [ "$DOWNLOAD_OK" = true ]; then
echo -e " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"

# Extract the zip file (contents are at the zip root, so extract into the target folder)
EXTRACTED_DIR="${EXTRACT_PATH}/${EXAMPLES_FOLDER_NAME}"
rm -rf "$EXTRACTED_DIR" 2>/dev/null # Remove existing if present
unzip -q -o "$TEMP_ZIP" -d "$EXTRACTED_DIR" 2>/dev/null
rm -rf "$EXTRACTED_DIR" 2>/dev/null || true # Remove existing if present

if [ $? -eq 0 ]; then
# Keep the extractor's own error output: it is the only clue about why
# extraction failed (corrupt download, no disk space, no permissions).
if EXTRACT_OUTPUT=$(unzip -q -o "$TEMP_ZIP" -d "$EXTRACTED_DIR" 2>&1); then
# Remove the .gitignore file from the root of the extracted directory
if [ -f "${EXTRACTED_DIR}/.gitignore" ]; then
rm -f "${EXTRACTED_DIR}/.gitignore"
fi

clear
clear || true
echo ""
echo -e " ${GREEN}✓ Examples downloaded successfully!${NC}"
echo ""
Expand All @@ -80,18 +150,26 @@ if [ "$SKIP_DOWNLOAD" = false ]; then
echo ""
echo -e " ${GRAY}See hello-world/python/README.md for details.${NC}"
echo ""
EXAMPLES_INSTALLED=true
else
echo -e " ${RED}✗ Failed to extract examples.${NC}"
if [ -n "$EXTRACT_OUTPUT" ]; then
echo -e " ${GRAY}$(echo "$EXTRACT_OUTPUT" | tail -n 3)${NC}"
fi
fi

# Clean up temp file
rm -f "$TEMP_ZIP"
else
echo -e " ${RED}✗ Failed to download examples.${NC}"
echo -e " ${GRAY}${DOWNLOAD_ERROR}${NC}"
rm -f "$TEMP_ZIP"
fi

echo ""
PROMPT=$(printf '%b' " Press ${WHITE}[Enter]${NC} to continue...")
read -r -p "$PROMPT" < /dev/tty
if [ "$EXAMPLES_INSTALLED" = true ]; then
echo ""
prompt_user "$(printf '%b' " Press ${WHITE}[Enter]${NC} to continue...")" _CONTINUE
else
confirm_continue_after_failure
fi
fi
6 changes: 4 additions & 2 deletions install/bash/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,11 @@ else
echo ""
fi

# Run examples download if user agrees
# Run examples download if user agrees. The examples are a nice-to-have, so a
# failure there must never abort the installation (set -e would otherwise take
# the whole installer down without printing anything).
if [[ ! "${DOWNLOAD_EXAMPLES:-}" =~ ^[Nn]$ ]]; then
run_script "examples.sh"
run_script "examples.sh" || echo -e "${GRAY}Example download did not complete. Continuing with the installation.${NC}"
fi

# Final verification: make sure the installed codeplain can actually run and
Expand Down
68 changes: 65 additions & 3 deletions install/powershell/examples.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@ $CROSS = [char]0x2717
$EXAMPLES_FOLDER_NAME = "plainlang-examples"
$EXAMPLES_DOWNLOAD_URL = "https://codeplain.ai/examples/windows"

# Make the user acknowledge that the examples were not installed. The
# installation continues either way, but the failure must not scroll past
# unseen - the installer clears the screen in its next step.
function Confirm-ContinueAfterFailure {
Write-Host ""
Write-Host " ${YELLOW}The examples were not installed.${NC} ${GRAY}The installation will continue.${NC}"
Write-Host ""
Read-Host " Continue? Press ${WHITE}[Enter]${NC} to acknowledge"
Write-Host ""
}

# Expand-Archive lives in the Microsoft.PowerShell.Archive module, shipped with
# PowerShell 5.0 and later. Checked before anything is downloaded, so a machine
# without it is told what to do instead of just seeing an extraction failure.
function Show-MissingArchiveCmdletMessage {
Write-Host " ${RED}${CROSS} Cannot extract the examples: Expand-Archive is not available.${NC}"
Write-Host ""
Write-Host " ${GRAY}Expand-Archive requires PowerShell 5.0 or later. Install a newer${NC}"
Write-Host " ${GRAY}PowerShell and re-run the installer to get the examples:${NC}"
Write-Host ""
Write-Host " ${WHITE}${BOLD}https://aka.ms/powershell${NC}"
Write-Host ""
Write-Host " ${GRAY}The examples are also available at:${NC}"
Write-Host " ${WHITE}https://github.com/Codeplain-ai/plainlang-examples${NC}"
Write-Host ""
}

if (-not (Get-Command Expand-Archive -ErrorAction SilentlyContinue)) {
Show-MissingArchiveCmdletMessage
Confirm-ContinueAfterFailure
exit 0
}

# Show current directory and ask for extraction path
$CURRENT_DIR = Get-Location
Write-Host " Current folder: ${WHITE}${CURRENT_DIR}${NC}"
Expand Down Expand Up @@ -47,11 +80,13 @@ if (-not (Test-Path $EXTRACT_PATH -PathType Container)) {
New-Item -ItemType Directory -Path $EXTRACT_PATH -Force | Out-Null
} catch {
Write-Host " ${RED}${CROSS} Failed to create directory: ${EXTRACT_PATH}${NC}"
Write-Host " ${GRAY}Skipping example download.${NC}"
Confirm-ContinueAfterFailure
$SKIP_DOWNLOAD = $true
}
}

$EXAMPLES_INSTALLED = $false

if (-not $SKIP_DOWNLOAD) {
Write-Host " ${GRAY}Downloading examples...${NC}"

Expand All @@ -60,7 +95,26 @@ if (-not $SKIP_DOWNLOAD) {
try {
Invoke-WebRequest -Uri $EXAMPLES_DOWNLOAD_URL -OutFile $TEMP_ZIP -UseBasicParsing

# Check for the zip magic bytes rather than handing Expand-Archive a
# server error page. Read via a stream: 'Get-Content -Encoding Byte' is
# Windows PowerShell 5.1 only, '-AsByteStream' is PowerShell 6+ only.
$IS_ZIP = $false
if (Test-Path $TEMP_ZIP) {
$HEADER = New-Object byte[] 2
$STREAM = [System.IO.File]::OpenRead($TEMP_ZIP)
try {
$BYTES_READ = $STREAM.Read($HEADER, 0, 2)
} finally {
$STREAM.Close()
}
$IS_ZIP = ($BYTES_READ -eq 2 -and $HEADER[0] -eq 0x50 -and $HEADER[1] -eq 0x4B)
}

if (-not $IS_ZIP -and (Test-Path $TEMP_ZIP)) {
Write-Host " ${RED}${CROSS} Failed to download examples.${NC}"
Write-Host " ${GRAY}The downloaded file is not a zip archive.${NC}"
Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
} elseif ($IS_ZIP) {
Write-Host " ${GRAY}Extracting to ${EXTRACT_PATH}...${NC}"

try {
Expand Down Expand Up @@ -91,8 +145,12 @@ if (-not $SKIP_DOWNLOAD) {
Write-Host ""
Write-Host " ${GRAY}See hello-world/python/README.md for details.${NC}"
Write-Host ""
$EXAMPLES_INSTALLED = $true
} catch {
# Keep the cmdlet's own message: it is the only clue about why
# extraction failed (corrupt download, no space, no permissions).
Write-Host " ${RED}${CROSS} Failed to extract examples.${NC}"
Write-Host " ${GRAY}$($_.Exception.Message)${NC}"
}

Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
Expand All @@ -104,6 +162,10 @@ if (-not $SKIP_DOWNLOAD) {
Remove-Item -Path $TEMP_ZIP -Force -ErrorAction SilentlyContinue
}

Write-Host ""
Read-Host " Press ${WHITE}[Enter]${NC} to continue..."
if ($EXAMPLES_INSTALLED) {
Write-Host ""
Read-Host " Press ${WHITE}[Enter]${NC} to continue..."
} else {
Confirm-ContinueAfterFailure
}
}
11 changes: 9 additions & 2 deletions install/powershell/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,16 @@ if ($nonInteractive) {
Write-Host ""
}

# Run examples download if user agrees
# Run examples download if user agrees. The examples are a nice-to-have, so a
# failure there must never abort the installation: examples.ps1 runs in this
# runspace via the call operator, so a terminating error inside it would
# otherwise propagate here and take the whole installer down.
if ($downloadExamples -notmatch '^[Nn]$') {
Invoke-SubScript "examples.ps1"
try {
Invoke-SubScript "examples.ps1"
} catch {
Write-Host "${GRAY}Example download did not complete. Continuing with the installation.${NC}"
}
}

# Final verification: make sure the installed codeplain can actually run and
Expand Down