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

refactor: extract table formatting #103

Merged
merged 3 commits into from
Jan 14, 2024
Merged
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
59 changes: 14 additions & 45 deletions src/cmd-search.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import npmSearch, { Options } from "libnpmsearch";
import npmFetch from "npm-registry-fetch";
import Table from "cli-table";
import log from "./logger";
import { is404Error, isHttpError } from "./utils/error-type-guards";
import * as os from "os";
import assert from "assert";
import { tryGetLatestVersion, UnityPackument } from "./types/packument";
import { UnityPackument } from "./types/packument";
import { parseEnv } from "./utils/env";
import { DomainName } from "./types/domain-name";
import { SemanticVersion } from "./types/semantic-version";
import { CmdOptions } from "./types/options";
import { Registry } from "./registry-client";

type DateString = string;

type TableRow = [DomainName, SemanticVersion, DateString, ""];
import { formatAsTable } from "./output-formatting";

type SearchResultCode = 0 | 1;

Expand Down Expand Up @@ -45,14 +40,14 @@ const getNpmFetchOptions = function (registry: Registry): Options {
const searchEndpoint = async function (
registry: Registry,
keyword: string
): Promise<TableRow[] | undefined> {
): Promise<SearchedPackument[] | undefined> {
try {
// NOTE: The results of the search will be Packument objects so we can change the type
const results = <SearchedPackument[]>(
await npmSearch(keyword, getNpmFetchOptions(registry))
);
log.verbose("npmsearch", results.join(os.EOL));
return results.map(getTableRow);
return results;
} catch (err) {
if (isHttpError(err) && !is404Error(err)) {
log.error("", err.message);
Expand All @@ -64,32 +59,28 @@ const searchEndpoint = async function (
const searchOld = async function (
registry: Registry,
keyword: string
): Promise<TableRow[] | undefined> {
): Promise<SearchedPackument[] | undefined> {
// all endpoint
try {
const results = <OldSearchResult | undefined>(
await npmFetch.json("/-/all", getNpmFetchOptions(registry))
);
let objects: SearchedPackument[] = [];
let packuments: SearchedPackument[] = [];
if (results) {
if (Array.isArray(results)) {
// results is an array of objects
objects = results;
packuments = results;
} else {
// results is an object
if ("_updated" in results) delete results["_updated"];
objects = Object.values(results);
packuments = Object.values(results);
}
}
log.verbose("endpoint.all", objects.join(os.EOL));
// prepare rows
const rows = objects.map((packument) => {
return getTableRow(packument);
});
log.verbose("endpoint.all", packuments.join(os.EOL));
// filter keyword
const klc = keyword.toLowerCase();
return rows.filter(
(row) => row.filter((x) => x.toLowerCase().includes(klc)).length > 0
return packuments.filter((packument) =>
packument.name.toLowerCase().includes(klc)
);
} catch (err) {
if (isHttpError(err) && !is404Error(err)) {
Expand All @@ -99,26 +90,6 @@ const searchOld = async function (
}
};

const getTable = function () {
return new Table({
head: ["Name", "Version", "Date"],
colWidths: [42, 20, 12],
});
};

const getTableRow = function (packument: SearchedPackument): TableRow {
const name = packument.name;
const version = tryGetLatestVersion(packument);
let date = "";
if (packument.time && packument.time.modified)
date = packument.time.modified.split("T")[0]!;
if (packument.date) {
date = packument.date.toISOString().slice(0, 10);
}
assert(version !== undefined);
return [name, version, date, ""];
};

export async function search(
keyword: string,
options: SearchOptions
Expand All @@ -127,17 +98,15 @@ export async function search(
const env = await parseEnv(options, false);
if (env === null) return 1;

const table = getTable();
// search endpoint
let results = await searchEndpoint(env.registry, keyword);
// search old search
if (results === undefined) {
results = (await searchOld(env.registry, keyword)) || [];
results = await searchOld(env.registry, keyword);
}
// search upstream
if (results && results.length) {
results.forEach((x) => table.push(x.slice(0, -1)));
console.log(table.toString());
if (results !== undefined && results.length > 0) {
console.log(formatAsTable(results));
} else log.notice("", `No matches found for "${keyword}"`);
return 0;
}
45 changes: 45 additions & 0 deletions src/output-formatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Table from "cli-table";
import {
tryGetLatestVersion,
UnityPackument,
VersionedPackument,
} from "./types/packument";
import assert from "assert";

/**
* A type describing the minimum required properties of a packument
* for being able to be formatted as a table.
*/
type TableablePackument = Pick<UnityPackument, "name" | "time" | "date"> &
VersionedPackument;

/**
* Formats an array of packuments as a table. The table is returned
* as a single string (Includes line breaks).
* @param packuments The packuments
*/
export function formatAsTable(packuments: TableablePackument[]): string {
const table = new Table({
head: ["Name", "Version", "Date"],
colWidths: [42, 20, 12],
});

function getTableRow(
packument: TableablePackument
): [string, string, string] {
const name = packument.name;
const version = tryGetLatestVersion(packument);
let date = "";
if (packument.time && packument.time.modified)
date = packument.time.modified.split("T")[0]!;
if (packument.date) {
date = packument.date.toISOString().slice(0, 10);
}
assert(version !== undefined);
return [name, version, date];
}

const rows = packuments.map(getTableRow);
rows.forEach((row) => table.push(row));
return table.toString();
}
19 changes: 12 additions & 7 deletions src/types/packument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ export type UnityPackument = {
users?: Record<string, unknown>;
};

const hasLatestDistTag = (
packument: Partial<UnityPackument>
): packument is Partial<UnityPackument> & {
/**
* The minimum properties a Packument must have in order for it's version
* to be determined.
*/
export type VersionedPackument = Pick<UnityPackument, "dist-tags" | "version">;

const hasLatestDistTag = <T extends VersionedPackument>(
packument: T
): packument is T & {
"dist-tags": { latest: SemanticVersion };
} => {
return packument["dist-tags"]?.["latest"] !== undefined;
Expand All @@ -84,10 +90,9 @@ const hasLatestDistTag = (
* Attempt to get the latest version from a package
* @param packument The package. All properties are assumed to be potentially missing
*/
export const tryGetLatestVersion = function (packument: {
"dist-tags"?: { latest?: SemanticVersion };
version?: SemanticVersion;
}): SemanticVersion | undefined {
export const tryGetLatestVersion = function (
packument: VersionedPackument
): SemanticVersion | undefined {
if (hasLatestDistTag(packument)) return packument["dist-tags"].latest;
else if (packument.version) return packument.version;
};