Skip to content

Commit

Permalink
homebrew info
Browse files Browse the repository at this point in the history
  • Loading branch information
puffyCid committed Nov 18, 2023
1 parent 2f7581c commit aefa8d6
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 10 deletions.
29 changes: 29 additions & 0 deletions artemis-docs/docs/API/Artifacts/macos.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,32 @@ details on Dock Tile PlugIns

Includes embedded Apps, Frameworks, and any file that ends with
`%/Contents/Info.plist`

### getPackages(glob_path) -> `HomebrewReceipt[]`

Get Homebrew packages on the system. Does **not** include Casks.\
Use getHomebrewInfo() to get all packages and Casks.

By default this function will search for all packages at:
`/opt/homebrew/Cellar/` and `/usr/local/Cellar`

| Param | Type | Description |
| --------- | -------- | ------------------------------------- |
| glob_path | `string` | Optional alternative glob path to use |

### getCasks(glob_path) -> `HomebrewFormula[]`

Get Homebrew Casks on the system. Does **not** include packages.\
Use getHomebrewInfo() to get all packages and Casks.

By default this function will search for all packages at:
`/opt/homebrew/Caskroom` and `/usr/local/Caskroom`

| Param | Type | Description |
| --------- | -------- | ------------------------------------- |
| glob_path | `string` | Optional alternative glob path to use |

### getHomebrewInfo() -> `HomebrewData`

Get Homebrew packages and Casks on the system. Searches for Homebrew data at
`/opt/homebrew` and `/usr/local`.
226 changes: 226 additions & 0 deletions src/macos/homebrew.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import {
HomebrewData,
HomebrewFormula,
HomebrewReceipt,
} from "../../types/macos/homebrew.ts";
import { FileError } from "../filesystem/errors.ts";
import { glob, readTextFile } from "../filesystem/files.ts";

/**
* Function to get Homebrew info on installed packages and Casks
* @returns `HomebrewData` which includes data on Homebrew packages and Casks
*/
export function getHomebrewInfo(): HomebrewData {
const packages = getPackages();
const casks = getCasks();

const info: HomebrewData = {
packages,
casks,
};

return info;
}

/**
* Function to get installed Homebrew packages. Does **not** include Casks.
* Use `getHomebrewInfo()` to get packages and Casks
* @param glob_path Optional glob to use for Homebrew data
* @returns Array of `HomebrewReceipt`
*/
export function getPackages(glob_path?: string): HomebrewReceipt[] {
// Default paths to glob if we are not provided a path
let paths = [
"/opt/homebrew/Cellar/*/*/INSTALL_RECEIPT.json",
"/usr/local/Cellar/*/*/INSTALL_RECEIPT.json",
];

// Use user provided glob path
if (glob_path != undefined) {
paths = [glob_path];
}
const brew_receipts: HomebrewReceipt[] = [];

// Loop through all paths
for (const path of paths) {
const globs = glob(path);
if (globs instanceof FileError) {
console.warn(`failed to glob ${path}: ${globs}`);
continue;
}

for (const entry of globs) {
const brew_info: HomebrewReceipt = {
installedAsDependency: false,
installedOnRequest: false,
installTime: 0,
sourceModified: 0,
version: "",
name: "",
description: "",
homepage: "",
url: "",
license: "",
caskName: "",
formulaPath: "",
};
// Get base path for ruby formulas
const dirs = entry.full_path.split("/");
dirs.pop();
dirs.push(".brew/*.rb");

const rb_glob = dirs.join("/");
// Now glob for ruby files
const rb_path = glob(rb_glob);
if (rb_path instanceof FileError) {
continue;
}

// Loop through the ruby file(s)
for (const rb of rb_path) {
if (!rb.is_file) {
continue;
}
const formula = parseRuby(rb.full_path);
if (formula instanceof FileError) {
console.warn(
`failed to parse ruby formula ${entry.full_path}: ${formula}`,
);
continue;
}

brew_info.description = formula.description;
brew_info.homepage = formula.homepage;
brew_info.url = formula.url;
brew_info.license = formula.license;
brew_info.caskName = formula.caskName;
brew_info.formulaPath = formula.formulaPath;
brew_info.version = formula.version;
brew_info.name = rb.filename.substring(0, rb.filename.length - 3);

// Parse the INSTALL_RECEIPT.json now
const receipt = readTextFile(entry.full_path);
if (receipt instanceof FileError) {
console.warn(
`failed to read install_receipt.json formata ${entry.full_path}: ${receipt}`,
);
continue;
}
const receipt_data = JSON.parse(receipt);

brew_info.installTime = receipt_data["time"];
brew_info.installedAsDependency =
receipt_data["installed_as_dependency"];
brew_info.installedOnRequest = receipt_data["installed_on_request"];
brew_info.sourceModified = receipt_data["source_modified_time"];
}

brew_receipts.push(brew_info);
}
}
return brew_receipts;
}

/**
* Function to get Homebrew Casks. Does **not** include packages
* Use `getHomebrewInfo()` to get packages and Casks
* @param glob_path Optional glob to use for Homebrew Casks
* @returns Array of `HomebrewFormula`
*/
export function getCasks(glob_path?: string): HomebrewFormula[] {
let paths = [
"/usr/local/Caskroom/*/.metadata/*/*/Casks/*.rb",
"/opt/homebrew/Caskroom/*/.metadata/*/*/Casks/*.rb",
];

if (glob_path != undefined) {
paths = [glob_path];
}

const casks: HomebrewFormula[] = [];

for (const path of paths) {
const globs = glob(path);
if (globs instanceof FileError) {
console.warn(`failed to glob ${path}: ${globs}`);
continue;
}

for (const entry of globs) {
if (!entry.filename.endsWith(".rb")) {
continue;
}

const formula = parseRuby(entry.full_path);
if (formula instanceof FileError) {
console.warn(
`failed to parse ruby formula ${entry.full_path}: ${formula}`,
);
continue;
}
casks.push(formula);
}
}
return casks;
}

/**
* Function to parse the Ruby formula associated with Hoembrew package
* @param path Path to the Ruby file to parse
* @returns `HomebrewFormula` or FileError
*/
function parseRuby(path: string): HomebrewFormula | FileError {
const desc = /(?<=desc ).*$/m;
const homepage_reg = /(?<=homepage ).*$/m;
const reg_license = /(?<=license ).*$/m;
const reg_url = /(?<=url ).*$/m;
const reg_name = /(?<=name ).*$/m;
const reg_version = /(?<=version ).*$/m;

const rubyText = readTextFile(path);

if (rubyText instanceof FileError) {
return rubyText;
}

const descriptoin = rubyText.match(desc);
const receipt: HomebrewFormula = {
description: "",
homepage: "",
url: "",
license: "",
caskName: "",
formulaPath: path,
version: "",
};
if (typeof descriptoin?.[0] === "string") {
receipt.description = descriptoin?.[0].replaceAll('"', "");
}

const homepage = rubyText.match(homepage_reg);
if (typeof homepage?.[0] === "string") {
receipt.homepage = homepage?.[0].replaceAll('"', "");
}

const license = rubyText.match(reg_license);
if (typeof license?.[0] === "string") {
receipt.license = license?.[0].replaceAll('"', "");
}

const url = rubyText.match(reg_url);
if (typeof url?.[0] === "string") {
receipt.url = url?.[0].replaceAll('"', "");
}

const name = rubyText.match(reg_name);
if (typeof name?.[0] === "string") {
receipt.caskName = name?.[0].replaceAll('"', "");
}

const version = rubyText.match(reg_version);
if (typeof version?.[0] === "string") {
receipt.version = version?.[0].replaceAll('"', "");
}

return receipt;
}
10 changes: 5 additions & 5 deletions src/macos/firewall.ts → src/macos/plist/firewall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {
FirewallApplication,
FirewallExceptions,
Services,
} from "../../types/macos/firewall.d.ts";
import { parseAlias } from "./alias.ts";
import { parseRequirementBlob } from "./codesigning/blob.ts";
import { MacosError } from "./errors.ts";
import { getPlist } from "./plist.ts";
} from "../../../types/macos/plist/firewall.d.ts";
import { parseAlias } from "../alias.ts";
import { parseRequirementBlob } from "../codesigning/blob.ts";
import { MacosError } from "../errors.ts";
import { getPlist } from "../plist.ts";

/**
* Function to get the macOS Firewall status and metadata
Expand Down
6 changes: 3 additions & 3 deletions src/macos/policies.ts → src/macos/plist/policies.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PasswordPolicy } from "../../types/macos/policies.d.ts";
import { MacosError } from "./errors.ts";
import { getPlist } from "./plist.ts";
import { PasswordPolicy } from "../../../types/macos/plist/policies.d.ts";
import { MacosError } from "../errors.ts";
import { getPlist } from "../plist.ts";

/**
* Get Password Policies on macOS. Will parse plist file at `/var/db/dslocal/nodes/Default/config/shadowhash.plist`
Expand Down
22 changes: 22 additions & 0 deletions types/macos/homebrew.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface HomebrewReceipt extends HomebrewFormula {
installedAsDependency: boolean;
installedOnRequest: boolean;
installTime: number;
sourceModified: number;
name: string;
}

export interface HomebrewFormula {
description: string;
homepage: string;
url: string;
license: string;
caskName: string;
formulaPath: string;
version: string;
}

export interface HomebrewData {
packages: HomebrewReceipt[];
casks: HomebrewFormula[];
}
4 changes: 2 additions & 2 deletions types/macos/firewall.d.ts → types/macos/plist/firewall.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Alias } from "./alias.d.ts";
import { SingleRequirement } from "./codesigning.d.ts";
import { Alias } from "../alias.d.ts";
import { SingleRequirement } from "../codesigning.d.ts";

/**
* Represents the current status of the macOS Firewall
Expand Down
File renamed without changes.

0 comments on commit aefa8d6

Please sign in to comment.