3 Commits
Author SHA1 Message Date
seung6lee fc398f7a2a add auto trust operation 2026-07-15 22:04:08 +09:00
seung6lee f9b31593c2 add automatic tap detection 2026-07-15 21:58:52 +09:00
seung6lee 585ecab616 add MIT license 2026-07-15 08:30:25 +09:00
3 changed files with 94 additions and 19 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2 -2
View File
@@ -51,7 +51,7 @@ fastfetch,formula,,yes,utilities,Neofetch alternative,free
mas,formula,,yes,utilities,CLI appstore,free mas,formula,,yes,utilities,CLI appstore,free
tree,formula,,yes,utilities,File structure tree,free tree,formula,,yes,utilities,File structure tree,free
scrcpy,formula,,no,utilities,Android screen mirroring,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 nmap,formula,,no,network,Network scanner,free
davinci resolve ,manual,,no,creativity,Video editor,free davinci resolve ,manual,,no,creativity,Video editor,free
reef,manual,,no,personalization,"Shortcuts based app switcher 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 svelte.svelte-vscode,vscode,,yes,language,Sevelte integration,free
wayou.vscode-todo-highlight,vscode,,yes,tool,Todo file highlighter,free wayou.vscode-todo-highlight,vscode,,yes,tool,Todo file highlighter,free
yzhang.markdown-all-in-one,vscode,,yes,language,Markdown highlighter & formatter,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 zhuangtongfa.material-theme,vscode,,yes,theme,Best darkmode theme (One Dark Pro),free
1 name type mas_id install_on_setup category description price
51 mas formula yes utilities CLI appstore free
52 tree formula yes utilities File structure tree free
53 scrcpy formula no utilities Android screen mirroring free
54 thaw formula cask yes personalization Menubar hidder free
55 nmap formula no network Network scanner free
56 davinci resolve manual no creativity Video editor free
57 reef manual no personalization Shortcuts based app switcher https://github.com/gouwsxander/Reef free
91 wayou.vscode-todo-highlight vscode yes tool Todo file highlighter free
92 yzhang.markdown-all-in-one vscode yes language Markdown highlighter & formatter free
93 zhuangtongfa.material-theme vscode yes theme Best darkmode theme (One Dark Pro) free
94
+71 -17
View File
@@ -1,3 +1,10 @@
import csv
import subprocess
import shutil
import datetime
import sys
import re
# ====== Config ====== # ====== Config ======
CSV_FILE = './apps.csv' CSV_FILE = './apps.csv'
BREWFILE = './Brewfile' BREWFILE = './Brewfile'
@@ -11,14 +18,9 @@ MANUAL_TYPE = 'manual'
support_types = list(SUPPORT_CONFIG.keys()) 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): def check_package(name):
path = shutil.which(name) path = shutil.which(name)
@@ -33,7 +35,13 @@ def check_brew():
def get_csv(): 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)) 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(): def create_brewfile():
try: try:
@@ -47,11 +55,15 @@ def create_brewfile():
raw_list = get_csv() 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: for row in raw_list:
pkg_type = row['type'] pkg_type = row['type']
if pkg_type in temp.keys(): if pkg_type in temp.keys():
if row['install_on_setup'] == 'yes': 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) temp[pkg_type].append(content)
elif pkg_type != MANUAL_TYPE: elif pkg_type != MANUAL_TYPE:
@@ -59,12 +71,51 @@ def create_brewfile():
sys.exit() sys.exit()
with open(BREWFILE, 'w') as f: 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: 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.writelines(temp[pkg_type])
f.write('\n') 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)
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')
return False
return True
def get_brewfile_types(): 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}
@@ -105,6 +156,9 @@ def install_dependencies():
return True return True
def bundle_install(): def bundle_install():
if not handle_untrusted_taps():
return
if not install_dependencies(): if not install_dependencies():
print('❌ Aborting install due to missing dependency') print('❌ Aborting install due to missing dependency')
return return
@@ -125,7 +179,7 @@ def bundle_check():
['brew', 'bundle', 'check', f'--file={BREWFILE}', '--verbose'] ['brew', 'bundle', 'check', f'--file={BREWFILE}', '--verbose']
) )
if result.returncode == 0: if result.returncode == 0:
print('Every packages are installed') print('All packages are installed')
return True return True
else: else:
print('⚠️ There are uninstalled package(s)') print('⚠️ There are uninstalled package(s)')
@@ -133,13 +187,13 @@ def bundle_check():
except FileNotFoundError: except FileNotFoundError:
print('❌ Unable to find brew') print('❌ Unable to find brew')
return False return False
def bundle_cleanup(): def bundle_cleanup():
try: try:
subprocess.run(['brew', 'bundle', 'cleanup', f'--file={BREWFILE}']) 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) subprocess.run(['brew', 'bundle', 'cleanup', f'--file={BREWFILE}', '--force'], check=True)
print('✅ Successfully cleanup') print('✅ Successfully cleanup')
else: else:
@@ -171,9 +225,9 @@ def select_option():
print() print()
return return
else: else:
print(f'⚠️ Write valid number\n') print('⚠️ Write valid number\n')
except ValueError as e: except ValueError:
print(f'⚠️ Write valid number\n') print('⚠️ Write valid number\n')
def main(): def main():
@@ -182,4 +236,4 @@ def main():
select_option() select_option()
if __name__ == '__main__': if __name__ == '__main__':
main() main()