Skip to content

Commit

Permalink
fix(scriptlets): account for numbers after pipes
Browse files Browse the repository at this point in the history
  • Loading branch information
johnlindquist committed Sep 6, 2024
1 parent 29a3a72 commit 1d8340d
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 10 deletions.
123 changes: 123 additions & 0 deletions src/core/scriptlets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import ava from "ava"
import type { Scriptlet } from "../types"
import { parseMarkdownAsScriptlets, home, kenvPath } from "./utils"
import { formatScriptlet } from "./scriptlets"
import * as os from "node:os"
import { execSync } from "child_process"

// Helper function to create a temporary snippet file
process.env.KENV = home(".mock-kenv")
Expand Down Expand Up @@ -287,6 +289,10 @@ ava("formatScriptlet with numbered inputs", (t) => {
})

ava("formatScriptlet with Windows-style inputs", (t) => {
if (os.platform() !== "win32") {
t.pass("Skipping test on non-Windows platforms")
return
}
const scriptlet = {
name: "Windows Inputs Test",
tool: "cmd",
Expand Down Expand Up @@ -799,6 +805,101 @@ ava("formatScriptlet - benchmark performance", (t) => {
t.pass()
})

ava(
"formatScriptlet replaces numbered variables only before pipe in Unix-style",
(t) => {
if (os.platform() === "win32") {
t.pass("Skipping test on Windows")
return
}

const scriptlet: Scriptlet = {
name: "Test Unix Pipe",
tool: "bash",
scriptlet: "echo $1 $2 | grep $1",
inputs: []
} as Scriptlet

const { formattedScriptlet } = formatScriptlet(scriptlet, [
"hello",
"world"
])
t.is(formattedScriptlet, "echo hello world | grep $1")
}
)

ava(
"formatScriptlet replaces numbered variables only before ampersand in Windows-style",
(t) => {
if (os.platform() !== "win32") {
t.pass("Skipping test on non-Windows platforms")
return
}
const scriptlet: Scriptlet = {
name: "Test Windows Pipe",
tool: "cmd",
scriptlet: "echo %1 %2 & findstr %1",
inputs: []
} as Scriptlet

const { formattedScriptlet } = formatScriptlet(scriptlet, [
"hello",
"world"
])
if (os.platform() === "win32") {
t.is(formattedScriptlet, "echo hello world & findstr %1")
} else {
t.is(formattedScriptlet, "echo hello world | findstr %1")
}
}
)

ava(
"formatScriptlet handles multiple pipes correctly on non-Windows platforms",
(t) => {
if (os.platform() === "win32") {
t.pass("Skipping test on Windows platforms")
return
}
const scriptlet: Scriptlet = {
name: "Test Multiple Pipes",
tool: "bash",
scriptlet: "echo $1 $2 | grep $3 | sed 's/$4/$5/'",
inputs: []
} as Scriptlet

const { formattedScriptlet } = formatScriptlet(scriptlet, [
"a",
"b",
"c",
"d",
"e"
])
t.is(formattedScriptlet, "echo a b | grep $3 | sed 's/$4/$5/'")
}
)

ava("formatScriptlet handles mixed Unix and Windows style variables", (t) => {
const scriptlet: Scriptlet = {
name: "Test Mixed Styles",
tool: "bash",
scriptlet: "echo $1 %2 | grep ${3} %4",
inputs: []
} as Scriptlet

const { formattedScriptlet } = formatScriptlet(scriptlet, [
"a",
"b",
"c",
"d"
])
if (os.platform() === "win32") {
t.is(formattedScriptlet, "echo $1 a | grep ${3} b")
} else {
t.is(formattedScriptlet, "echo a %2 | grep ${3} %4")
}
})

ava("formatScriptlet should not treat 'else' as an input", (t) => {
const scriptlet = {
name: "Else Not Input Test",
Expand Down Expand Up @@ -951,3 +1052,25 @@ ava(
t.deepEqual(remainingInputs, [])
}
)

ava("formatScriptlet handles complex du command with awk", (t) => {
if (os.platform() === "win32") {
t.pass("Skipping test on Windows")
return
}

const complexCommand =
"find ~/.kit -type d -exec du -sh {} + | sort -hr | awk '$1 ~ /[0-9]M|[0-9]G/ {print $0}'"

const scriptlet: Scriptlet = {
name: "Find Large Directories",
tool: "bash",
scriptlet: complexCommand,

inputs: []
} as Scriptlet

const { formattedScriptlet } = formatScriptlet(scriptlet, [])

t.is(formattedScriptlet, complexCommand)
})
23 changes: 13 additions & 10 deletions src/core/scriptlets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { kenvPath } from "./resolvers.js"
import { getKenvFromPath, highlight, tagger } from "./utils.js"
import { SHELL_TOOLS } from "./constants.js"
import { processConditionals } from "./scriptlet.utils.js"
import os from "node:os"

export function formatScriptlet(
focusedScriptlet: Scriptlet,
Expand All @@ -17,20 +18,22 @@ export function formatScriptlet(
throw new Error(`No template found for ${focusedScriptlet.value.name}`)
}

scriptlet = processConditionals(scriptlet, flag).trim() // Trim the result after processing conditionals
scriptlet = processConditionals(scriptlet, flag).trim()

const namedInputs = focusedScriptlet?.inputs || []
const remainingInputs = [...namedInputs]

// Replace numbered inputs first
for (let i = 0; i < inputs.length; i++) {
const index = i + 1
const unixPattern = new RegExp(`\\$\\{?${index}\\}?`, "g")
const windowsPattern = new RegExp(`%${index}`, "g")
scriptlet = scriptlet
.replace(unixPattern, inputs[i])
.replace(windowsPattern, inputs[i])
}
const pipeSymbol = os.platform() === "win32" ? "&" : "|"
const parts = scriptlet.split(new RegExp(`\\${pipeSymbol}`))
const variableSymbol = os.platform() === "win32" ? "%" : "$"
parts[0] = parts[0].replace(
new RegExp(`\\${variableSymbol}(\\d+)`, "g"),
(match, unixNum, winNum) => {
const index = Number.parseInt(unixNum || winNum) - 1
return inputs[index] !== undefined ? inputs[index] : match
}
)
scriptlet = parts.join(pipeSymbol)

// Then replace named inputs, but only if they haven't been replaced by numbered inputs
// and are not part of a conditional statement
Expand Down

0 comments on commit 1d8340d

Please sign in to comment.