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

tea --magic=install; tea --magic=uninstall #646

Merged
merged 2 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</p>


# tea/cli 0.38.4
# tea/cli 0.39.0

`tea` is [`npx`] for *everything*.

Expand All @@ -30,9 +30,12 @@ $ brew install teaxyz/pkgs/tea-cli
$ tea node --version
v19.7.0

$ tea node@16 --version
v16.20.1

$ node
command not found: node
# ^^ tea is not a package manager; keep installing shit w/brew
# ^^ tea is not a package manager; if you need installs, keep using brew
```

[`npx`]: https://www.npmjs.com/package/npx
Expand All @@ -54,6 +57,7 @@ v16.20.1

$ source <(tea --env)
# temporarily adds `my-project`’s deps to your current shell
# install our “shell magic” to do this automatically (read on for docs)

$ node --version
v16.20.1
Expand All @@ -77,7 +81,8 @@ This works for everything. eg. `pyproject.toml`, `.ruby-version`, etc.

> <details><summary><i>PSA:</i> Stop using Docker</summary><br>
>
> Docker is great for deployment and cross compilation, but… let’s face it: it
> Look, don’t *stop* using Docker—that’s not what we mean.
> Docker is great for deployment, but… let’s face it: it
> sucks for dev.
>
> *Docker stifles builders*.
Expand Down Expand Up @@ -173,8 +178,9 @@ $ tea \

## Shell Magic

If you’re cool and you love cool stuff then `tea` can optionally make command
not found errors a thing of the past:
Every README for every project has 10 pages of “how to install x” commands.
With `tea`’s magic you can just copy and paste the usage instructions and skip
the install instructions.

```sh
$ which node
Expand Down Expand Up @@ -216,6 +222,18 @@ $ node
command not found: node
```

### Installing Shell Magic

If you want shell magic when your terminal loads, install it:

```sh
$ tea --magic=install
# installs a one-liner to your ~/.shellrc

$ tea --magic=uninstall
# nps if you change your mind
```

&nbsp;


Expand All @@ -235,17 +253,15 @@ sudo install -m 755 \
/usr/local/bin/tea
```

## Setting up Magic

By itself tea works well, it’s just somewhat manual, but we’re all magic
addicts and recommend it:
Once installed you can install our shell magic:

```sh
echo 'source <(tea --magic)' >> ~/.zshrc
tea --magic=install
```

With magic stepping into directories ensures the packages those projects need
* Stepping into directories ensures the packages those projects need
are installed on demand and available to the tools you’re using.
* Commands you type just work without package management nonsense.

&nbsp;

Expand Down
86 changes: 86 additions & 0 deletions src/app.magic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,89 @@ export default function(self: Path, shell?: string) {
throw new TeaError("unsupported shell", {shell})
}
}

import { readLines } from "deno/io/read_lines.ts"
import { readAll } from "deno/streams/read_all.ts"
import { writeAll } from "deno/streams/write_all.ts"
import { flatmap } from "tea/utils/misc.ts";


//TODO could be a fun efficiency excercise to maintain a separate write file-pointer
//TODO assumes unix line-endings
export async function install_magic(op: 'install' | 'uninstall') {
let opd_at_least_once = false
const encode = (() => { const e = new TextEncoder(); return e.encode.bind(e) })()

here: for (const [file, line] of shells()) {
const fd = await Deno.open(file.string, {read: true, write: true})
try {
let pos = 0
for await (const readline of readLines(fd)) {
if (readline.trim() == line) {
if (op == 'install') {
console.info("magic already installed:", file)
continue here
} else if (op == 'uninstall') {
// we have to seek because readLines is buffered and thus the seek pos is probs already at the file end
fd.seek(pos + readline.length + 1, Deno.SeekMode.Start)
const rest = await readAll(fd)

await fd.truncate(pos) // deno has no way I can find to truncate from the current seek position
fd.seek(pos, Deno.SeekMode.Start)
await writeAll(fd, rest)

opd_at_least_once = true
console.info("removed magic:", file)

continue here
}
}

pos += readline.length + 1 // the +1 is because readLines() truncates it
}

if (op == 'install') {
const byte = new Uint8Array(1)
fd.seek(0, Deno.SeekMode.End) // potentially the above didn't reach the end
while (true) {
fd.seek(-1, Deno.SeekMode.Current)
await fd.read(byte)
if (byte[0] != 10) break
fd.seek(-1, Deno.SeekMode.Current)
}

await writeAll(fd, encode(`\n\n${line}\n`))

console.info("magic installed:", file)
}
} finally {
fd.close()
}
}

if (op == 'uninstall' && !opd_at_least_once) {
console.info("magic already not installed")
}
}

function shells(): [Path, string][] {
const zdotdir = flatmap(Deno.env.get("ZDOTDIR"), Path.abs) ?? Path.home()
const fishdir = flatmap(Deno.env.get("XDG_CONFIG_HOME"), Path.abs) ?? Path.home().join(".config")
mxcl marked this conversation as resolved.
Show resolved Hide resolved

const std = (shell: string) => `source <(tea --magic=${shell}) #docs.tea.xyz/magic`

const candidates: [Path, string][] = [
[zdotdir.join(".zshrc"), std("zsh")],
[Path.home().join(".bashrc"), 'source /dev/stdin <<<"$(tea --magic=bash) #docs.tea.xyz/magic'],
[Path.home().join(".config/elvish/rc.elv"), std("elvish")],
mxcl marked this conversation as resolved.
Show resolved Hide resolved
[fishdir.join("fish/config.fish"), "tea --magic=fish | source #docs.tea.xyz/magic"],
]

const viable_candidates = candidates.filter(([file]) => file.exists())

if (viable_candidates.length == 0) {
throw new TeaError("no shell rc files found to install magic into")
}

return viable_candidates
}
11 changes: 9 additions & 2 deletions src/app.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import complete from "./app.complete.ts"
import provides from "./app.provides.ts"
import setup from "./prefab/setup.ts"
const { useSync, usePantry } = hooks
import magic from "./app.magic.ts"
import magic, { install_magic } from "./app.magic.ts"
import dump from "./app.dump.ts"
import help from "./app.help.ts"
import { Args } from "./args.ts"
Expand Down Expand Up @@ -112,7 +112,14 @@ export async function run(args: Args) {
await complete(args.complete)
break
default:
await print(magic(execPath, args.mode[1]))
switch (args.mode[1]) {
case 'install':
case 'uninstall':
install_magic(args.mode[1])
break
default:
await print(magic(execPath, args.mode[1]))
}
}
}

Expand Down