Compare commits

..

No commits in common. "3a5f070efadb9f8203bcd16c5f0feed66227956d" and "2559fb1eb2116864274598b256794cb6fd9d6c63" have entirely different histories.

3 changed files with 182 additions and 252 deletions

323
dotman.py
View File

@ -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:
data = toml_parse(f.read())
params.update({k: str(v) if isinstance(v, (toml_String, toml_Item)) else v for k, v in data.items()})
else:
with open(params['config_file'], 'w') as f:
f.write(toml_dumps(params))
def task_config():
is_local_repo_created = lambda: os.path.exists(f"{params['managed_repo']}/.git") """Reads and parses the configuration file
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. with open(params['config_file'], 'rb') as f:
conf = tomllib.load(f)
:param str from_url: URL of the remote if 'repo_url' not in conf.keys():
:param bool local: Whether to create locally (default = False) util_errout(f'[ERR] expected "repo_url" in {params["config_file"]}', 1)
:param bool check: Whether to check if local repository exists (default = True)
""" params['repo_url'] = conf['repo_url']
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): def task_repo():
try: is_repo = os.path.exists(f"{params['managed_repo']}/.git")
# clone the repo from remote if local doesn't exist
# or if we are allowed to overwrite existing local if not is_repo:
p_local_exists = is_local_repo_created() Repo.clone_from(params['repo_url'], params['managed_repo'])
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: else:
# repo exists and it's forbidden to overwrite
repo = Repo(params['managed_repo']) repo = Repo(params['managed_repo'])
repo.remotes.origin.pull() 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): def task_list():
params[param_key] = param_value with open(params['deploy_list'], 'r') as f:
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)
if repo.index.diff(None) or repo.untracked_files:
repo.git.commit('-m', 'committed by dotman') repo.git.commit('-m', 'committed by dotman')
repo.remotes.origin.push('main') repo.remotes.origin.push()
return True
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 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

Binary file not shown.

View File

@ -1,10 +1,7 @@
#!/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"
@ -13,6 +10,7 @@ control() {
case "$subcommand" in case "$subcommand" in
off) off)
rfkill block "$device" && rfkill block "$device" &&
case $device in case $device in
bluetooth) bluetoothctl power off ;; bluetooth) bluetoothctl power off ;;
wifi) nmcli radio wifi off ;; wifi) nmcli radio wifi off ;;
@ -21,6 +19,7 @@ control() {
on) on)
rfkill unblock "$device" && sleep 1 && rfkill unblock "$device" && sleep 1 &&
case $device in case $device in
bluetooth) bluetoothctl power on ;; bluetooth) bluetoothctl power on ;;
wifi) nmcli radio wifi on ;; wifi) nmcli radio wifi on ;;
@ -30,7 +29,6 @@ control() {
# shellcheck disable=SC2154 # shellcheck disable=SC2154
echo "$command: subcommand '$subcommand' is not a valid argument." >&2 echo "$command: subcommand '$subcommand' is not a valid argument." >&2
return 1 return 1
;;
esac esac
} }
@ -41,5 +39,4 @@ if [[ $# -ne 2 ]]; then
exit 1 exit 1
fi fi
control "$1" "$2" & control "$1" "$2"
disown