diff --git a/apps.csv b/apps.csv index f12f044..95ec156 100644 --- a/apps.csv +++ b/apps.csv @@ -51,7 +51,7 @@ fastfetch,formula,,yes,utilities,Neofetch alternative,free mas,formula,,yes,utilities,CLI appstore,free tree,formula,,yes,utilities,File structure tree,free scrcpy,formula,,no,utilities,Android screen mirroring,free -thaw,formula,,yes,personalization,Menubar hidder,free +thaw,cask,,yes,personalization,Menubar hidder,free nmap,formula,,no,network,Network scanner,free davinci resolve ,manual,,no,creativity,Video editor,free reef,manual,,no,personalization,"Shortcuts based app switcher @@ -91,4 +91,4 @@ ritwickdey.liveserver,vscode,,no,tool,Web frontend live server,free svelte.svelte-vscode,vscode,,yes,language,Sevelte integration,free wayou.vscode-todo-highlight,vscode,,yes,tool,Todo file highlighter,free yzhang.markdown-all-in-one,vscode,,yes,language,Markdown highlighter & formatter,free -zhuangtongfa.material-theme,vscode,,yes,theme,Best darkmode theme (One Dark Pro),free \ No newline at end of file +zhuangtongfa.material-theme,vscode,,yes,theme,Best darkmode theme (One Dark Pro),free diff --git a/brewfile_tool.py b/brewfile_tool.py index 3d81cf4..b5b449b 100644 --- a/brewfile_tool.py +++ b/brewfile_tool.py @@ -1,3 +1,10 @@ +import csv +import subprocess +import shutil +import datetime +import sys +import re + # ====== Config ====== CSV_FILE = './apps.csv' BREWFILE = './Brewfile' @@ -11,14 +18,9 @@ MANUAL_TYPE = 'manual' support_types = list(SUPPORT_CONFIG.keys()) +def clear(): + subprocess.run(['clear']) -import csv -import subprocess -import shutil -import datetime -import sys - -clear = lambda: subprocess.run(['clear']) def check_package(name): path = shutil.which(name) @@ -33,7 +35,13 @@ def check_brew(): def get_csv(): with open(CSV_FILE, 'r', encoding='utf-8-sig') as f: - return list(csv.DictReader(f)) + return list(csv.DictReader(f)) + +def extract_tap(name): + parts = name.split('/') + if len(parts) == 3: + return f'{parts[0]}/{parts[1]}' + return None def create_brewfile(): try: @@ -47,11 +55,15 @@ def create_brewfile(): raw_list = get_csv() temp = {pkg_type:[] for pkg_type in support_types} + taps = set() for row in raw_list: pkg_type = row['type'] if pkg_type in temp.keys(): if row['install_on_setup'] == 'yes': + tap = extract_tap(row['name']) + if tap: + taps.add(tap) content = f'# {row["description"]}\n{SUPPORT_CONFIG[pkg_type]["structure"](row)}\n' temp[pkg_type].append(content) elif pkg_type != MANUAL_TYPE: @@ -59,12 +71,50 @@ def create_brewfile(): sys.exit() with open(BREWFILE, 'w') as f: + if taps: + f.write(f'# {"=" * 15} tap {"=" * 15}\n') + for tap in sorted(taps): + f.write(f'tap "{tap}"\n') + f.write('\n') + for pkg_type in support_types: f.write(f'# {"=" * 15} {pkg_type} {"=" * 15}\n') f.writelines(temp[pkg_type]) f.write('\n') - - print('✅ Successfully create Brewfile') + + print('✅ Successfully created Brewfile') + +def handle_untrusted_taps(): + taps = set() + try: + with open(BREWFILE, 'r') as f: + for line in f: + m = re.fullmatch(r'tap "([^/]+/[^/]+)"', line.strip()) + if m: + taps.add(m.group(1)) + except FileNotFoundError: + print('❌ Unable to find Brewfile (create it first)') + return False + + if taps: + print('⚠️ These third-party taps are needed') + for tap in sorted(taps): + print(f' - {tap}') + print(' These taps is not official and my execute certain script.\n') + if input('Trust these taps? (y/N): ').strip().lower() !='y': + print('📢 Operation is canceled as these taps are not allowed.') + return False + for tap in sorted(taps): + try: + subprocess.run(['brew', 'tap', tap], check=True) + print(f'✅ Successfully installed tap "{tap}"') + except subprocess.CalledProcessError as e: + print(f'❌ Failed to install tap "{tap}" ({e.returncode})') + return False + except FileNotFoundError: + print('❌ Unable to find brew') + return False + return True def get_brewfile_types(): token_to_type = {SUPPORT_CONFIG[t]['token']: t for t in support_types} @@ -105,6 +155,9 @@ def install_dependencies(): return True def bundle_install(): + if not handle_untrusted_taps(): + return + if not install_dependencies(): print('❌ Aborting install due to missing dependency') return @@ -125,7 +178,7 @@ def bundle_check(): ['brew', 'bundle', 'check', f'--file={BREWFILE}', '--verbose'] ) if result.returncode == 0: - print('✅ Every packages are installed') + print('✅ All packages are installed') return True else: print('⚠️ There are uninstalled package(s)') @@ -133,13 +186,13 @@ def bundle_check(): except FileNotFoundError: print('❌ Unable to find brew') return False - + def bundle_cleanup(): try: subprocess.run(['brew', 'bundle', 'cleanup', f'--file={BREWFILE}']) - if input('⚠️ Are sure want to remove? (y/N): ').strip().lower() == 'y': + if input('⚠️ Are sure to remove? (y/N): ').strip().lower() == 'y': subprocess.run(['brew', 'bundle', 'cleanup', f'--file={BREWFILE}', '--force'], check=True) print('✅ Successfully cleanup') else: @@ -171,9 +224,9 @@ def select_option(): print() return else: - print(f'⚠️ Write valid number\n') - except ValueError as e: - print(f'⚠️ Write valid number\n') + print('⚠️ Write valid number\n') + except ValueError: + print('⚠️ Write valid number\n') def main(): @@ -182,4 +235,4 @@ def main(): select_option() if __name__ == '__main__': - main() \ No newline at end of file + main()