Skip to content

Commit dfd4f4e

Browse files
authored
Merge pull request #12 from lambdalisue/refactoring
🐛 Bug fixes and refactoring
2 parents 5f0aa34 + a685015 commit dfd4f4e

10 files changed

+294
-78
lines changed

bin/browse.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
#!/usr/bin/env -S deno run --allow-run --allow-read
1+
#!/usr/bin/env -S deno run --allow-run --allow-read --allow-env
22
import { parse } from "https://deno.land/[email protected]/flags/mod.ts";
3+
import { join } from "https://deno.land/[email protected]/path/mod.ts";
34
import { ensure, is } from "https://deno.land/x/[email protected]/mod.ts";
45
import { systemopen } from "https://deno.land/x/[email protected]/mod.ts";
6+
import config_dir from "https://deno.land/x/[email protected]/config_dir/mod.ts";
57
import {
68
getCommitAbbrevRef,
79
getCommitSHA1,
@@ -10,6 +12,7 @@ import {
1012
getObjectURL,
1113
getPullRequestURL,
1214
} from "../mod.ts";
15+
import { __throw } from "../util.ts";
1316

1417
export type Options = {
1518
cwd?: string;
@@ -26,22 +29,46 @@ export async function getURL(
2629
commitish: string,
2730
options: Options = {},
2831
): Promise<URL> {
32+
options.aliases = options.aliases ?? await readAliasesFile();
2933
if (options.home) {
3034
return getHomeURL(options);
3135
}
3236
if (options.pr) {
33-
commitish = await getCommitSHA1(commitish, options);
37+
commitish = await getCommitSHA1(commitish, options) ?? __throw(
38+
new Error(`No commit found for ${commitish}`),
39+
);
3440
return getPullRequestURL(commitish, options);
3541
}
3642
commitish = options.permalink
37-
? await getCommitSHA1(commitish, options)
38-
: await getCommitAbbrevRef(commitish, options);
43+
? await getCommitSHA1(commitish, options) ?? __throw(
44+
new Error(`No commit found for ${commitish}`),
45+
)
46+
: await getCommitAbbrevRef(commitish, options) ?? __throw(
47+
new Error(`No commit found for ${commitish}`),
48+
);
3949
if (options.commit) {
4050
return getCommitURL(commitish, options);
4151
}
4252
return getObjectURL(commitish, options.path ?? ".", options);
4353
}
4454

55+
export async function readAliasesFile(): Promise<Record<string, string>> {
56+
const cdir = config_dir();
57+
if (!cdir) {
58+
return {};
59+
}
60+
try {
61+
return await import(join(cdir, "browse", "aliases.json"), {
62+
assert: { type: "json" },
63+
});
64+
} catch (err) {
65+
if (err instanceof TypeError) {
66+
return {};
67+
}
68+
throw err;
69+
}
70+
}
71+
4572
async function main(args: string[]): Promise<void> {
4673
const opts = parse(args, {
4774
boolean: [

bin/browse_usage.txt

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@ OPTIONS
1212
--permalink Use a permalink instead of a regular URL.
1313
--remote=REMOTE Use a URL from the specified remote repository.
1414

15+
ALIASES
16+
To enable support for non-standard repository URLs (e.g., GitHub Enterprise), you can create aliases
17+
for the repository URL. To define these aliases, create a file named "aliases.json" within the
18+
specified configuration directory, as illustrated below:
19+
20+
{
21+
"my.github.com": "github.com",
22+
// Add more aliases as needed
23+
}
24+
25+
This file needs to be stored in the "browse" directory within the configuration directory, as
26+
indicated by the following paths based on your operating system:
27+
28+
┌─────────┬────────────────────────────────────────────┬──────────────────────────────────────────┐
29+
│ OS │ Value │ Example │
30+
├─────────┼────────────────────────────────────────────┼──────────────────────────────────────────┤
31+
│ Linux │ ($XDG_CONFIG_HOME or $HOME/.config)/browse │ /home/alisue/.config/browse │
32+
├─────────┼────────────────────────────────────────────┼──────────────────────────────────────────┤
33+
│ macOS │ $HOME/Library/Preferences/browse │ /Users/alisue/Library/Preferences/browse │
34+
├─────────┼────────────────────────────────────────────┼──────────────────────────────────────────┤
35+
│ Windows │ $APPDATA\browse │ C:\Users\alisue\AppData\Roaming\browse │
36+
└─────────┴────────────────────────────────────────────┴──────────────────────────────────────────┘
37+
1538
EXAMPLES
1639
$ browse
1740
#=> Opens a tree page of the current working directory in the HEAD commit of the current branch

commit_url.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ export async function getCommitURL(
1111
commitish: string,
1212
options: Options = {},
1313
): Promise<URL> {
14-
if (!options.remote) {
15-
const remote = await getRemoteContains(commitish, options);
16-
return getCommitURL(commitish, { ...options, remote: remote ?? "origin" });
17-
}
18-
const fetchURL = await getRemoteFetchURL(options.remote, options);
14+
const remote = options.remote ??
15+
await getRemoteContains(commitish, options) ??
16+
"origin";
17+
const fetchURL = await getRemoteFetchURL(remote, options);
1918
if (!fetchURL) {
20-
throw new Error(`Remote '${options.remote}' has no fetch URL`);
19+
throw new Error(`No remote '${remote}' found`);
2120
}
2221
const hostingService = await getHostingService(fetchURL, options);
2322
return hostingService.getCommitURL(fetchURL, commitish);

home_url.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ export type Options = ExecuteOptions & {
1010
export async function getHomeURL(
1111
options: Options = {},
1212
): Promise<URL> {
13-
if (!options.remote) {
14-
const remote = await getRemoteContains("HEAD", options);
15-
return getHomeURL({ ...options, remote: remote ?? "origin" });
16-
}
17-
const fetchURL = await getRemoteFetchURL(options.remote, options);
13+
const remote = options.remote ??
14+
await getRemoteContains("HEAD", options) ??
15+
"origin";
16+
const fetchURL = await getRemoteFetchURL(remote, options);
1817
if (!fetchURL) {
19-
throw new Error(`Remote '${options.remote}' has no fetch URL`);
18+
throw new Error(`No remote '${remote}' found or failed to get fetch URL.`);
2019
}
2120
const hostingService = await getHostingService(fetchURL, options);
2221
return hostingService.getHomeURL(fetchURL);

hosting_service/mod.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,26 @@ export async function getHostingService(
2626
): Promise<HostingService> {
2727
const hostname = aliases?.[fetchURL.hostname] ?? fetchURL.hostname;
2828
const svcName = hostname.replace(/\W/g, "_");
29-
const svc = await import(
30-
new URL(`./services/${svcName}.ts`, import.meta.url).href
31-
);
32-
return svc.service;
29+
try {
30+
const svc = await import(
31+
new URL(`./services/${svcName}.ts`, import.meta.url).href
32+
);
33+
return svc.service;
34+
} catch (err: unknown) {
35+
if (err instanceof TypeError) {
36+
// TypeError: Module not found "...".
37+
throw new UnsupportedHostingServiceError(hostname, svcName);
38+
}
39+
throw err;
40+
}
41+
}
42+
43+
export class UnsupportedHostingServiceError extends Error {
44+
constructor(
45+
public hostname: string,
46+
public svcName: string,
47+
) {
48+
super(`Unsupported hosting service: ${hostname} (${svcName})`);
49+
this.name = this.constructor.name;
50+
}
3351
}

hosting_service/mod_test.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import { assertSnapshot } from "https://deno.land/[email protected]/testing/snapshot.ts";
2-
import { getHostingService } from "./mod.ts";
1+
import { assertRejects } from "https://deno.land/[email protected]/assert/mod.ts";
2+
import { assertSnapshot } from "https://deno.land/[email protected]/testing/snapshot.ts";
3+
import { getHostingService, UnsupportedHostingServiceError } from "./mod.ts";
34

45
Deno.test("getHostingService", async (t) => {
6+
await t.step("throws error for unsupported hosting service", async () => {
7+
const url = new URL("https://example.com/lambdalisue/gin.vim");
8+
await assertRejects(
9+
() => {
10+
return getHostingService(url);
11+
},
12+
UnsupportedHostingServiceError,
13+
);
14+
});
15+
516
const urls = [
617
new URL("ssh://[email protected]/lambdalisue/gin.vim"),
718
new URL("https://github.com/lambdalisue/gin.vim"),
819
];
9-
1020
for (const url of urls) {
1121
const svc = await getHostingService(url);
1222

object_url.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,12 @@ export async function getObjectURL(
1313
path: string,
1414
options: Options = {},
1515
): Promise<URL> {
16-
if (!options.remote) {
17-
const remote = await getRemoteContains(commitish, options);
18-
return getObjectURL(commitish, path, {
19-
...options,
20-
remote: remote ?? "origin",
21-
});
22-
}
23-
const fetchURL = await getRemoteFetchURL(options.remote, options);
16+
const remote = options.remote ??
17+
await getRemoteContains(commitish, options) ??
18+
"origin";
19+
const fetchURL = await getRemoteFetchURL(remote, options);
2420
if (!fetchURL) {
25-
throw new Error(`Remote '${options.remote}' has no fetch URL`);
21+
throw new Error(`No remote '${remote}' found`);
2622
}
2723
const hostingService = await getHostingService(fetchURL, options);
2824
const [normPath, range] = parsePath(path);

pull_request_url.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { execute, ExecuteOptions } from "./process.ts";
22
import { getHostingService } from "./hosting_service/mod.ts";
3-
import { getCommitSHA1, getRemoteContains, getRemoteFetchURL } from "./util.ts";
3+
import {
4+
__throw,
5+
getCommitSHA1,
6+
getRemoteContains,
7+
getRemoteFetchURL,
8+
} from "./util.ts";
49

510
type Options = ExecuteOptions & {
611
remote?: string;
@@ -11,16 +16,12 @@ export async function getPullRequestURL(
1116
commitish: string,
1217
options: Options = {},
1318
): Promise<URL> {
14-
if (!options.remote) {
15-
const remote = await getRemoteContains(commitish, options);
16-
return getPullRequestURL(commitish, {
17-
...options,
18-
remote: remote ?? "origin",
19-
});
20-
}
21-
const fetchURL = await getRemoteFetchURL(options.remote, options);
19+
const remote = options.remote ??
20+
await getRemoteContains(commitish, options) ??
21+
"origin";
22+
const fetchURL = await getRemoteFetchURL(remote, options);
2223
if (!fetchURL) {
23-
throw new Error(`Remote '${options.remote}' has no fetch URL`);
24+
throw new Error(`No remote '${remote}' found`);
2425
}
2526
const hostingService = await getHostingService(fetchURL, options);
2627
if (!hostingService.getPullRequestURL) {
@@ -30,7 +31,7 @@ export async function getPullRequestURL(
3031
}
3132
const pr = await getPullRequestContains(
3233
commitish,
33-
options.remote,
34+
remote,
3435
options,
3536
);
3637
if (!pr) {
@@ -45,7 +46,9 @@ async function getPullRequestContains(
4546
options: ExecuteOptions = {},
4647
): Promise<number | undefined> {
4748
const branch = await getRemoteDefaultBranch(remote, options) ?? "main";
48-
const sha = await getCommitSHA1(commitish, options);
49+
const sha = await getCommitSHA1(commitish, options) ?? __throw(
50+
new Error(`No commit found for ${commitish}`),
51+
);
4952
let stdout: string;
5053
// The sha may points to a merge commit itself.
5154
stdout = await execute(

0 commit comments

Comments
 (0)