Skip to content

Commit

Permalink
Save
Browse files Browse the repository at this point in the history
  • Loading branch information
anishathalye committed Dec 28, 2024
1 parent 0d00af7 commit 1089a50
Show file tree
Hide file tree
Showing 17 changed files with 188 additions and 144 deletions.
40 changes: 10 additions & 30 deletions src/dotbot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,17 @@


def add_options(parser: ArgumentParser) -> None:
parser.add_argument(
"-Q", "--super-quiet", action="store_true", help="suppress almost all output"
)
parser.add_argument("-Q", "--super-quiet", action="store_true", help="suppress almost all output")
parser.add_argument("-q", "--quiet", action="store_true", help="suppress most output")
parser.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="enable verbose output\n"
"-v: typical verbose\n"
"-vv: also, set shell commands stderr/stdout to true",
)
parser.add_argument(
"-d", "--base-directory", help="execute commands from within BASEDIR", metavar="BASEDIR"
)
parser.add_argument(
"-c", "--config-file", help="run commands given in CONFIGFILE", metavar="CONFIGFILE"
help="enable verbose output\n" "-v: typical verbose\n" "-vv: also, set shell commands stderr/stdout to true",
)
parser.add_argument("-d", "--base-directory", help="execute commands from within BASEDIR", metavar="BASEDIR")
parser.add_argument("-c", "--config-file", help="run commands given in CONFIGFILE", metavar="CONFIGFILE")
parser.add_argument(
"-p",
"--plugin",
Expand All @@ -42,9 +34,7 @@ def add_options(parser: ArgumentParser) -> None:
help="load PLUGIN as a plugin",
metavar="PLUGIN",
)
parser.add_argument(
"--disable-built-in-plugins", action="store_true", help="disable built-in plugins"
)
parser.add_argument("--disable-built-in-plugins", action="store_true", help="disable built-in plugins")
parser.add_argument(
"--plugin-dir",
action="append",
Expand All @@ -53,21 +43,11 @@ def add_options(parser: ArgumentParser) -> None:
metavar="PLUGIN_DIR",
help="load all plugins in PLUGIN_DIR",
)
parser.add_argument(
"--only", nargs="+", help="only run specified directives", metavar="DIRECTIVE"
)
parser.add_argument(
"--except", nargs="+", dest="skip", help="skip specified directives", metavar="DIRECTIVE"
)
parser.add_argument(
"--force-color", dest="force_color", action="store_true", help="force color output"
)
parser.add_argument(
"--no-color", dest="no_color", action="store_true", help="disable color output"
)
parser.add_argument(
"--version", action="store_true", help="show program's version number and exit"
)
parser.add_argument("--only", nargs="+", help="only run specified directives", metavar="DIRECTIVE")
parser.add_argument("--except", nargs="+", dest="skip", help="skip specified directives", metavar="DIRECTIVE")
parser.add_argument("--force-color", dest="force_color", action="store_true", help="force color output")
parser.add_argument("--no-color", dest="no_color", action="store_true", help="disable color output")
parser.add_argument("--version", action="store_true", help="show program's version number and exit")
parser.add_argument(
"-x",
"--exit-on-failure",
Expand Down
4 changes: 3 additions & 1 deletion src/dotbot/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class Context:
Contextual data and information for plugins.
"""

def __init__(self, base_directory: str, options: Optional[Namespace] = None, plugins: "Optional[List[Type[Plugin]]]" = None):
def __init__(
self, base_directory: str, options: Optional[Namespace] = None, plugins: "Optional[List[Type[Plugin]]]" = None
):
self._base_directory = base_directory
self._defaults: Dict[str, Any] = {}
self._options = options if options is not None else Namespace()
Expand Down
8 changes: 4 additions & 4 deletions src/dotbot/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def __init__(
self._skip = skip
self._exit = exit_on_failure

def _setup_context(self, base_directory: str, options: Optional[Namespace], plugins: Optional[List[Type[Plugin]]]) -> None:
def _setup_context(
self, base_directory: str, options: Optional[Namespace], plugins: Optional[List[Type[Plugin]]]
) -> None:
path = os.path.abspath(os.path.expanduser(base_directory))
if not os.path.exists(path):
msg = "Nonexistent base directory"
Expand Down Expand Up @@ -71,9 +73,7 @@ def dispatch(self, tasks: List[Dict[str, Any]]) -> bool:
success &= local_success
handled = True
except Exception as err: # noqa: BLE001
self._log.error(
f"An error was encountered while executing action {action}"
)
self._log.error(f"An error was encountered while executing action {action}")
self._log.debug(str(err))
if self._exit:
# There was an execption exit
Expand Down
4 changes: 1 addition & 3 deletions src/dotbot/plugins/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ def _clean(self, target: str, *, force: bool, recursive: bool) -> bool:
self._log.debug(f"Ignoring nonexistent directory {target}")
return True
for item in os.listdir(os.path.expandvars(os.path.expanduser(target))):
path = os.path.abspath(
os.path.join(os.path.expandvars(os.path.expanduser(target)), item)
)
path = os.path.abspath(os.path.join(os.path.expandvars(os.path.expanduser(target)), item))
if recursive and os.path.isdir(path):
# isdir implies not islink -- we don't want to descend into
# symlinked directories. okay to do a recursive call here
Expand Down
28 changes: 10 additions & 18 deletions src/dotbot/plugins/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ def _process_links(self, links: Any) -> bool:
# extended config
test = source.get("if", test)
relative = source.get("relative", relative)
canonical_path = source.get(
"canonicalize", source.get("canonicalize-path", canonical_path)
)
canonical_path = source.get("canonicalize", source.get("canonicalize-path", canonical_path))
force = source.get("force", force)
relink = source.get("relink", relink)
create = source.get("create", create)
Expand All @@ -67,11 +65,7 @@ def _process_links(self, links: Any) -> bool:
for glob_full_item in glob_results:
# Find common dirname between pattern and the item:
glob_dirname = os.path.dirname(os.path.commonprefix([path, glob_full_item]))
glob_item = (
glob_full_item
if len(glob_dirname) == 0
else glob_full_item[len(glob_dirname) + 1 :]
)
glob_item = glob_full_item if len(glob_dirname) == 0 else glob_full_item[len(glob_dirname) + 1 :]
# Add prefix to basepath, if provided
if base_prefix:
glob_item = base_prefix + glob_item
Expand All @@ -97,9 +91,7 @@ def _process_links(self, links: Any) -> bool:
else:
if create:
success &= self._create(destination)
if not ignore_missing and not self._exists(
os.path.join(self._context.base_directory(), path)
):
if not ignore_missing and not self._exists(os.path.join(self._context.base_directory(), path)):
# we seemingly check this twice (here and in _link) because
# if the file doesn't exist and force is True, we don't
# want to remove the original (this is tested by
Expand All @@ -108,8 +100,12 @@ def _process_links(self, links: Any) -> bool:
self._log.warning(f"Nonexistent source {destination} -> {path}")
continue
if force or relink:
success &= self._delete(path, destination, relative=relative, canonical_path=canonical_path, force=force)
success &= self._link(path, destination, relative=relative, canonical_path=canonical_path, ignore_missing=ignore_missing)
success &= self._delete(
path, destination, relative=relative, canonical_path=canonical_path, force=force
)
success &= self._link(
path, destination, relative=relative, canonical_path=canonical_path, ignore_missing=ignore_missing
)
if success:
self._log.info("All links have been set up")
else:
Expand Down Expand Up @@ -246,11 +242,7 @@ def _link(self, source: str, link_name: str, *, relative: bool, canonical_path:
absolute_source = os.path.join(base_directory, source)
link_name = os.path.normpath(link_name)
source = self._relative_path(absolute_source, destination) if relative else absolute_source
if (
not self._exists(link_name)
and self._is_link(link_name)
and self._link_destination(link_name) != source
):
if not self._exists(link_name) and self._is_link(link_name) and self._link_destination(link_name) != source:
self._log.warning(f"Invalid link {link_name} -> {self._link_destination(link_name)}")
# we need to use absolute_source below because our cwd is the dotfiles
# directory, and if source is relative, it will be relative to the
Expand Down
9 changes: 8 additions & 1 deletion src/dotbot/util/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
from typing import Optional


def shell_command(command: str, cwd: Optional[str] = None, *, enable_stdin: bool = False, enable_stdout: bool = False, enable_stderr: bool = False) -> int:
def shell_command(
command: str,
cwd: Optional[str] = None,
*,
enable_stdin: bool = False,
enable_stdout: bool = False,
enable_stderr: bool = False,
) -> int:
with open(os.devnull, "w") as devnull_w, open(os.devnull) as devnull_r:
stdin = None if enable_stdin else devnull_r
stdout = None if enable_stdout else devnull_w
Expand Down
4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def get_long_path(path: str) -> str:
allowed_tempfile_internal_unlink_calls: List[str] = []


def wrap_function(function: Callable[..., Any], function_path: str, arg_index: int, kwarg_key: str, root: str) -> Callable[..., Any]:
def wrap_function(
function: Callable[..., Any], function_path: str, arg_index: int, kwarg_key: str, root: str
) -> Callable[..., Any]:
def wrapper(*args: Any, **kwargs: Any) -> Any:
value = kwargs[kwarg_key] if kwarg_key in kwargs else args[arg_index]

Expand Down
4 changes: 1 addition & 3 deletions tests/dotbot_plugin_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ def handle(self, directive: str, _data: Any) -> bool:
self._log.debug("Attempting to get options from Context")
options = self._context.options()
if len(options.plugin_dirs) != 1:
self._log.debug(
"Context.options.plugins length is %i, expected 1" % len(options.plugins)
)
self._log.debug("Context.options.plugins length is %i, expected 1" % len(options.plugins))
return False

with open(os.path.abspath(os.path.expanduser("~/flag")), "w") as file:
Expand Down
13 changes: 6 additions & 7 deletions tests/dotbot_plugin_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ class File(dotbot.Plugin):
def can_handle(self, directive: str) -> bool:
return directive == "plugin_file"

def handle(self, directive: str, data: Any) -> bool:
def handle(self, directive: str, _data: Any) -> bool:
if directive != "plugin_file":
msg = f"File cannot handle directive {directive}"
raise ValueError(msg)
self._log.debug("Attempting to get options from Context")
options = self._context.options()
if len(options.plugins) != 1:
self._log.debug(
"Context.options.plugins length is %i, expected 1" % len(options.plugins)
)
self._log.debug(f"Context.options.plugins length is {len(options.plugins)}, expected 1")
return False
if not options.plugins[0].endswith("file.py"):
self._log.debug(
"Context.options.plugins[0] is %s, expected end with file.py" % options.plugins[0]
)
self._log.debug(f"Context.options.plugins[0] is {options.plugins[0]}, expected end with file.py")
return False

with open(os.path.abspath(os.path.expanduser("~/flag")), "w") as file:
Expand Down
6 changes: 4 additions & 2 deletions tests/dotbot_plugin_issue_357.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from typing import Any

from dotbot.plugin import Plugin
Expand All @@ -13,5 +12,8 @@ class NoopPlugin(Plugin):
def can_handle(self, directive: str) -> bool:
return directive == self._directive

def handle(self, directive: str, data: Any) -> bool:
def handle(self, directive: str, _data: Any) -> bool:
if directive != self._directive:
msg = f"NoopPlugin cannot handle directive {directive}"
raise ValueError(msg)
return True
13 changes: 6 additions & 7 deletions tests/test_bin_dotbot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import shutil
import subprocess
from typing import Optional

import pytest

Expand All @@ -11,14 +12,12 @@
"sys.platform == 'win32'",
reason="The hybrid sh/Python dotbot script doesn't run on Windows platforms",
)
@pytest.mark.parametrize("python_name", (None, "python", "python3"))
def test_find_python_executable(python_name: str, home: str, dotfiles: Dotfiles) -> None:
@pytest.mark.parametrize("python_name", [None, "python", "python3"])
def test_find_python_executable(python_name: Optional[str], home: str, dotfiles: Dotfiles) -> None:
"""Verify that the sh/Python hybrid dotbot executable can find Python."""

dotfiles.write_config([])
dotbot_executable = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bin", "dotbot"
)
dotbot_executable = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bin", "dotbot")

# Create a link to sh.
tmp_bin = os.path.join(home, "tmp_bin")
Expand All @@ -27,15 +26,15 @@ def test_find_python_executable(python_name: str, home: str, dotfiles: Dotfiles)
assert sh_path is not None
os.symlink(sh_path, os.path.join(tmp_bin, "sh"))

if python_name:
if python_name is not None:
with open(os.path.join(tmp_bin, python_name), "w") as file:
file.write("#!" + tmp_bin + "/sh\n")
file.write("exit 0\n")
os.chmod(os.path.join(tmp_bin, python_name), 0o777)
env = dict(os.environ)
env["PATH"] = tmp_bin

if python_name:
if python_name is not None:
subprocess.check_call(
[dotbot_executable, "-c", dotfiles.config_filename],
env=env,
Expand Down
Loading

0 comments on commit 1089a50

Please sign in to comment.