Skip to content

Instantly share code, notes, and snippets.

@ogra
Last active March 4, 2026 08:04
Show Gist options
  • Select an option

  • Save ogra/808c5e832f007cc8ad59ffd6185fcae6 to your computer and use it in GitHub Desktop.

Select an option

Save ogra/808c5e832f007cc8ad59ffd6185fcae6 to your computer and use it in GitHub Desktop.
Output Markdown text (name, url, description) from newly added Homebrew Formulae/Casks info.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# This script is designed to parse the output of `brew update` commands,
# extract relevant information about new formulae and casks, and then format that information
# for display. It uses regular expressions to identify and extract the name, version, URL,
# and description of each item. The script also includes debug statements to help trace the
# processing of the information. The final output is a formatted list of new formulae and casks
# with their details.
# usage (bash/zsh): brew update 2>&1 | (python3 brew-new-md.py)
# import fileinput
import pprint
import re
import subprocess
import sys
import tempfile
def get_item_info(name, type, info_text):
lines = info_text.splitlines()
version, url, description, description_label_match = "", "", "", None
if type == "formula":
description = lines[1].strip()
for line in lines:
line = line.strip()
if description_label_match:
description = line
description_label_match = None
continue
version_match = re.match(rf"^==> {re.escape(name)}.*:\s*([^\(\)]+)\(?.*$", line)
if version_match:
version = version_match.group(1).strip()
url_match = re.match(r"^(http(s?)://.*)$", line)
if url_match:
url = url_match.group(1)
description_label_match = re.match(r"^==> Description$", line)
# print(
# f"Debug: name={name}, type={type}, version={version}, url={url}, description={description}"
# )
return version, url, description
def main():
fp = tempfile.NamedTemporaryFile(mode="w+", delete=True)
formula, cask = False, False
items = []
# input_lines = fileinput.input()
input_lines = sys.stdin.read().splitlines()
for line in input_lines:
line = line.strip()
item = {}
if re.match(r"(^==> Outdated.*$)|(^Error:.*$)", line):
formula, cask = False, False
if formula or cask:
pattern_match1 = re.match(r"^([a-zA-Z0-9\-]+):?\s*(.*)$", line)
pattern_match2 = re.match(r"^([a-zA-Z0-9\-\/]+)@([\d\.]+)$", line)
if pattern_match2:
item["name"] = pattern_match2.group(1)
item["type"] = "formula" if formula else "cask"
item["version"] = pattern_match2.group(2)
items.append(item)
continue
if pattern_match1:
item["name"] = pattern_match1.group(1)
item["type"] = "formula" if formula else "cask"
if pattern_match1.group(2):
item["desc"] = pattern_match1.group(2)
items.append(item)
if re.match(r"^==> New Formulae$", line):
formula = True
cask = False
if re.match(r"^==> New Casks$", line):
formula = False
cask = True
for item in items:
info_text = None
if item["type"] == "formula":
info = subprocess.run(["brew", "info", item["name"]], capture_output=True)
elif item["type"] == "cask":
info = subprocess.run(
["brew", "info", "--cask", item["name"]], capture_output=True
)
if info.returncode == 0:
info_text = info.stdout.decode("utf-8")
print(f"Debug: info_text for {item['name']}:\n{info_text}\n")
item["info"] = info_text
if item["info"]:
item["version"], item["url"], item["description"] = get_item_info(
item["name"], item["type"], item["info"]
)
fp.write(pprint.pformat(items))
fp.seek(0)
# print(fp.read())
fp.close()
for item in items:
name = item.get("name", "")
url = item.get("url")
if url:
print(f"[{name}]({url})")
else:
print(name)
print(f"Type: {item.get('type', '')}")
print(f"Version: {item.get('version', '')}")
print(f"Description: {item.get('description', '')}")
print()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment