Skip to content

Commit e4e18cf

Browse files
authored
Merge pull request #5 from pkgxdev/integrate
`dev integrate` and `dev deintegrate`
2 parents 9b1ab37 + f0703ad commit e4e18cf

File tree

4 files changed

+143
-4
lines changed

4 files changed

+143
-4
lines changed

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ packages you need for different projects as you navigate in your shell.
66
## Installation
77

88
```sh
9-
echo 'eval "$(pkgx dev --shellcode)"' >> ~/.zshrc
9+
pkgx dev integrate
1010
```
1111

12-
We support bashlike shells (adapt the rc file above). Fish support is welcome,
13-
but I don’t understand Fish so please PR!
14-
1512
> [!NOTE]
1613
>
1714
> `pkgx` is a required dependency.
@@ -20,6 +17,22 @@ but I don’t understand Fish so please PR!
2017
> brew install pkgxdev/made/pkgx || sh <(curl https://pkgx.sh)
2118
> ```
2219
20+
> `pkgx dev integrate` looks for and edits known `shell.rc` files adding one
21+
> line:
22+
>
23+
> ```sh
24+
> eval "$(pkgx dev --shellcode)"
25+
> ```
26+
>
27+
> If you don’t trust us (good on you), then do a dry run first:
28+
>
29+
> ```sh
30+
> pkgx dev integrate --dry-run
31+
> ```
32+
33+
> We support **Bash** and **Zsh**. We would love to support more shells. PRs
34+
> very welcome.
35+
2336
> [!TIP]
2437
> If you like, preview the shellcode: `pkgx dev --shellcode`.
2538

app.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import shellcode from "./src/shellcode().ts";
88
import sniff from "./src/sniff.ts";
99
import shell_escape from "./src/shell-escape.ts";
1010
import app_version from "./src/app-version.ts";
11+
import integrate from "./src/integrate.ts";
1112

1213
switch (Deno.args[0]) {
1314
case "--help": {
@@ -25,6 +26,13 @@ switch (Deno.args[0]) {
2526
console.log(`dev ${app_version}`);
2627
Deno.exit(0);
2728
break; // deno lint insists
29+
case "integrate":
30+
await integrate("install", { dryrun: Deno.args[1] == "--dry-run" });
31+
Deno.exit(0);
32+
break;
33+
case "deintegrate":
34+
await integrate("uninstall", { dryrun: Deno.args[1] == "--dry-run" });
35+
Deno.exit(0);
2836
}
2937

3038
const snuff = await sniff(Path.cwd());

deno.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/integrate.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import readLines from "libpkgx/utils/read-lines.ts";
2+
import { readAll, writeAll } from "jsr:@std/io";
3+
import { Path, utils } from "libpkgx";
4+
const { flatmap } = utils;
5+
6+
export default async function (
7+
op: "install" | "uninstall",
8+
{ dryrun }: { dryrun: boolean },
9+
) {
10+
let opd_at_least_once = false;
11+
const encode = ((e) => e.encode.bind(e))(new TextEncoder());
12+
13+
const fopts = { read: true, ...dryrun ? {} : { write: true, create: true } };
14+
15+
here: for (const [file, line] of shells()) {
16+
const fd = await Deno.open(file.string, fopts);
17+
try {
18+
let pos = 0;
19+
for await (const readline of readLines(fd)) {
20+
if (readline.trim().endsWith("# https://github.com/pkgxdev/dev")) {
21+
if (op == "install") {
22+
console.error("hook already integrated:", file);
23+
continue here;
24+
} else if (op == "uninstall") {
25+
// we have to seek because readLines is buffered and thus the seek pos is probs already at the file end
26+
await fd.seek(pos + readline.length + 1, Deno.SeekMode.Start);
27+
const rest = await readAll(fd);
28+
29+
if (!dryrun) await fd.truncate(pos); // deno has no way I can find to truncate from the current seek position
30+
await fd.seek(pos, Deno.SeekMode.Start);
31+
if (!dryrun) await writeAll(fd, rest);
32+
33+
opd_at_least_once = true;
34+
console.error("removed hook:", file);
35+
36+
continue here;
37+
}
38+
}
39+
40+
pos += readline.length + 1; // the +1 is because readLines() truncates it
41+
}
42+
43+
if (op == "install") {
44+
const byte = new Uint8Array(1);
45+
if (pos) {
46+
await fd.seek(0, Deno.SeekMode.End); // potentially the above didn't reach the end
47+
while (true && pos > 0) {
48+
await fd.seek(-1, Deno.SeekMode.Current);
49+
await fd.read(byte);
50+
if (byte[0] != 10) break;
51+
await fd.seek(-1, Deno.SeekMode.Current);
52+
pos -= 1;
53+
}
54+
55+
if (!dryrun) {
56+
await writeAll(
57+
fd,
58+
encode(`\n\n${line} # https://github.com/pkgxdev/dev\n`),
59+
);
60+
}
61+
}
62+
opd_at_least_once = true;
63+
console.error(`${file} << \`${line}\``);
64+
}
65+
} finally {
66+
fd.close();
67+
}
68+
}
69+
if (dryrun && opd_at_least_once) {
70+
console.error(
71+
"%cthis was a dry-run. %cnothing was changed.",
72+
"color: #5f5fff",
73+
"color: initial",
74+
);
75+
} else {switch (op) {
76+
case "uninstall":
77+
if (!opd_at_least_once) {
78+
console.error("nothing to deintegrate found");
79+
}
80+
break;
81+
case "install":
82+
if (opd_at_least_once) {
83+
console.log(
84+
"now %crestart your terminal%c for `dev` hooks to take effect",
85+
"color: #5f5fff",
86+
"color: initial",
87+
);
88+
}
89+
}}
90+
}
91+
92+
function shells(): [Path, string][] {
93+
const eval_ln = 'eval "$(pkgx dev --shellcode)"';
94+
95+
const zdotdir = flatmap(Deno.env.get("ZDOTDIR"), Path.abs) ?? Path.home();
96+
const zshpair: [Path, string] = [zdotdir.join(".zshrc"), eval_ln];
97+
98+
const candidates: [Path, string][] = [
99+
zshpair,
100+
[Path.home().join(".bashrc"), eval_ln],
101+
[Path.home().join(".bash_profile"), eval_ln],
102+
];
103+
104+
const viable_candidates = candidates.filter(([file]) => file.exists());
105+
106+
if (viable_candidates.length == 0) {
107+
if (Deno.build.os == "darwin") {
108+
/// macOS has no .zshrc by default and we want mac users to get a just works experience
109+
return [zshpair];
110+
} else {
111+
console.error("no `.shellrc` files found");
112+
Deno.exit(1);
113+
}
114+
}
115+
116+
return viable_candidates;
117+
}

0 commit comments

Comments
 (0)