Skip to content

Commit

Permalink
Merge pull request #81 from bakfile/feat/catch-rootitude
Browse files Browse the repository at this point in the history
🤞
  • Loading branch information
ChanceNCounter authored Oct 7, 2021
2 parents 9433ed4 + b62682a commit dad71f9
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 248 deletions.
2 changes: 1 addition & 1 deletion bak/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
BAK_VERSION = "0.2.2a5"
BAK_VERSION = "0.2.2a6"
257 changes: 15 additions & 242 deletions bak/__main__.py
Original file line number Diff line number Diff line change
@@ -1,247 +1,20 @@
import functools
from pathlib import Path
from typing import Union
import click
from click_default_group import DefaultGroup
from os import geteuid
from sys import exit as exitapp

from bak import commands
from bak import BAK_VERSION as bak_version
from bak.configuration import bak_cfg as cfg
from click import confirm

def run_bak():
if geteuid() == 0:
if not confirm("WARNING: You are running bak as root! "
"This will create separate config and bakfiles for root, "
"and is probably not what you're trying to do.\n\n"
"If bak needs superuser privileges to copy or overwrite a file, "
"it will invoke sudo cp by itself.\n\n"
"Are you sure you want to continue as root?"):
exitapp()

def __print_help():
with click.get_current_context() as ctx:
click.echo(bak.get_help(ctx))


def normalize_path(args_key: str = 'filename'):
def on_decorator(func):
@functools.wraps(func)
def on_call(*args, **kwargs):
try:
# expand path
arg = Path(kwargs[args_key]).expanduser().resolve()
if arg.is_dir():
click.echo(
f"Error: bak cannot operate on directories ({arg})")
return
else:
kwargs[args_key] = arg
# Account for optional params and params that default to None or False
except (IndexError, KeyError, TypeError):
pass
return func(*args, **kwargs)
return on_call
return on_decorator


BASIC_HELP_TEXT = "bak FILENAME (creates a bakfile)\n\nalias: bak create\n\n" +\
"See also: bak COMMAND --help"


@click.group(cls=DefaultGroup, default='\0', no_args_is_help=True, help=BASIC_HELP_TEXT,
invoke_without_command=True)
# default command behavior is duplicated here because it's cleaner from a Click perspective,
# which is to say that it gets the desired behavior across the board. ugly but it works!
@click.option("--version", required=False, is_flag=True, help="Print current version and exit.")
def bak(version:bool=False):
if version:
create_bak_cmd(None, version)


@bak.command("\0", hidden=True)
@normalize_path()
@click.option("--version", required=False, is_flag=True, help="Print current version and exit.")
@click.argument("filename", required=False, type=click.Path(exists=True))
def _create(filename, version):
create_bak_cmd(filename, version)


@bak.command("create", hidden=True)
@normalize_path()
@click.option("--version", required=False, is_flag=True)
@click.argument("filename", required=False, type=click.Path(exists=True))
def create(filename, version):
create_bak_cmd(filename, version)


def create_bak_cmd(filename, version):
if version:
click.echo(f"bak version {bak_version}")
elif not filename:
# Ensures that 'bak --help' is printed if it doesn't get a filename
__print_help()
else:
filename = Path(filename).expanduser().resolve()
commands.create_bakfile(filename)


@bak.command("up", help="Replace a .bakfile with a fresh copy of the parent file")
@normalize_path()
@click.argument("filename", required=True, type=click.Path(exists=True))
@click.argument("bakfile_number", metavar="[#]", required=False, type=int)
def bak_up(filename, bakfile_number):
if not filename:
click.echo("A filename or operation is required.\n"
"\tbak --help")
filename = Path(filename).expanduser().resolve()
if not commands.bak_up_cmd(filename, bakfile_number):
# TODO descriptive failures
click.echo("An error occurred.")


@bak.command("down", help="Restore from a .bakfile (.bakfiles deleted without '--keep')")
@click.option("--keep", "-k",
is_flag=False,
default=0,
multiple=True,
type=str,
help="Keep .bakfiles (optionally accepts a bakfile #; can be used multiple times.\n" +\
"Also accepts 'all'")
@click.option("--quietly", "-q",
is_flag=True,
default=False,
help="No confirmation prompt")
@click.option('-d', '-o', '--destination', default=None, type=str)
@click.argument("filename", required=True)
@click.argument("bakfile_number", metavar="[#]", required=False, type=int)
def bak_down(filename: str, keep: bool, quietly: bool, destination: str, bakfile_number: int=0):
if not filename:
click.echo("A filename or operation is required.\n"
"\tbak --help")
filename = Path(filename).expanduser().resolve()
if destination:
destination = Path(destination).expanduser().resolve()
if not isinstance(keep, tuple):
if keep == -1 or keep == 'all':
keep = True
elif keep == 0:
keep = False
else:
keep = list(keep)
commands.bak_down_cmd(filename, destination, keep, quietly, bakfile_number)


@bak.command("off", help="Use when finished to delete .bakfiles")
@click.option("--quietly", "-q",
is_flag=True,
default=False,
help="Delete all related .bakfiles without confirming")
@click.argument("filename", required=True)
def bak_off(filename, quietly):
filename = Path(filename).expanduser().resolve()
if not commands.bak_off_cmd(filename, quietly):
# TODO better output here
click.echo("Operation cancelled or failed.")

@bak.command("del", help="Delete a single .bakfile by number (see `bak list FILENAME`)"
"\n\n\talias: `bak rm`")
@click.option("--quietly", "-q",
is_flag=True,
default=False,
help="Delete .bakfile without confirming")
@click.argument("filename", required=True, type=click.Path(exists=False))
@click.argument("number", metavar="#", required=False, type=int)
def bak_del(filename, number, quietly):
filename = Path(filename).expanduser().resolve()
if not commands.bak_del_cmd(filename, number, quietly):
# TODO this is just a copy of `bak off`, so...
click.echo("Operation cancelled or failed.")

@bak.command("rm", hidden=True, help="Delete a single .bakfile by number (see `bak list FILENAME`)"
"\n\n\talias of `bak del`")
@click.option("--quietly", "-q",
is_flag=True,
default=False,
help="Delete .bakfile without confirming")
@click.argument("filename", required=True, type=click.Path(exists=False))
@click.argument("number", metavar="#", required=False, type=int)
def _bak_rm(filename, number, quietly):
bak_del(filename, number, quietly)

@bak.command("open", help="View or edit a .bakfile in an external program")
@click.option("--using", "--in", "--with",
help="Program to open (default: $PAGER or less)",
required=False, hidden=True)
@normalize_path()
@click.argument("filename", required=True, type=click.Path(exists=True))
@click.argument("bakfile_number", metavar="[#]", required=False, type=int)
def bak_print(filename, using, bakfile_number):
filename = Path(filename).expanduser().resolve()
commands.bak_print_cmd(filename, using, bakfile_number)


@bak.command("where",
help="Outputs the real path of a .bakfile. "
"Useful for piping, and not much else.",
short_help="Output the real path of a .bakfile")
@click.argument("filename",
required=True,
type=click.Path())
@click.argument("bakfile_number", metavar="[#]", required=False, type=int)
@normalize_path()
def bak_get(filename, bakfile_number=0):
to_where_you_once_belonged = Path(
filename).expanduser().resolve()
commands.bak_getfile_cmd(to_where_you_once_belonged, bakfile_number)


@bak.command("diff",
help="diff a file against its .bakfile")
@click.option("--using", "--with",
help="Program to use instead of system diff",
required=False)
@normalize_path()
@click.argument("filename", required=True, type=click.Path(exists=True))
@click.argument("bakfile_number", metavar="[#]", required=False, type=int)
def bak_diff(filename, using, bakfile_number=0):
filename = Path(filename).expanduser().resolve()
commands.bak_diff_cmd(filename, command=using, bakfile_number=bakfile_number or 0)


@bak.command("list",
help="List all .bakfiles, or a particular file's")
@click.option("--colors/--nocolors", "-c/-C",
help="Colorize output",
is_flag=True,
default=cfg['bak_list_colors'] and not cfg['fast_mode'])
@click.option("--relpaths", "--rel", "-r",
help="Display relative paths instead of abspaths",
required=False,
is_flag=True,
default=commands.BAK_LIST_RELPATHS)
@click.option("--compare", "--diff", "-d",
help="Compare .bakfiles with current file, identify exact copies",
required=False,
is_flag=True,
default=False)
@click.argument("filename",
required=False,
type=click.Path(exists=True))
@normalize_path()
def bak_list(colors, relpaths, compare, filename):
if filename:
filename = Path(filename).expanduser().resolve()
commands.show_bak_list(filename=filename or None,
relative_paths=relpaths, colors=colors, compare=compare)


TAB = '\t'
CFG_HELP_TEXT = '\b\nGet/set config values. Valid settings include:\n\n\t' + \
f'\b\n{(TAB + cfg.newline).join(cfg.SETTABLE_VALUES)}' + \
'\b\n\nNOTE: diff-exec\'s value should be enclosed in quotes, and' \
'\nformatted like:\b\n\n\t\'diff %old %new\' \b\n\n(%old and %new will be substituted ' \
'with the bakfile and the original file, respectively)'


@bak.command("config",
short_help="get/set config options", help=CFG_HELP_TEXT)
@click.option("--get/--set", default=True)
@click.argument("setting", required=True)
@click.argument("value", required=False, nargs=-1, type=str)
def bak_config(get, setting, value):
commands.bak_config_command(get, setting, value)

from bak.cli import bak as _bak
_bak()

if __name__ == "__main__":
bak()
run_bak()
Loading

0 comments on commit dad71f9

Please sign in to comment.