Compare commits
No commits in common. "3a5f070efadb9f8203bcd16c5f0feed66227956d" and "2559fb1eb2116864274598b256794cb6fd9d6c63" have entirely different histories.
3a5f070efa
...
2559fb1eb2
341
dotman.py
341
dotman.py
@ -1,11 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Yiğid BALABAN <fyb@fybx.dev>, 2024
|
# Ferit Yiğit BALABAN <fyb@fybx.dev>, 2024
|
||||||
#
|
#
|
||||||
|
|
||||||
# dotman
|
# Description
|
||||||
#
|
|
||||||
# [S] Description
|
|
||||||
# dotman is a simple dotfiles manager that can be used to backup and deploy dotfiles.
|
# dotman is a simple dotfiles manager that can be used to backup and deploy dotfiles.
|
||||||
#
|
#
|
||||||
# It manages a git repository to deploy a list of files and directories to
|
# It manages a git repository to deploy a list of files and directories to
|
||||||
@ -21,207 +19,177 @@
|
|||||||
# a tag name. The tag is created after the files and directories are copied,
|
# a tag name. The tag is created after the files and directories are copied,
|
||||||
# essentially creating a snapshot of the configuration at that time.
|
# essentially creating a snapshot of the configuration at that time.
|
||||||
|
|
||||||
# [S] Details
|
# Details
|
||||||
# * The configuration file for dotman is locatd in $HOME/.config/dotman/config
|
# * The configuration file for dotman is searched in $HOME/dotman/config
|
||||||
# * The deploy list for selecting what and what not $HOME/.config/dotman/deploy_list
|
# * The repository managed by dotman is searched in $HOME/dotman/managed_repo
|
||||||
# to backup/deploy is searched in
|
# * The deploy list for selecting what and what not
|
||||||
# * The repository managed by dotman is located in $HOME/.local/state/dotman/managed_repo
|
# to backup/deploy is searched in $HOME/dotman/deploy_list
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import tomllib
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from git.repo import Repo
|
from git.repo import Repo
|
||||||
from crispy.crispy import Crispy
|
from crispy.crispy import Crispy
|
||||||
from tomlkit import dumps as toml_dumps
|
|
||||||
from tomlkit import parse as toml_parse
|
|
||||||
from tomlkit.items import String as toml_String
|
|
||||||
from tomlkit.items import Item as toml_Item
|
|
||||||
from os.path import join, relpath
|
|
||||||
|
|
||||||
VER = 'v2'
|
|
||||||
|
VER = 'v1.8'
|
||||||
help_message = f'''
|
help_message = f'''
|
||||||
dotman {VER} dotfiles/home backup helper by yigid balaban
|
dotman {VER} dotfiles manager by ferityigitbalaban
|
||||||
|
|
||||||
Commands:
|
Unrecognized keys are ignored. If every key supplied is unrecognized,
|
||||||
=> init Initialize dotman installation.
|
this have the same effect as calling dotman without any key.
|
||||||
-u, --url [required] the URL of remote
|
|
||||||
-l, --local [optional] create repository locally
|
|
||||||
-d, --deploy [optional] deploy configuration
|
|
||||||
|
|
||||||
=> backup Backups configuration to managed_repo (pushes to remote)
|
Keys:
|
||||||
=> deploy Deploys configuration in place from managed_repo (pulls remote)
|
-b, --backup Backup your dotfiles. Doesn't require user assistance but errors may occur.
|
||||||
=> help Prints this message.
|
-d, --deploy Deploy your dotfiles. Doesn't require user assistance but errors may occur.
|
||||||
=> version Prints the version.
|
-v, --version Shows the version and quits
|
||||||
|
-h, --help Shows this message and quits
|
||||||
'''
|
'''
|
||||||
|
|
||||||
INITIALIZED = False
|
|
||||||
dir_home = os.path.expandvars('$HOME')
|
dir_home = os.path.expandvars('$HOME')
|
||||||
dir_config = join(dir_home, '.config')
|
dir_config = os.path.join(dir_home, '.config')
|
||||||
dir_state = join(dir_home, '.local', 'state')
|
|
||||||
params = {}
|
params = {}
|
||||||
|
list_ignore = []
|
||||||
|
list_deploy = []
|
||||||
|
|
||||||
|
|
||||||
def u_path(mode, path):
|
def util_get_all_files(directory: str) -> list[str]:
|
||||||
if mode == 'deploy':
|
|
||||||
return join(params['managed_repo'], relpath(path, dir_home))
|
|
||||||
elif mode == 'backup':
|
|
||||||
return join(params['managed_repo'], relpath(path, dir_home))
|
|
||||||
else:
|
|
||||||
raise ValueError(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def u_get_files(directory: str) -> list[str]:
|
|
||||||
if not os.path.exists(directory):
|
if not os.path.exists(directory):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
files = []
|
files = []
|
||||||
for root, _, filenames in os.walk(directory):
|
for root, _, filenames in os.walk(directory):
|
||||||
files.extend(join(root, filename) for filename in filenames)
|
files.extend(os.path.join(root, filename) for filename in filenames)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
def t_init():
|
def util_errout(msg: str, code: int):
|
||||||
global INITIALIZED
|
print(msg)
|
||||||
if INITIALIZED:
|
sys.exit(code)
|
||||||
return
|
|
||||||
INITIALIZED = True
|
|
||||||
|
|
||||||
|
|
||||||
|
def task_init():
|
||||||
global params
|
global params
|
||||||
params = {
|
params = {
|
||||||
'managed_repo': f'{dir_state}/dotman/managed_repo',
|
'managed_repo': f'{dir_config}/dotman/managed_repo',
|
||||||
'deploy_list': f'{dir_config}/dotman/deploy_list',
|
'deploy_list': f'{dir_config}/dotman/deploy_list',
|
||||||
'config_file': f'{dir_config}/dotman/config',
|
'config_file': f'{dir_config}/dotman/config',
|
||||||
'repo_url': '',
|
'repo_url': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.path.exists(params['config_file']):
|
|
||||||
with open(params['config_file'], 'r') as f:
|
def task_config():
|
||||||
data = toml_parse(f.read())
|
"""Reads and parses the configuration file
|
||||||
params.update({k: str(v) if isinstance(v, (toml_String, toml_Item)) else v for k, v in data.items()})
|
"""
|
||||||
|
with open(params['config_file'], 'rb') as f:
|
||||||
|
conf = tomllib.load(f)
|
||||||
|
|
||||||
|
if 'repo_url' not in conf.keys():
|
||||||
|
util_errout(f'[ERR] expected "repo_url" in {params["config_file"]}', 1)
|
||||||
|
|
||||||
|
params['repo_url'] = conf['repo_url']
|
||||||
|
|
||||||
|
|
||||||
|
def task_repo():
|
||||||
|
is_repo = os.path.exists(f"{params['managed_repo']}/.git")
|
||||||
|
|
||||||
|
if not is_repo:
|
||||||
|
Repo.clone_from(params['repo_url'], params['managed_repo'])
|
||||||
else:
|
else:
|
||||||
with open(params['config_file'], 'w') as f:
|
repo = Repo(params['managed_repo'])
|
||||||
f.write(toml_dumps(params))
|
repo.remotes.origin.pull()
|
||||||
|
|
||||||
|
|
||||||
is_local_repo_created = lambda: os.path.exists(f"{params['managed_repo']}/.git")
|
def task_list():
|
||||||
|
with open(params['deploy_list'], 'r') as f:
|
||||||
|
|
||||||
def t_make_repo(from_url: str, local = False, check = True):
|
|
||||||
"""
|
|
||||||
Create the local repository either by cloning from a remote, or by initializing it.
|
|
||||||
|
|
||||||
:param str from_url: URL of the remote
|
|
||||||
:param bool local: Whether to create locally (default = False)
|
|
||||||
:param bool check: Whether to check if local repository exists (default = True)
|
|
||||||
"""
|
|
||||||
if check and is_local_repo_created():
|
|
||||||
print('[W] dotman: a managed repository was initialized. overriding contents')
|
|
||||||
if local:
|
|
||||||
r = Repo.init(params['managed_repo'])
|
|
||||||
r.create_remote('origin', url=from_url)
|
|
||||||
r.git.checkout('-b', 'main')
|
|
||||||
return
|
|
||||||
print(f'[I] dotman: cloning from remote {params['repo_url']}')
|
|
||||||
Repo.clone_from(from_url, params['managed_repo'])
|
|
||||||
|
|
||||||
|
|
||||||
def t_pull_repo(overwrite: bool):
|
|
||||||
try:
|
|
||||||
# clone the repo from remote if local doesn't exist
|
|
||||||
# or if we are allowed to overwrite existing local
|
|
||||||
p_local_exists = is_local_repo_created()
|
|
||||||
if not p_local_exists or overwrite:
|
|
||||||
if p_local_exists:
|
|
||||||
shutil.rmtree(params['managed_repo'])
|
|
||||||
t_make_repo(params['repo_url'], check=False)
|
|
||||||
else:
|
|
||||||
# repo exists and it's forbidden to overwrite
|
|
||||||
repo = Repo(params['managed_repo'])
|
|
||||||
repo.remotes.origin.pull()
|
|
||||||
except Exception as e:
|
|
||||||
print(f'[E] dotman: unhandled error in \'t_pull_repo\': {e}')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def t_set_params(param_key: str, param_value: str):
|
|
||||||
params[param_key] = param_value
|
|
||||||
with open(params['config_file'], 'w') as f:
|
|
||||||
f.write(toml_dumps(params))
|
|
||||||
|
|
||||||
|
|
||||||
def t_list(p_list: str) -> list[str]:
|
|
||||||
l_i, l_d = [], []
|
|
||||||
|
|
||||||
with open(p_list, 'r') as f:
|
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
lines = [l.strip() for l in lines]
|
lines = [l.strip() for l in lines]
|
||||||
lines = [l for l in lines if l]
|
lines = [l for l in lines if l]
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
ignore_it = False
|
ignore_it = False
|
||||||
|
|
||||||
if line.startswith('#'):
|
if line.startswith('#'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.startswith('!'):
|
if line.startswith('!'):
|
||||||
ignore_it = True
|
ignore_it = True
|
||||||
line = line.removeprefix('!')
|
line = line.removeprefix('!')
|
||||||
elif '.git' in line:
|
if line.startswith('%'):
|
||||||
ignore_it = True
|
line = os.path.join(dir_config, line.replace('%', ''))
|
||||||
|
else:
|
||||||
|
line = os.path.join(dir_home, line)
|
||||||
|
|
||||||
line = join(dir_config, line[1:]) if line.startswith('%') else join(dir_home, line)
|
|
||||||
|
|
||||||
if os.path.isfile(line):
|
if os.path.isfile(line):
|
||||||
if ignore_it: l_i.append(line)
|
if ignore_it: list_ignore.append(line)
|
||||||
else: l_d.append(line)
|
else: list_deploy.append(line)
|
||||||
else:
|
else:
|
||||||
if ignore_it: l_i.extend(u_get_files(line))
|
if ignore_it: list_ignore.extend(util_get_all_files(line))
|
||||||
else: l_d.extend(u_get_files(line))
|
else: list_deploy.extend(util_get_all_files(line))
|
||||||
|
|
||||||
for element in l_i:
|
for element in list_ignore:
|
||||||
if element in l_d:
|
if element in list_deploy:
|
||||||
l_d.remove(element)
|
list_deploy.remove(element)
|
||||||
|
|
||||||
with open(join(dir_state, 'dotman', 'dotman.log'), 'w') as f:
|
with open(f'{dir_home}/dotman.log', 'w') as f:
|
||||||
f.writelines(map(lambda x: x + '\n', l_d))
|
f.writelines(map(lambda x: x + '\n', list_deploy))
|
||||||
return l_d
|
|
||||||
|
|
||||||
|
|
||||||
def a_backup():
|
def backup(tag=''):
|
||||||
l_deploy = t_list(params['deploy_list'])
|
"""Copies files and directories denoted in deploy_list from their source to
|
||||||
|
managed_repo directory.
|
||||||
|
|
||||||
if len(l_deploy) == 0:
|
Args:
|
||||||
print('[W] dotman: deploy_list is not created or empty. nothing will be backed up.')
|
tag (str, optional): Git tag to publish for the commit. Defaults to ''.
|
||||||
return False
|
"""
|
||||||
|
for file in list_deploy:
|
||||||
for file in l_deploy:
|
file_in_repo = util_path('backup', file)
|
||||||
file_in_repo = u_path('backup', file)
|
print(file_in_repo)
|
||||||
os.makedirs(os.path.dirname(file_in_repo), exist_ok=True)
|
os.makedirs(os.path.dirname(file_in_repo), exist_ok=True)
|
||||||
shutil.copy(file, file_in_repo)
|
shutil.copy(file, file_in_repo)
|
||||||
|
|
||||||
repo = Repo(params['managed_repo'])
|
repo = Repo(params['managed_repo'])
|
||||||
repo.git.add(all=True)
|
repo.git.add(all=True)
|
||||||
|
repo.git.commit('-m', 'committed by dotman')
|
||||||
|
repo.remotes.origin.push()
|
||||||
|
|
||||||
if repo.index.diff(None) or repo.untracked_files:
|
if tag != '':
|
||||||
repo.git.commit('-m', 'committed by dotman')
|
if tag in map(lambda x: x.replace('refs/tags/', ''), repo.tags):
|
||||||
repo.remotes.origin.push('main')
|
return
|
||||||
return True
|
created_tag = repo.create_tag(tag)
|
||||||
|
repo.remotes.origin.push(created_tag.name)
|
||||||
|
|
||||||
|
|
||||||
def a_deploy(use_deploy_list_in_managed_repo = False):
|
def deploy(tag=''):
|
||||||
l_deploy = t_list(os.path.join(params['managed_repo'], 'dotman', 'deploy_list')) if use_deploy_list_in_managed_repo else t_list(params['deploy_list'])
|
"""Copies files and directories in managed Git repository to
|
||||||
|
local .config directory, if they are present in the deploy list.
|
||||||
|
|
||||||
if len(l_deploy) == 0:
|
Optinally, a tag can be specified to deploy a specific configuration.
|
||||||
print('[W] dotman: deploy_list is not created or empty. nothing will be deployed.')
|
|
||||||
return False
|
|
||||||
|
|
||||||
for file in l_deploy:
|
Args:
|
||||||
file_in_repo = u_path('deploy', file)
|
tag (str, optional): Git tag for a specific configuration. Defaults to ''.
|
||||||
|
"""
|
||||||
|
if tag != '':
|
||||||
|
repo = Repo(params['managed_repo'])
|
||||||
|
repo.git.checkout(tag)
|
||||||
|
task_list()
|
||||||
|
|
||||||
|
for file in list_deploy:
|
||||||
|
file_in_repo = util_path('deploy', file)
|
||||||
os.makedirs(os.path.dirname(file), exist_ok=True)
|
os.makedirs(os.path.dirname(file), exist_ok=True)
|
||||||
shutil.copy(file_in_repo, file)
|
shutil.copy(file_in_repo, file)
|
||||||
return True
|
|
||||||
|
|
||||||
|
def util_path(mode, path):
|
||||||
|
if mode == 'deploy':
|
||||||
|
return os.path.join(params['managed_repo'], os.path.relpath(path, dir_home))
|
||||||
|
elif mode == 'backup':
|
||||||
|
return os.path.join(params['managed_repo'], os.path.relpath(path, dir_home))
|
||||||
|
else:
|
||||||
|
raise ValueError(mode)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -229,80 +197,45 @@ def main():
|
|||||||
print(help_message)
|
print(help_message)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
t_init()
|
task_init()
|
||||||
c = Crispy()
|
task_config()
|
||||||
c.add_subcommand('init', 'Initialize dotman for use')
|
task_repo()
|
||||||
c.add_subcommand('config', 'Configure dotman')
|
task_list()
|
||||||
c.add_variable('url', str)
|
|
||||||
c.add_variable('local', bool)
|
|
||||||
c.add_variable('deploy', bool)
|
|
||||||
|
|
||||||
c.add_subcommand('deploy', 'Deploy a configuration to place')
|
c = Crispy()
|
||||||
c.add_subcommand('backup', 'Backup current state following a deploy list')
|
c.add_variable('backup', bool)
|
||||||
|
c.add_variable('deploy', bool)
|
||||||
c.add_variable('tag', str)
|
c.add_variable('tag', str)
|
||||||
|
|
||||||
subcommand, args = c.parse_arguments(sys.argv[1:])
|
args = c.parse_arguments(sys.argv[1:])[1]
|
||||||
|
|
||||||
match subcommand:
|
if args['backup'] and args['deploy']:
|
||||||
case 'init':
|
util_errout('[ERR] can\'t do both, sorry :(', 11)
|
||||||
if args['url'] and type(args['url']) == str:
|
elif args['backup']:
|
||||||
t_set_params('repo_url', str(args['url']))
|
backup(args['tag'] if 'tag' in args.keys() else '')
|
||||||
if not args['local']:
|
elif args['deploy']:
|
||||||
s_pull = t_pull_repo(overwrite=True)
|
deploy(args['tag'])
|
||||||
if args['deploy'] and s_pull:
|
|
||||||
a_deploy(True)
|
|
||||||
else:
|
|
||||||
t_make_repo(args['url'], True)
|
|
||||||
case 'config':
|
|
||||||
if args['url']: t_set_params('repo_url', str(args['url']))
|
|
||||||
case 'deploy':
|
|
||||||
if t_pull_repo(False):
|
|
||||||
a_deploy()
|
|
||||||
case 'backup':
|
|
||||||
a_backup()
|
|
||||||
case 'help':
|
|
||||||
print(help_message)
|
|
||||||
case 'version':
|
|
||||||
print(VER)
|
|
||||||
case _:
|
|
||||||
print(help_message)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
# Tasks
|
||||||
|
# 1. expand dir_config
|
||||||
|
# 2. read configuration file
|
||||||
|
# 3. check managed_repo status
|
||||||
|
# 1. create if necessary
|
||||||
|
# 2. or pull changes
|
||||||
|
# 4. read deploy_list
|
||||||
|
|
||||||
# def backup(tag=''):
|
# command is deploy (tag?)
|
||||||
# """Copies files and directories denoted in deploy_list from their source to
|
# 1. if tag is specified, checkout to that tag
|
||||||
# managed_repo directory.
|
# 2. copy files and directories in deploy_list (ask for using the same deploy_list) to local .config directory
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# tag (str, optional): Git tag to publish for the commit. Defaults to ''.
|
|
||||||
# """
|
|
||||||
# if tag != '':
|
|
||||||
# if tag in map(lambda x: x.replace('refs/tags/', ''), repo.tags):
|
|
||||||
# return
|
|
||||||
# created_tag = repo.create_tag(tag)
|
|
||||||
# repo.remotes.origin.push(created_tag.name)
|
|
||||||
|
|
||||||
|
|
||||||
# def deploy(tag=''):
|
|
||||||
# """Copies files and directories in managed Git repository to
|
|
||||||
# local .config directory, if they are present in the deploy list.
|
|
||||||
#
|
|
||||||
# Optinally, a tag can be specified to deploy a specific configuration.
|
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# tag (str, optional): Git tag for a specific configuration. Defaults to ''.
|
|
||||||
# """
|
|
||||||
# if tag != '':
|
|
||||||
# repo = Repo(params['managed_repo'])
|
|
||||||
# repo.git.checkout(tag)
|
|
||||||
# t_list()
|
|
||||||
#
|
|
||||||
# for file in list_deploy:
|
|
||||||
# file_in_repo = util_path('deploy', file)
|
|
||||||
# os.makedirs(os.path.dirname(file), exist_ok=True)
|
|
||||||
# shutil.copy(file_in_repo, file)
|
|
||||||
|
|
||||||
|
# command is backup (tag?)
|
||||||
|
# 1. copy using deploy_list from sources to repository directory
|
||||||
|
# 2. create a new commit and push
|
||||||
|
# 3. if tag is specified, check if tag exists
|
||||||
|
# 1. if tag does not exist, create tag and push
|
||||||
|
# 2. if tag exists, warn and exit
|
||||||
|
|
||||||
|
BIN
nowplaying
Executable file
BIN
nowplaying
Executable file
Binary file not shown.
61
wireless.sh
61
wireless.sh
@ -1,45 +1,42 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Yigid BALABAN, <fyb@fybx.dev>
|
# Yigid BALABAN, <fyb@fybx.dev>
|
||||||
# wireless.sh
|
|
||||||
#
|
#
|
||||||
# description
|
|
||||||
# toggle wifi and bluetooth on or off quickly
|
|
||||||
|
|
||||||
control() {
|
control() {
|
||||||
local device="$1"
|
local device="$1"
|
||||||
local subcommand="$2"
|
local subcommand="$2"
|
||||||
|
|
||||||
case "$subcommand" in
|
case "$subcommand" in
|
||||||
off)
|
off)
|
||||||
rfkill block "$device" &&
|
rfkill block "$device" &&
|
||||||
case $device in
|
|
||||||
bluetooth) bluetoothctl power off ;;
|
|
||||||
wifi) nmcli radio wifi off ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
on)
|
|
||||||
|
|
||||||
rfkill unblock "$device" && sleep 1 &&
|
case $device in
|
||||||
case $device in
|
bluetooth) bluetoothctl power off ;;
|
||||||
bluetooth) bluetoothctl power on ;;
|
wifi) nmcli radio wifi off ;;
|
||||||
wifi) nmcli radio wifi on ;;
|
esac
|
||||||
esac
|
;;
|
||||||
;;
|
on)
|
||||||
*)
|
|
||||||
# shellcheck disable=SC2154
|
rfkill unblock "$device" && sleep 1 &&
|
||||||
echo "$command: subcommand '$subcommand' is not a valid argument." >&2
|
|
||||||
return 1
|
case $device in
|
||||||
;;
|
bluetooth) bluetoothctl power on ;;
|
||||||
esac
|
wifi) nmcli radio wifi on ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
echo "$command: subcommand '$subcommand' is not a valid argument." >&2
|
||||||
|
return 1
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $# -ne 2 ]]; then
|
if [[ $# -ne 2 ]]; then
|
||||||
echo "Usage: $0 <device> <subcommand>" >&2
|
echo "Usage: $0 <device> <subcommand>" >&2
|
||||||
echo "Valid devices: bluetooth, wifi" >&2
|
echo "Valid devices: bluetooth, wifi" >&2
|
||||||
echo "Valid subcommands: on, off" >&2
|
echo "Valid subcommands: on, off" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
control "$1" "$2" &
|
control "$1" "$2"
|
||||||
disown
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user