Skip to content

Commit

Permalink
refactor: extract table formatting (#103)
Browse files Browse the repository at this point in the history
* refactor: extract table formatting function

Extract logic for formatting packument for viewing into separate function.

* refactor: extract helper type

Extract type that represents the version-part of a packument. Shorter than always rewriting the properties manually.

* refactor: extract module

Extract table formatting logic to own module. This way other output formatting functions can be grouped together an reused.
  • Loading branch information
ComradeVanti authored Jan 14, 2024
1 parent 6fe0806 commit 1a049cf
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 52 deletions.
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;
};

0 comments on commit 1a049cf

Please sign in to comment.