Created
September 28, 2022 07:50
-
-
Save cvan/7474708d475630201d3aeb266998867c to your computer and use it in GitHub Desktop.
extract user/repo pair from a GitHub URL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const fs = require("fs"); | |
const path = require("path"); | |
// Copy this file into a file into $HOME/.local/bin/ (or a similar directory in `$PATH`) | |
// | |
// 1. pbpaste > ~/.local/bin/git_pair.js | |
// x-sel --clipboard --output > ~/.local/bin/git_pair.js | |
// 2. Run commands above to generate an executable file called `git_pair` | |
// 3. chmod +x ~/.local/bin/git_pair | |
// 4. git_pair | |
/* | |
Run these shell commands to generate an executable file called `git_pair` | |
cd $HOME/.local/bin | |
ls -al git_pair | |
rm git_pair | |
tee git_pair <<'EO_TEST_RUN' | |
#!/bin/bash | |
node ./git_pair.js ${@} | |
EO_TEST_RUN | |
chmod u+x git_pair | |
ls -al git_pair | |
git_pair | |
*/ | |
const SCRIPT_NAME = "git_pair"; | |
const SCRIPT_VERSION = "v0.1.0"; | |
const DEFAULT_FALLBACK_ORIGIN = "https://github.com"; | |
const PROVIDER_HOST_ORIGINS = { | |
github: "https://github.com", | |
gitlab: "https://gitlab.com" | |
}; | |
const safeTrim = (text) => `${text || ""}`.replace(/\s+/g, " ").trim(); | |
const isMaybeUrl = (text) => { | |
if (typeof text || !text?.length) { | |
return false; | |
} | |
const urlMaybe = safeTrim(text); | |
return ( | |
urlMaybe.startsWith("//") || | |
text.startsWith("https:") || | |
text.startsWith("http:") | |
); | |
}; | |
function parseGhUrl(url, customHost = DEFAULT_FALLBACK_ORIGIN) { | |
const inputChunks = safeTrim(url).split("/"); | |
if (inputChunks.length === 2) { | |
let [user, repo] = inputChunks.filter((x) => !isMaybeUrl(x)); | |
return [user, repo].join("/"); | |
} | |
if (inputChunks.length < 2) { | |
return url; | |
} | |
// Ensure there's a protocol. | |
let hostOrigin = ""; | |
if ( | |
typeof customHost === "undefined" || | |
(typeof customHost === "string" && | |
customHost?.length && | |
!customHost.includes("/") && | |
!customHost.includes(".")) | |
) { | |
// matches: | |
// - 'gitlab' | |
// - 'github' | |
const matchFound = PROVIDER_HOST_ORIGINS[customHost.toLowerCase()]; | |
hostOrigin = matchFound || PROVIDER_HOST_ORIGINS.github; | |
if (url.toLowerCase().includes("gitlab.")) { | |
hostOrigin = PROVIDER_HOST_ORIGINS.gitlab; | |
} | |
if (url.toLowerCase().includes("github.")) { | |
hostOrigin = PROVIDER_HOST_ORIGINS.github; | |
} | |
} else { | |
hostOrigin = customHost; | |
if ( | |
!hostOrigin.startsWith("https://") && | |
!hostOrigin.startsWith("http://") | |
) { | |
if (hostOrigin.startsWith("//")) { | |
hostOrigin = `https:${hostOrigin}`; | |
} else { | |
hostOrigin = `https://${hostOrigin}`; | |
} | |
} | |
} | |
let rootHostname = new URL(hostOrigin)?.hostname; | |
const { origin, hostname, pathname } = new URL( | |
url.includes(`${rootHostname}/`) ? url.split(`${rootHostname}/`)[1] : url, | |
fallbackOrigin || DEFAULT_FALLBACK_ORIGIN | |
); | |
const [user, repo] = [...pathname.split("/")].splice(1, 2); | |
const userRepo = [user.trim(), repo.trim().replace(/.git$/i, "")].join("/"); | |
return `${userRepo}`; | |
} | |
const getSafeArgs = (args) => | |
(args || []).join(" ").replace(/\s+/g, " ").trim().split(" "); | |
function processInput(inputArgs = []) { | |
const safeArgs = getSafeArgs(inputArgs); | |
const getFlag = (flag = "--output") => | |
safeArgs.find((x) => flag === x.replace(/^-+/g, "")); | |
const getFlagValue = (flag = "--output") => | |
safeArgs[safeArgs.findIndex((x) => flag === x.replace(/^-+/g, "")) + 1]; | |
const hasFlag = (flag = "help") => Boolean(getFlag(flag)); | |
if (hasFlag("help")) { | |
console.log(help()); | |
process.exit(0); | |
return [undefined, undefined]; | |
} | |
if (hasFlag("version")) { | |
process.stdout.write(`${SCRIPT_VERSION}\n`); | |
process.exit(0); | |
return [undefined, undefined]; | |
} | |
const possibleInputUrl = | |
safeArgs.find((x) => `${x || ""}`.trim().split("/").length >= 2) || | |
safeArgs[0]; | |
const possibleInputFallbackOrigin = | |
safeArgs.find( | |
(x) => | |
`${x || ""}`.trim().includes(".") && | |
x.length >= 2 && | |
x !== possibleInputUrl | |
) || safeArgs[1]; | |
return [ | |
possibleInputUrl || undefined, | |
possibleInputFallbackOrigin || undefined | |
]; | |
} | |
function readStdinPiped() { | |
const rlp = require("readline"); | |
const { stdin: input, stdout: output } = process; | |
const rl = rlp.createInterface({ input, output }); | |
function ask() { | |
return new Promise((resolve) => { | |
return rl.question( | |
`${help()} | |
Enter a GitHub/GitLab link: | |
${"\x1b[2m"}${"\x1b[3m"}[example]${"\x1b[22m"}${"\x1b[0m"}\t${"\x1b[4m"}${"\x1b[2m"}${"https://github.com/greensock/GSAP/issues/322#issuecomment-553763556"}${"\x1b[24m"} | |
${"\x1b[0m"} | |
\n ___________________________\n \n `, | |
(value) => { | |
rl.output.write("\n"); | |
resolve(value); | |
} | |
); | |
}); | |
} | |
return ask(); | |
} | |
function help() { | |
return ( | |
"\n" + | |
` | |
${SCRIPT_NAME} | |
${SCRIPT_VERSION} | |
Usage: ${SCRIPT_NAME} [URL] [HOST] | |
${"\x1b[2m"}${"\x1b[3m"}[URL]${"\x1b[22m"}${"\x1b[0m"}\t${"\x1b[4m"}${"\x1b[2m"}${"https://github.com/greensock/GSAP/issues/322#issuecomment-553763556"}${"\x1b[24m"}${"\x1b[0m"} | |
${"\x1b[2m"}${"\x1b[3m"}[HOST]${"\x1b[22m"}${"\x1b[0m"}\t${"\x1b[4m"}${"\x1b[2m"}${"https://github.com"}${"\x1b[24m"} | |
${"\x1b[0m"} | |
${"\x1b[2m"}${"\x1b[3m"}[URL]${"\x1b[22m"}${"\x1b[0m"}\t${"\x1b[4m"}${"\x1b[2m"}${"https://gitlab.com/stavros/IPFessay/-/tree/master/ipfessay"}${"\x1b[24m"}${"\x1b[0m"} | |
${"\x1b[2m"}${"\x1b[3m"}[HOST]${"\x1b[22m"}${"\x1b[0m"}\t${"\x1b[4m"}${"\x1b[2m"}${"gitlab"}${"\x1b[24m"} | |
${"\x1b[0m"} | |
`.trim() + | |
"\n" | |
); | |
} | |
function parseAndExit(args) { | |
const [url, origin] = processInput(args) || []; | |
if ( | |
typeof url === "string" && | |
url.length && | |
typeof origin === "string" && | |
origin?.length | |
) { | |
if (process.stdout.isTTY) { | |
// Clear screen. | |
process.stdout.write("\033c"); | |
} | |
process.stdout.write(parseGhUrl(url, origin)); | |
process.stdout.write("\n"); | |
process.exit(0); | |
return; | |
} | |
if (process.stdout.isTTY) { | |
process.stdout.write("\n"); | |
process.stdout.moveCursor(1, -3); | |
process.stdout.write( | |
`${"\x1b[31m"}` + " [ ERROR ]" + `${"\x1b[0m"}\t Invalid URL\n\n` | |
); | |
process.stdout.write("\n"); | |
} | |
process.exit(1); | |
} | |
function main() { | |
const argv = [...(process?.argv || [])].slice(2); | |
if (argv.length) { | |
parseAndExit(argv); | |
} else { | |
readStdinPiped().then((input) => { | |
const delimeter = " "; | |
const fauxArgs = (input || "").trim().split(delimeter); | |
parseAndExit(fauxArgs); | |
}); | |
} | |
} | |
// @see https://nodejs.org/docs/latest/api/packages.html#determining-module-system | |
if (typeof require !== "undefined" && require.main === module) { | |
main(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const TEST = {}; | |
TEST.cases = [ | |
"greensock/GSAP", | |
"greensock/GSAP", | |
"github.com/greensock/GSAP", | |
"https://github.com/greensock/GSAP", | |
"https://github.com/greensock/GSAP.git", | |
"https://github.com/greensock/GSAP/blob/master/src/Flip.js", | |
"https://github.com/greensock/GSAP.git/blob/master/src/Flip.js" | |
]; | |
TEST.expected = "greensock/GSAP"; | |
const testResults = {}; | |
TEST.cases.forEach((input) => { | |
const output = parseGhUrl(input); | |
testResults[input] = output; | |
}); | |
const testResultsFormatted = Object.keys(testResults).map((input) => { | |
const output = testResults[input]; | |
const outcome = output === TEST.expected ? "PASS" : "FAIL"; | |
return { input, output, outcome }; | |
}); | |
console.table(testResultsFormatted); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment