Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shellcode for fish #838

Closed
wants to merge 3 commits into from
Closed
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.shellcode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ jobs:
duf --version
shell: bash -eo posix {0}

- name: x
run: |
eval "$(pkgx --shellcode)"
ack --help
if [ $? -eq 0 ]; then exit 1; fi
set -e
x
shell: bash {0} # no erroring for first command thanks

- name: x with quoted args
run: |
eval "$(pkgx --shellcode)"
echo "baz foo bar baz" > test-file
ack "foo bar" test-file
if [ $? -eq 0 ]; then exit 1; fi
set -e
x
shell: bash {0} # no erroring for first command thanks

zsh:
needs: compile
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion src/err-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function(err: Error) {
render('version unavailable', utils.pkg.str(err.pkg), [
['please check the following url for available versions'],
['if it’s not there, we’ll build it! open a ticket on the pantry.']
], `https://dist.pkgx.dev/?prefix=${err.pkg.project}/${platform}/${arch}`)
], `https://pkgx.dev/pkgs/${err.pkg.project}/`)
} else if (err instanceof PantryParseError) {
//TODO well not if it's a custom edit tho
render('parse error', err.project, [
Expand Down
30 changes: 19 additions & 11 deletions src/modes/internal.use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import escape_if_necessary from "../utils/sh-escape.ts"
import construct_env from "../prefab/construct-env.ts"
import install, { Logger } from "../prefab/install.ts"
import { PackageRequirement, utils } from "pkgx"
import { basename } from "deno/path/basename.ts"

interface Pkgs {
plus: PackageRequirement[]
Expand All @@ -11,12 +12,18 @@ interface Pkgs {

export default async function(opts: { pkgs: Pkgs, logger: Logger, pkgenv?: Record<string, string>, update: boolean | Set<string> }) {
const { install, construct_env, getenv } = _internals
const isfish = utils.flatmap(Deno.env.get("SHELL"), basename) == 'fish'

const export_ = (key: string, value: string) => isfish
? `set -gx ${key} ${escape_if_necessary(value)}`
: `export ${key}=${escape_if_necessary(value)}`

const pkgs = consolidate(opts.pkgs)

if (pkgs.length == 0) {
const shellcode = `${isfish ? 'set -e' : 'unset'} PKGX_POWDER PKGX_PKGENV`
return {
shellcode: 'unset PKGX_POWDER PKGX_PKGENV',
shellcode,
pkgenv: []
}
} else {
Expand All @@ -27,11 +34,11 @@ export default async function(opts: { pkgs: Pkgs, logger: Logger, pkgenv?: Recor
const env = await construct_env(pkgenv)

for (const [key, value] of Object.entries(env)) {
print(`export ${key}=${escape_if_necessary(value)}`)
print(export_(key, value))
}

print(`export PKGX_POWDER="${pkgenv.pkgenv.map(utils.pkg.str).join(' ')}"`)
print(`export PKGX_PKGENV="${pkgenv.installations.map(({pkg}) => utils.pkg.str(pkg)).join(' ')}"`)
print(export_('PKGX_POWDER', pkgenv.pkgenv.map(utils.pkg.str).join(' ')))
print(export_('PKGX_PKGENV', pkgenv.installations.map(({pkg}) => utils.pkg.str(pkg)).join(' ')))

// if (/\(pkgx\)/.test(getenv("PS1") ?? '') == false) {
// //FIXME doesn't work with warp.dev for fuck knows why reasons
Expand All @@ -41,33 +48,34 @@ export default async function(opts: { pkgs: Pkgs, logger: Logger, pkgenv?: Recor

print('')

print('_pkgx_reset() {')
print(isfish ? 'function _pkgx_reset' : '_pkgx_reset() {')
for (const key in env) {
const old = getenv(key)
if (old !== undefined) {
//TODO don’t export if not currently exported!
print(` export ${key}=${escape_if_necessary(old)}`)
print(' ' + export_(key, old))
} else {
print(` unset ${key}`)
print(` ${isfish ? 'set -e' : 'unset'} ${key}`)
}
}

// const ps1 = getenv('PS1')
// print(ps1 ? ` export PS1="${ps1}"` : ' unset PS1')
// print(' unset -f _pkgx_reset _pkgx_install')

print('}')
print(isfish ? 'end' : '}')

const install_set = (({pkgenv, installations}) => {
const set = new Set(pkgenv.map(({project}) => project))
return installations.compact(({pkg}) => set.has(pkg.project) && pkg)
})(pkgenv)

print('')
print('_pkgx_install() {')

print(isfish ? 'function _pkgx_install' : '_pkgx_install() {')
print(` command pkgx install ${install_set.map(utils.pkg.str).join(' ')}`)
print(` pkgx ${pkgenv.pkgenv.map(x => `-${utils.pkg.str(x)}`).join(' ')}`)
print('}')
print(` env ${pkgenv.pkgenv.map(x => `-${utils.pkg.str(x)}`).join(' ')}`)
print(isfish ? 'end' : '}')

return {shellcode: rv, pkgenv: install_set}
}
Expand Down
104 changes: 79 additions & 25 deletions src/modes/shellcode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import useConfig from 'pkgx/hooks/useConfig.ts'
import { flatmap } from "pkgx/utils/misc.ts"
import { basename } from "deno/path/mod.ts"
import undent from 'outdent'
import { Path } from 'pkgx'
import { flatmap } from "pkgx/utils/misc.ts";

// NOTES
// * is safely re-entrant (and idempotent)
Expand All @@ -14,13 +15,23 @@ import { flatmap } from "pkgx/utils/misc.ts";
// * remove the files we create for command not found handler once any prompt appears
// * need to use a proper tmp location perhaps

const blurple = (x: string) => `\\033[38;5;63m${x}\\033[0m`
const dim = (x: string) => `\\e[2m${x}\\e[0m`

export default function() {
const blurple = (x: string) => `\\033[38;5;63m${x}\\033[0m`
const dim = (x: string) => `\\e[2m${x}\\e[0m`
const datadir = useConfig().data.join("dev")
const tmp = (flatmap(Deno.env.get("XDG_STATE_HOME"), Path.abs) ?? platform_state_default()).join("pkgx")
const sh = '${SHELL:-/bin/sh}'

switch (flatmap(Deno.env.get("SHELL"), basename)) {
case 'fish':
return fish(tmp.string)
default:
return posixish({tmp, datadir, sh})
}
}

function posixish({tmp, sh, datadir}: {datadir: Path, tmp: Path, sh: string}) {
return undent`
pkgx() {
case "$1" in
Expand All @@ -45,22 +56,18 @@ export default function() {
}

x() {
case $1 in
"")
if [ -f "${tmp}/shellcode/x.$$" ]; then
if foo="$("${tmp}/shellcode/u.$$")"; then
eval "$foo"
${sh} "${tmp}/shellcode/x.$$"
unset foo
fi
rm "${tmp}/shellcode/"?.$$
else
echo "pkgx: nothing to run" >&2
return 1
fi;;
*)
command pkgx -- "$@";;
esac
if [ $# -gt 0 ]; then
command pkgx -- "$@"
elif [ ! -f "${tmp}/shellcode/x.$$" ]; then
echo "pkgx: nothing to run" >&2
return 1
elif foo="$("${tmp}/shellcode/u.$$")"; then
eval "$foo"
unset foo
"${tmp}"/shellcode/x.$$
else
echo "pkgx: unexpected error" >&2
fi
}

env() {
Expand Down Expand Up @@ -113,15 +120,21 @@ export default function() {
echo -e '${dim('^^ type `')}x${dim('` to run that')}' >&2

d="${tmp}/shellcode"
u="$d/u.$$"
x="$d/x.$$"
mkdir -p "$d"
echo "#!${sh}" > "$d/u.$$"
echo "echo -e \\"${blurple('env')} +$1 ${dim('&&')} $@ \\" >&2" >> "$d/u.$$"
echo "exec pkgx --internal.use +\\"$1\\"" >> "$d/u.$$"
chmod u+x "$d/u.$$"
echo -n "exec " > "$d/x.$$"

echo "#!${sh}" > "$u"
echo "echo -e \\"${blurple('env')} +$1 ${dim('&&')} $@ \\" >&2" >> "$u"
echo "pkgx --internal.use +\\"$1\\"" >> "$u"

echo "#!${sh}" > "$x"
echo "rm \\"$d\\"/?.$$" >> "$x"
echo -n "exec " >> "$x"
for arg in "$@"; do
printf "%q " "$arg" >> "$d/x.$$"
printf "%q " "$arg" >> "$x"
done
chmod u+x "$d"/*.$$

return 127
else
Expand Down Expand Up @@ -203,3 +216,44 @@ function platform_state_default() {
return Path.home().join(".local", "state")
}
}

function fish(tmp: string) {
return undent`
function fish_command_not_found
set -l cmd $argv[1]
if test "$cmd" = "pkgx"
echo "fatal: 'pkgx' not in PATH" >&2
return 1
else if test -t 2; and pkgx --provider --silent "$cmd"
echo -e "${dim("^^ type \`")}x${dim("\` to run that")}" >&2
set -l d "${tmp}/shellcode"
mkdir -p $d

echo "#!/bin/sh" > "$d/u.$fish_pid"
echo "echo -e '${blurple("env")} +$cmd ${dim("&&")} $argv' >&2" >> "$d/u.$fish_pid"
echo "rm \"$tmp\"/shellcode/*.$fish_pid" >> "$d/u.$fish_pid"
echo "SHELL=fish exec pkgx --internal.use +$cmd" >> "$d/u.$fish_pid"

echo "#!/bin/sh" > "$d/x.$fish_pid"
echo "exec $argv" >> "$d/x.$fish_pid"

chmod u+x "$d"/*.$fish_pid

return 127
else
echo "cmd not found: $cmd" >&2
return 127
end
end

function x
if test -f "${tmp}/shellcode/x.$fish_pid"
"${tmp}"/shellcode/u.$fish_pid | source
"${tmp}"/shellcode/x.$fish_pid
else
echo "pkgx: nothing to run" >&2
return 1
end
end
`
}
7 changes: 4 additions & 3 deletions src/prefab/construct-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export default async function(pkgenv: { installations: Installation[] }) {
}
}

for (const key in rv) {
rv[key] = rv[key].replaceAll(new RegExp(`\\$${key}\\b`, 'g'), `\${${key}}`)
}
// stopped doing this since it doesn’t work with fish
// for (const key in rv) {
// rv[key] = rv[key].replaceAll(new RegExp(`\\$${key}\\b`, 'g'), `\${${key}}`)
// }

return rv
}
Expand Down