commit 903850faaa6a377ce75031b54171f7371bdd2ee3
parent f8765f7e6674b0046f27c9d540330dc4c5d7d688
Author: gearsix <gearsix@tuta.io>
Date: Thu, 22 Apr 2021 00:22:45 +0100
1/? total refactor to tidyup everything and make it more maintainable
Diffstat:
M | src/dotfm.py | | | 272 | ++++++++++++++++++++++++++++++++++++++----------------------------------------- |
1 file changed, 130 insertions(+), 142 deletions(-)
diff --git a/src/dotfm.py b/src/dotfm.py
@@ -1,12 +1,11 @@
#!/usr/bin/env python3
-#==========================
-# dotfm - dot file manager
-#==========================
+#=========================
+# dotfm - dotfile manager
+#=========================
# authors: gearsix
# created: 2020-01-15
-# updated: 2020-11-25
-# notes:
+# updated: 2021-04-22
#---------
# IMPORTS
@@ -21,16 +20,17 @@ import argparse
#---------
# GLOBALS
#---------
-NAME = os.path.basename(__file__) # program name
-HOME = os.getenv('HOME') # $HOME (where user's dotfiles are stored)
-USER = os.getenv('USER') # $USER calling dotfm
-ARGS = sys.argv # parsed arguments
-EDITOR = os.getenv('EDITOR') or 'nano' # text editor to modify dotfiles with
-VERSION = 'v2.1.2'
-DOTFM_CSV_FILE = '/home/{}/.local/share/dotfm/installed.csv'.format(USER)
-KNOWN_DOTFILES = [ # dotfiles that dotfm knows by default
+NAME = os.path.basename(__file__)
+HOME = os.getenv('HOME')
+USER = os.getenv('USER')
+ARGS = []
+EDITOR = os.getenv('EDITOR') or 'nano'
+VERSION = 'v2.2.0'
+INSTALLED = []
+INSTALLED_FILE = '/home/{}/.local/share/dotfm/installed.csv'.format(USER)
+KNOWN = [ # dotfiles that dotfm knows by default
# install location, aliases...
- [DOTFM_CSV_FILE, 'dotfm', 'dotfm.csv'],
+ [INSTALLED_FILE, 'dotfm'],
['{}/.bashrc'.format(HOME), '.bashrc', 'bashrc'],
['{}/.bash_profile'.format(HOME), '.bash_profile', 'bash_profile'],
['{}/.profile'.format(HOME), '.profile', 'profile'],
@@ -56,29 +56,31 @@ KNOWN_DOTFILES = [ # dotfiles that dotfm knows by default
['{}/.inputrc'.format(HOME), '.inputrc', 'inputrc'],
['{}/.sfeed'.format(HOME), '.sfeedrc', 'sfeedrc'],
]
-INSTALLED_DOTFILES = [] # appended to during dotfm_init
#-----------
# FUNCTIONS
#-----------
-def log_info(info):
+# utilities
+def debug(message):
+ if ARGS.debug == True:
+ print(message)
+
+def info(message):
if ARGS.quiet == False:
- LOGGER.info(info)
+ print(message)
+
+def warn(message):
+ input('{}, press enter to continue.'.format(message))
-def error_exit(message):
- LOGGER.error(message)
+def fatal(message):
+ print(message)
sys.exit()
-def parse_arguments():
- global ARGS
+# main
+def parseargs():
valid_commands = ['install', 'update', 'remove', 'edit', 'list']
-
- parser = argparse.ArgumentParser(
- description='a simple tool to help you manage your dot files.')
- parser.add_argument('cmd', metavar='COMMAND', choices=valid_commands,
- help='the dotfm COMMAND to execute: {}'.format(valid_commands))
- parser.add_argument('dotfile', metavar='DOTFILE', nargs=argparse.REMAINDER,
- help='the target dotfile to execute COMMAND on')
+ parser = argparse.ArgumentParser(description='a simple tool to help you manage your dotfile symlinks.')
+ # OPTIONS
parser.add_argument('-s', '--skip', action='store_true',
help='skip any user prompts and use default values where possible')
parser.add_argument('-d', '--debug', action='store_true',
@@ -86,89 +88,82 @@ def parse_arguments():
parser.add_argument('-v', '--version', action='version',
version='%(prog)s {}'.format(VERSION))
parser.add_argument('-q', '--quiet', action='store_true',
- help='tell dotfm to shutup')
- ARGS = parser.parse_args()
-
-def dotfm_init():
- """ If DOTFM_CSV_FILE does not exist, create it. If it does, load it's
- values into KNOWN_DOTFILES.
- Will prompt the user where they want the file to be created at,
- if that location does not match DOTFM_CSV_FILE, DOTFM_CSV_FILE
- will be a symbolic link to that location.
- """
- LOGGER.debug('loading dotfile locations...')
-
- if not os.path.exists(DOTFM_CSV_FILE):
- LOGGER.warning('{} not found'.format(DOTFM_CSV_FILE))
-
- # get location to create dotfm.csv at
- location = -1
- while location == -1:
- location = input('where would you like to store the ' \
- 'dotfm csv file (default: {})? '.format(DOTFM_CSV_FILE))
- # prompt user to overwrite existing dotfm.csv file
- if len(location) > 0 and os.path.exists(location):
- yn = ''
- while yn == '':
- yn = input('{} already exists, ' \
- 'overwrite (y/n)? '.format(location))
- if yn[0] == 'y':
- log_info('overwriting {}'.format(location))
- elif yn[0] == 'n':
- log_info('{} already exists, using default ' \
- 'location ({})'.format(DOTFILE_LOCATION))
- location = DOTFM_CSV_FILE
- else:
- yn = ''
- # use default location
- elif len(location) == 0:
- location = DOTFM_CSV_FILE
- # ask again
- else:
- location = -1
-
- # write dotfm dotfile to csv
- log_info('creating dotfm_csv_file at {}'.format(location))
- os.system('mkdir -p {}'.format(os.path.dirname(location)))
- dotfm_csv = open(location, "w")
- for i, dfl in enumerate(KNOWN_DOTFILES[0]):
- if i == 0:
- dotfm_csv.write(dfl)
- else:
- dotfm_csv.write(',{}'.format(dfl))
- dotfm_csv.write('\n')
- dotfm_csv.close()
-
- # create dotfm.csv symbolic link
- os.system('mkdir -p {}'.format(os.path.dirname(DOTFM_CSV_FILE)))
- if os.path.abspath(location) != DOTFM_CSV_FILE:
- os.system('ln -isv {} {}'.format(os.path.abspath(location),
- DOTFM_CSV_FILE))
-
- # append to INSTALLED_DOTFILES
- INSTALLED_DOTFILES.append(KNOWN_DOTFILES[0])
- else: # load existing values into INSTALLED_DOTFILES
- dotfm_csv = open(DOTFM_CSV_FILE, "r")
- dotfm_csv_reader = csv.reader(dotfm_csv)
- for dfl in dotfm_csv_reader:
- INSTALLED_DOTFILES.append(dfl)
- dotfm_csv.close()
-
+ help='mute dotfm info logs')
+ # POSITIONAL
+ parser.add_argument('cmd', metavar='COMMAND', choices=valid_commands,
+ help='the dotfm COMMAND to execute: {}'.format(valid_commands))
+ parser.add_argument('dotfile', metavar='DOTFILE', nargs=argparse.REMAINDER,
+ help='the target dotfile to execute COMMAND on')
+ return parser.parse_args()
+
+# main/init
+def init():
+ debug('init: loading dotfile locations')
+ if not os.path.exists(INSTALLED_FILE):
+ debug(INSTALLED_FILE, 'not found')
+ init_createcsv()
+ INSTALLED = init_loadcsv(INSTALLED_FILE)
+ INSTALLED = init_cleandups(INSTALLED, 0)
+
+def init_createcsv(default_location):
+ location = default_location
+ if ARGS.skip == False:
+ location = input('dotfm csv file location (default: {})? '.format(default_location))
+ if len(location) == 0:
+ location = default_location
+ if os.path.exists(location):
+ debug(location, 'already exists')
+ on = input('[o]verwrite or [u]se {}? '.format(location))
+ if len(on) > 0:
+ if on[0] == 'o': # create file at location & write KNOWN[0] to it
+ warn('overwriting {}, all existing data in this file will be lost')
+ os.system('mkdir -p', os.path.dirname(location))
+ dotfm_csv = open(location, "w")
+ for i, dfl in enumerate(KNOWN[0]):
+ dotfm_csv.write(dfl if i == 0 else ',{}'.format(dfl))
+ dotfm_csv.write('\n')
+ dotfm_csv.close()
+ elif on[0] == 'u':
+ debug('', location)
+ sys.exit()
+ # create default_location symlink
+ if os.path.abspath(location) != os.path.abspath(default_location):
+ debug('creating dotfm csv file symlink')
+ os.system('mkdir -p', os.path.dirname(default_location))
+ os.system('ln -isv', os.path.abspath(location), default_location)
+
+def init_loadcsv(location):
+ data = []
+ dotfm_csv = csv.reader(open(location, "r"))
+ for dfl in dotfm_csv:
+ data.append(dfl)
+ dotfm_csv.close()
+ return data
+
+def init_cleandups(data, id_index):
+ unique = []
+ for d in data:
+ for i, u in enumerate(unique):
+ if d[id_index] == u[id_index]:
+ unique[i] = d # assume later entry = more recent
+ return unique
+
+# main/install
def dotfm_install(dotfile_source):
- """ Check "KNOWN_DOTFILES" to see if an alias matches "dotfile" basename,
+ """ Check "KNOWN" to see if an alias matches "dotfile" basename,
if it does create a symbolic link from "dotfile" to the matching
- "KNOWN_DOTFILES" location (index 0).
+ "KNOWN" location (index 0).
- If file at matching "KNOWN_DOTFILES" location exists, prompt user to
+ If file at matching "KNOWN" location exists, prompt user to
overwrite it.
@param dotfile = filepath to the dotfile to install
"""
log_info('installing {}...'.format(dotfile_source))
- # check if dotfile matches an alias in KNOWN_DOTFILES
+ # check if dotfile matches an alias in KNOWN
found = -1
- for d, dfl in enumerate(KNOWN_DOTFILES):
+ for d, dfl in enumerate(KNOWN):
for a, alias in enumerate(dfl[1:]):
if os.path.basename(dotfile_source) == alias:
found = d
@@ -176,7 +171,7 @@ def dotfm_install(dotfile_source):
# prompt for location
dest = ''
if found != -1:
- dest = os.path.abspath(KNOWN_DOTFILES[found][0])
+ dest = os.path.abspath(KNOWN[found][0])
if len(dest) == 0 or ARGS.skip == False:
default = dest
dest = ''
@@ -189,7 +184,7 @@ def dotfm_install(dotfile_source):
# prompt for aliases
aliases = []
if found != -1:
- aliases = KNOWN_DOTFILES[found][1:]
+ aliases = KNOWN[found][1:]
if len(aliases) == 0 or ARGS.skip == False:
default = aliases
aliases = []
@@ -221,22 +216,23 @@ def dotfm_install(dotfile_source):
oca = ''
# create symbolic link to dotfile
os.system('ln -vs {} {}'.format(dotfile_source, dest))
- # append to DOTFILE_CSV_FILE and INSTALLED_DOTFILES
+ # append to DOTFILE_CSV_FILE and INSTALLED
log_info('appending to installed dotfiles...')
dfl = aliases
dfl.insert(0, dest)
- with open(DOTFM_CSV_FILE, "a") as dotfm_csv_file:
+ with open(INSTALLED_FILE, "a") as dotfm_csv_file:
dotfm_csv = csv.writer(dotfm_csv_file, lineterminator='\n')
dotfm_csv.writerow(dfl)
dotfm_csv_file.close()
- INSTALLED_DOTFILES.append(dfl)
+ INSTALLED.append(dfl)
log_info('success - you might need to re-open the terminal to see changes take effect')
+# main/update
def dotfm_update(dotfile_alias, new_source):
""" Update the source location that the dotfile symlink of an already
installed dotfile points to. If the dotfile_alias does not exist in
- DOTFM_CSV_FILE, dotfm_install is called instead.
+ INSTALLED_FILE, dotfm_install is called instead.
@param alias = an alias matching the known aliases of the dotfile to
update
@@ -246,7 +242,7 @@ def dotfm_update(dotfile_alias, new_source):
log_info('updating {} -> {}...'.format(dotfile_alias, new_source))
found = -1
- for i, dfl in enumerate(INSTALLED_DOTFILES):
+ for i, dfl in enumerate(INSTALLED):
if dotfile_alias in dfl:
found = i
break
@@ -261,9 +257,10 @@ def dotfm_update(dotfile_alias, new_source):
log_info('could not find dotfile matching alias "{}"'.format(
dotfile_alias))
+# main/remove
def dotfm_remove(alias):
""" Remove a dotfile (from it's known location) and remove it from
- DOTFM_CSV_FILE
+ INSTALLED_FILE
@param alias = an alias matching the known aliases of the dotfile to
remove
@@ -271,7 +268,7 @@ def dotfm_remove(alias):
log_info('removing {}...'.format(alias))
found = -1
- for i, dfl in enumerate(INSTALLED_DOTFILES):
+ for i, dfl in enumerate(INSTALLED):
if alias in dfl:
found = i
break
@@ -281,14 +278,15 @@ def dotfm_remove(alias):
target = '{}'.format(os.path.abspath(dfl[0]))
os.system('rm -iv {}'.format(target))
# remove from installed
- del INSTALLED_DOTFILES[found]
- with open(DOTFM_CSV_FILE, 'w') as dotfm_file:
+ del INSTALLED[found]
+ with open(INSTALLED_FILE, 'w') as dotfm_file:
dotfm_csv = csv.writer(dotfm_file)
- dotfm_csv.writerows(INSTALLED_DOTFILES)
+ dotfm_csv.writerows(INSTALLED)
dotfm_file.close()
else:
log_info('could not find dotfile matching alias "{}"'.format(alias))
+# main/edit
def dotfm_edit(dotfile_alias):
""" Open dotfile with alias matching "dotfm_alias" in EDITOR
@param dotfile_alias = an alias of the dotfile to open
@@ -296,7 +294,7 @@ def dotfm_edit(dotfile_alias):
log_info('editing {}...'.format(dotfile_alias))
target = ''
- for dfl in INSTALLED_DOTFILES:
+ for dfl in INSTALLED:
if dotfile_alias in dfl:
target = '{}'.format(dfl[0])
os.system('{} {}'.format(EDITOR, target))
@@ -317,6 +315,7 @@ def dotfm_edit(dotfile_alias):
error_exit('could not find alias {} in installed.csv'.format(
os.path.basename(dotfile_alias)))
+# main/list
def dotfm_list(dotfile_aliases):
""" List specified dotfile aliases and install location (displays all if
none are specified).
@@ -328,11 +327,11 @@ def dotfm_list(dotfile_aliases):
if len(dotfile_aliases) == 0:
os.system('printf "LOCATION,ALIASES...\n$(cat {})" | ' \
- 'column -t -s,'.format(DOTFM_CSV_FILE))
+ 'column -t -s,'.format(INSTALLED_FILE))
else:
dotfiles = ''
for alias in dotfile_aliases:
- for dfl in INSTALLED_DOTFILES:
+ for dfl in INSTALLED:
if alias in dfl:
dotfiles += dfl[0]
for i, a in enumerate(dfl[1:]):
@@ -346,35 +345,24 @@ def dotfm_list(dotfile_aliases):
# MAIN
#------
if __name__ == '__main__':
- # parse args
- parse_arguments()
- command = ARGS.cmd
- dotfile = ARGS.dotfile
-
- # init LOGGER
- log_lvl = logging.INFO
- log_fmt = '%(levelname)-7s | %(message)s'
+ ARGS = parseargs()
if ARGS.debug == True:
- LOGGER.debug('displaying debug logs')
- log_lvl = logging.DEBUG
- logging.basicConfig(level=log_lvl, format=log_fmt)
- LOGGER = logging.getLogger(__name__)
-
- # load dotfile locations
- dotfm_init()
+ debug('printing debug logs')
+ if ARGS.quiet == True:
+ debug('muting info logs')
- # run command
- if command == 'install':
- for d in dotfile:
+ init()
+ if ARGS.cmd == 'install':
+ for d in ARGS.dotfile:
dotfm_install(os.path.abspath(d))
- elif command == 'update':
- dotfm_update(dotfile[0], dotfile[1])
- elif command == 'remove':
- for d in dotfile:
+ elif ARGS.cmd == 'update':
+ dotfm_update(ARGS.dotfile[0], ARGS.dotfile[1])
+ elif ARGS.cmd == 'remove':
+ for d in ARGS.dotfile:
dotfm_remove(d)
- elif command == 'edit':
- for d in dotfile:
+ elif ARGS.cmd == 'edit':
+ for d in ARGS.dotfile:
dotfm_edit(d)
- elif command == 'list':
- dotfm_list(dotfile)
+ elif ARGS.cmd == 'list':
+ dotfm_list(ARGS.dotfile)