2 Commits
Author SHA1 Message Date
Seungjun Lee a959fdfead add tmux 2026-07-25 22:50:35 +09:00
Seungjun Lee e5ba30d4d1 format code & add todo 2026-07-25 22:50:05 +09:00
3 changed files with 117 additions and 82 deletions
+1
View File
@@ -2,3 +2,4 @@
Todo:
☐ Make dependency not hardcoded
☐ Pass depencency detect if package is in list
☐ add type in brewfile_tool.py
+1
View File
@@ -100,3 +100,4 @@ Microsoft excel,manual,,no,productivity,Microsoft excel,paid
Microsoft powerpoint,manual,,no,productivity,Microsoft powerpoint,paid
wget,formula,,yes,utilities,File downloader,free
mise,formula,,yes,development,Package version manager,free
tmux,formula,,yes,development,Terminal multiplexer,free
1 name type mas_id install_on_setup category description price
100 Microsoft powerpoint manual no productivity Microsoft powerpoint paid
101 wget formula yes utilities File downloader free
102 mise formula yes development Package version manager free
103 tmux formula yes development Terminal multiplexer free
+114 -81
View File
@@ -1,25 +1,47 @@
import csv
import subprocess
import shutil
import datetime
import sys
import re
import shutil
import subprocess
import sys
# ====== Config ======
CSV_FILE = './apps.csv'
BREWFILE = './Brewfile'
CSV_FILE = "./apps.csv"
BREWFILE = "./Brewfile"
SUPPORT_CONFIG = {
'formula': {'token': 'brew', 'structure': lambda data: f'brew "{data["name"]}"', 'dependency': [] },
'cask' : {'token': 'cask', 'structure': lambda data: f'cask "{data["name"]}"', 'dependency': [] },
'mas': {'token': 'mas', 'structure': lambda data: f'mas "{data["name"]}", id: {data["mas_id"]}', 'dependency': [{'name': 'mas', 'script': ['brew', 'install', 'mas']}] },
'vscode': {'token': 'vscode', 'structure': lambda data: f'vscode "{data["name"]}"', 'dependency': [{'name': 'code', 'script': ['brew', 'install', '--cask', 'visual-studio-code']}] }
"formula": {
"token": "brew",
"structure": lambda data: f'brew "{data["name"]}"',
"dependency": [],
},
"cask": {
"token": "cask",
"structure": lambda data: f'cask "{data["name"]}"',
"dependency": [],
},
"mas": {
"token": "mas",
"structure": lambda data: f'mas "{data["name"]}", id: {data["mas_id"]}',
"dependency": [{"name": "mas", "script": ["brew", "install", "mas"]}],
},
"vscode": {
"token": "vscode",
"structure": lambda data: f'vscode "{data["name"]}"',
"dependency": [
{
"name": "code",
"script": ["brew", "install", "--cask", "visual-studio-code"],
}
],
},
}
MANUAL_TYPE = 'manual'
MANUAL_TYPE = "manual"
support_types = list(SUPPORT_CONFIG.keys())
def clear():
subprocess.run(['clear'])
subprocess.run(["clear"])
def check_package(name):
@@ -28,124 +50,131 @@ def check_package(name):
return False
return True
def check_brew():
if not check_package('brew'):
print('❌ Unable to find brew')
if not check_package("brew"):
print("❌ Unable to find brew")
sys.exit()
def get_csv():
with open(CSV_FILE, 'r', encoding='utf-8-sig') as f:
with open(CSV_FILE, "r", encoding="utf-8-sig") as f:
return list(csv.DictReader(f))
def extract_tap(name):
parts = name.split('/')
parts = name.split("/")
if len(parts) == 3:
return f'{parts[0]}/{parts[1]}'
return f"{parts[0]}/{parts[1]}"
return None
def create_brewfile():
try:
with open(BREWFILE, 'r') as old:
stamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
with open(f'./Brewfile_{stamp}.backup', 'w') as new:
with open(BREWFILE, "r") as old:
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
with open(f"./Brewfile_{stamp}.backup", "w") as new:
new.writelines(old.readlines())
print(f'📢 Backup old Brewfile (Brewfile_{stamp}.backup)')
print(f"📢 Backup old Brewfile (Brewfile_{stamp}.backup)")
except FileNotFoundError:
pass
raw_list = get_csv()
temp = {pkg_type:[] for pkg_type in support_types}
temp = {pkg_type: [] for pkg_type in support_types}
taps = set()
for row in raw_list:
pkg_type = row['type']
pkg_type = row["type"]
if pkg_type in temp.keys():
if row['install_on_setup'] == 'yes':
tap = extract_tap(row['name'])
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'
content = f"# {row['description']}\n{SUPPORT_CONFIG[pkg_type]['structure'](row)}\n"
temp[pkg_type].append(content)
elif pkg_type != MANUAL_TYPE:
print(f'Error: {row["name"]} {pkg_type}')
print(f"Error: {row['name']} {pkg_type}")
sys.exit()
with open(BREWFILE, 'w') as f:
with open(BREWFILE, "w") as f:
if taps:
f.write(f'# {"=" * 15} tap {"=" * 15}\n')
f.write(f"# {'=' * 15} tap {'=' * 15}\n")
for tap in sorted(taps):
f.write(f'tap "{tap}"\n')
f.write('\n')
f.write("\n")
for pkg_type in support_types:
f.write(f'# {"=" * 15} {pkg_type} {"=" * 15}\n')
f.write(f"# {'=' * 15} {pkg_type} {'=' * 15}\n")
f.writelines(temp[pkg_type])
f.write('\n')
f.write("\n")
print("✅ Successfully created Brewfile")
print('✅ Successfully created Brewfile')
def handle_untrusted_taps():
taps = set()
try:
with open(BREWFILE, 'r') as f:
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)')
print("❌ Unable to find Brewfile (create it first)")
return False
if taps:
print('⚠️ These third-party taps are needed')
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.')
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)
subprocess.run(['brew', 'trust', tap], check=True)
subprocess.run(["brew", "tap", tap], check=True)
subprocess.run(["brew", "trust", 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')
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}
token_to_type = {SUPPORT_CONFIG[t]["token"]: t for t in support_types}
types = set()
try:
with open(BREWFILE, 'r') as f:
with open(BREWFILE, "r") as f:
for line in f:
token = line.strip().split(' ', 1)[0]
token = line.strip().split(" ", 1)[0]
if token in token_to_type:
types.add(token_to_type[token])
except FileNotFoundError:
print('❌ Unable to find Brewfile (create it first)')
print("❌ Unable to find Brewfile (create it first)")
return types
def install_dependencies():
dependencies = []
seen = set()
for pkg_type in get_brewfile_types():
for dep in SUPPORT_CONFIG[pkg_type]['dependency']:
if dep['name'] not in seen:
seen.add(dep['name'])
for dep in SUPPORT_CONFIG[pkg_type]["dependency"]:
if dep["name"] not in seen:
seen.add(dep["name"])
dependencies.append(dep)
for dep in dependencies:
if check_package(dep['name']):
if check_package(dep["name"]):
print(f'✅ Dependency "{dep["name"]}" already installed')
continue
print(f'📢 Installing missing dependency "{dep["name"]}"...')
try:
subprocess.run(dep['script'], check=True)
subprocess.run(dep["script"], check=True)
print(f'✅ Successfully installed dependency "{dep["name"]}"')
except subprocess.CalledProcessError as e:
print(f'❌ Failed to install dependency "{dep["name"]}" ({e.returncode})')
@@ -155,79 +184,82 @@ def install_dependencies():
return False
return True
def bundle_install():
if not handle_untrusted_taps():
return
if not install_dependencies():
print('❌ Aborting install due to missing dependency')
print("❌ Aborting install due to missing dependency")
return
try:
subprocess.run(['brew', 'bundle', 'install', f'--file={BREWFILE}'], check=True)
print('✅ Successfully install all packages')
subprocess.run(["brew", "bundle", "install", f"--file={BREWFILE}"], check=True)
print("✅ Successfully install all packages")
except subprocess.CalledProcessError as e:
print(f'❌ Failed to install some packages ({e.returncode})')
print(f"❌ Failed to install some packages ({e.returncode})")
except FileNotFoundError:
print('❌ Unable to find brew')
print("❌ Unable to find brew")
def bundle_check():
try:
print('📢 Check script running')
print("📢 Check script running")
result = subprocess.run(
['brew', 'bundle', 'check', f'--file={BREWFILE}', '--verbose']
["brew", "bundle", "check", f"--file={BREWFILE}", "--verbose"]
)
if result.returncode == 0:
print('✅ All packages are installed')
print("✅ All packages are installed")
return True
else:
print('⚠️ There are uninstalled package(s)')
print("⚠️ There are uninstalled package(s)")
return False
except FileNotFoundError:
print('❌ Unable to find brew')
print("❌ Unable to find brew")
return False
def bundle_cleanup():
try:
subprocess.run(['brew', 'bundle', 'cleanup', f'--file={BREWFILE}'])
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')
subprocess.run(["brew", "bundle", "cleanup", f"--file={BREWFILE}"])
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:
print('📢 Canceled')
print("📢 Canceled")
except subprocess.CalledProcessError as e:
print(f'❌ Failed to cleanup ({e.returncode})')
print(f"❌ Failed to cleanup ({e.returncode})")
except FileNotFoundError:
print('❌ Unable to find brew')
print("❌ Unable to find brew")
def select_option():
options = [
{'text': 'Create Brewfile', 'func': create_brewfile},
{'text': 'Install packages', 'func': bundle_install},
{'text': 'Check packages', 'func': bundle_check},
{'text': 'Cleanup packages', 'func': bundle_cleanup},
{'text': 'Exit', 'func': sys.exit}
{"text": "Create Brewfile", "func": create_brewfile},
{"text": "Install packages", "func": bundle_install},
{"text": "Check packages", "func": bundle_check},
{"text": "Cleanup packages", "func": bundle_cleanup},
{"text": "Exit", "func": sys.exit},
]
while True:
print('Select option')
print("Select option")
for idx, row in enumerate(options, 1):
print(f' {idx}) {row["text"]}')
raw = input('option: ').strip()
print(f" {idx}) {row['text']}")
raw = input("option: ").strip()
try:
clear()
idx = int(raw) - 1
if 0 <= idx < len(options):
options[idx]['func']()
options[idx]["func"]()
print()
return
else:
print('⚠️ Write valid number\n')
print("⚠️ Write valid number\n")
except ValueError:
print('⚠️ Write valid number\n')
print("⚠️ Write valid number\n")
def main():
@@ -235,5 +267,6 @@ def main():
while True:
select_option()
if __name__ == '__main__':
if __name__ == "__main__":
main()