Merge pull request 'clan-cli: dev-shell, build, tab completion, formatting, linting, unit tests' (#11) from cli into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/11
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
{ self
|
{ lib
|
||||||
, inputs
|
, inputs
|
||||||
, ...
|
, ...
|
||||||
}: {
|
}: {
|
||||||
@@ -9,7 +9,32 @@
|
|||||||
treefmt.projectRootFile = "flake.nix";
|
treefmt.projectRootFile = "flake.nix";
|
||||||
treefmt.flakeCheck = true;
|
treefmt.flakeCheck = true;
|
||||||
treefmt.flakeFormatter = true;
|
treefmt.flakeFormatter = true;
|
||||||
treefmt.programs.nixpkgs-fmt.enable = true;
|
|
||||||
treefmt.programs.shellcheck.enable = true;
|
treefmt.programs.shellcheck.enable = true;
|
||||||
|
treefmt.settings.formatter.nix = {
|
||||||
|
command = "sh";
|
||||||
|
options = [
|
||||||
|
"-eucx"
|
||||||
|
''
|
||||||
|
# First deadnix
|
||||||
|
${lib.getExe pkgs.deadnix} --edit "$@"
|
||||||
|
# Then nixpkgs-fmt
|
||||||
|
${lib.getExe pkgs.nixpkgs-fmt} "$@"
|
||||||
|
''
|
||||||
|
"--" # this argument is ignored by bash
|
||||||
|
];
|
||||||
|
includes = [ "*.nix" ];
|
||||||
|
};
|
||||||
|
treefmt.settings.formatter.python = {
|
||||||
|
command = "sh";
|
||||||
|
options = [
|
||||||
|
"-eucx"
|
||||||
|
''
|
||||||
|
${lib.getExe pkgs.ruff} --fix "$@"
|
||||||
|
${lib.getExe pkgs.black} "$@"
|
||||||
|
''
|
||||||
|
"--" # this argument is ignored by bash
|
||||||
|
];
|
||||||
|
includes = [ "*.py" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ self, ... }: {
|
{ ... }: {
|
||||||
pre-commit.settings.hooks.alejandra.enable = true;
|
pre-commit.settings.hooks.alejandra.enable = true;
|
||||||
pre-commit.settings.hooks.shellcheck.enable = true;
|
pre-commit.settings.hooks.shellcheck.enable = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
outputs = inputs @ { flake-parts, ... }:
|
outputs = inputs @ { flake-parts, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } ({ lib
|
flake-parts.lib.mkFlake { inherit inputs; } ({ lib
|
||||||
, config
|
, config
|
||||||
, self
|
|
||||||
, ...
|
, ...
|
||||||
}: {
|
}: {
|
||||||
systems = lib.systems.flakeExposed;
|
systems = lib.systems.flakeExposed;
|
||||||
@@ -22,6 +21,7 @@
|
|||||||
./flake-parts/packages.nix
|
./flake-parts/packages.nix
|
||||||
./flake-parts/formatting.nix
|
./flake-parts/formatting.nix
|
||||||
./templates/python-project/flake-module.nix
|
./templates/python-project/flake-module.nix
|
||||||
|
./pkgs/clan-cli/flake-module.nix
|
||||||
];
|
];
|
||||||
flake = {
|
flake = {
|
||||||
nixosConfigurations.installer = lib.nixosSystem {
|
nixosConfigurations.installer = lib.nixosSystem {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{ config
|
{ lib
|
||||||
, lib
|
|
||||||
, pkgs
|
, pkgs
|
||||||
, ...
|
, ...
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
1
pkgs/clan-cli/.envrc
Normal file
1
pkgs/clan-cli/.envrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
use nix
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# !/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def create(args):
|
|
||||||
os.makedirs(args.folder, exist_ok=True)
|
|
||||||
# TODO create clan template in flake
|
|
||||||
subprocess.Popen(
|
|
||||||
[
|
|
||||||
"nix",
|
|
||||||
"flake",
|
|
||||||
"init",
|
|
||||||
"-t",
|
|
||||||
"git+https://git.clan.lol/clan/clan-core#clan-template",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def edit(args):
|
|
||||||
# TODO add some cli options to change certain options without relying on a text editor
|
|
||||||
clan_flake = f"{args.folder}/flake.nix"
|
|
||||||
if os.path.isfile(clan_flake):
|
|
||||||
subprocess.Popen(
|
|
||||||
[
|
|
||||||
os.environ["EDITOR"],
|
|
||||||
clan_flake,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f"{args.folder} has no flake.nix, so it does not seem to be the clan root folder"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def rebuild(args):
|
|
||||||
# TODO get clients from zerotier cli?
|
|
||||||
if args.host:
|
|
||||||
print(f"would redeploy {args.host} from clan {args.folder}")
|
|
||||||
else:
|
|
||||||
print(f"would redeploy all hosts from clan {args.folder}")
|
|
||||||
|
|
||||||
|
|
||||||
def destroy(args):
|
|
||||||
# TODO get clan folder & hosts from somwhere (maybe ~/.config/clan/$name /)
|
|
||||||
# send some kind of kill signal, then remove the folder
|
|
||||||
if args.yes:
|
|
||||||
print(f"would remove {args.folder}")
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
"are you really sure? this is non reversible and destructive, add --yes to confirm"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def backup(args):
|
|
||||||
if args.host:
|
|
||||||
print(f"would backup {args.host} from clan {args.folder}")
|
|
||||||
else:
|
|
||||||
print(f"would backup all hosts from clan {args.folder}")
|
|
||||||
|
|
||||||
|
|
||||||
def git(args):
|
|
||||||
subprocess.Popen(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"-C",
|
|
||||||
args.folder,
|
|
||||||
] + args.git_args
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="clan-admin")
|
|
||||||
parser.add_argument(
|
|
||||||
"-f",
|
|
||||||
"--folder",
|
|
||||||
help="the folder where the clan is defined, default to the current folder",
|
|
||||||
default=os.environ["PWD"],
|
|
||||||
)
|
|
||||||
subparser = parser.add_subparsers(
|
|
||||||
title="command",
|
|
||||||
description="the command to run",
|
|
||||||
help="the command to run",
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
parser_create = subparser.add_parser("create", help="create a new clan")
|
|
||||||
parser_create.set_defaults(func=create)
|
|
||||||
|
|
||||||
parser_edit = subparser.add_parser("edit", help="edit a clan")
|
|
||||||
parser_edit.set_defaults(func=edit)
|
|
||||||
|
|
||||||
parser_rebuild = subparser.add_parser(
|
|
||||||
"rebuild", help="build configuration of a clan and push it to the target"
|
|
||||||
)
|
|
||||||
parser_rebuild.add_argument(
|
|
||||||
"--host", help="specify single host to rebuild", default=None
|
|
||||||
)
|
|
||||||
parser_rebuild.set_defaults(func=rebuild)
|
|
||||||
|
|
||||||
parser_destroy = subparser.add_parser(
|
|
||||||
"destroy", help="destroy a clan, including all the machines"
|
|
||||||
)
|
|
||||||
parser_destroy.add_argument(
|
|
||||||
"--yes", help="specify single host to rebuild", action="store_true"
|
|
||||||
)
|
|
||||||
parser_destroy.set_defaults(func=destroy)
|
|
||||||
|
|
||||||
parser_backup = subparser.add_parser(
|
|
||||||
"backup", help="backup all the state of all machines in a clan or just a single one"
|
|
||||||
)
|
|
||||||
parser_backup.add_argument(
|
|
||||||
"--host", help="specify single host to rebuild", default=None
|
|
||||||
)
|
|
||||||
parser_backup.set_defaults(func=backup)
|
|
||||||
|
|
||||||
parser_git = subparser.add_parser("git", help="control the clan repo via git")
|
|
||||||
parser_git.add_argument("git_args", nargs="*")
|
|
||||||
parser_git.set_defaults(func=git)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
args.func(args)
|
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
# !/usr/bin/env python3
|
# !/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def showhelp():
|
|
||||||
print('''
|
|
||||||
usage:
|
|
||||||
clan admin ...
|
|
||||||
clan join ...
|
|
||||||
clan delete ...
|
|
||||||
''')
|
|
||||||
|
|
||||||
|
|
||||||
|
has_argcomplete = True
|
||||||
try:
|
try:
|
||||||
cmd = f'clan-{sys.argv[1]}'
|
import argcomplete
|
||||||
except: # noqa
|
except ImportError:
|
||||||
showhelp()
|
has_argcomplete = False
|
||||||
|
|
||||||
try:
|
import clan_admin
|
||||||
subprocess.Popen([cmd] + sys.argv[2:])
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f'command {cmd} not found')
|
# this will be the entrypoint under /bin/clan (see pyproject.toml config)
|
||||||
exit(2)
|
def clan() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description="cLAN tool")
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
# init clan admin
|
||||||
|
parser_admin = subparsers.add_parser("admin")
|
||||||
|
clan_admin.make_parser(parser_admin)
|
||||||
|
if has_argcomplete:
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
parser.parse_args()
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
parser.print_help()
|
||||||
|
|||||||
129
pkgs/clan-cli/clan_admin.py
Executable file
129
pkgs/clan-cli/clan_admin.py
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
# !/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def create(args: argparse.Namespace) -> None:
|
||||||
|
os.makedirs(args.folder, exist_ok=True)
|
||||||
|
# TODO create clan template in flake
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
"nix",
|
||||||
|
"flake",
|
||||||
|
"init",
|
||||||
|
"-t",
|
||||||
|
"git+https://git.clan.lol/clan/clan-core#clan-template",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def edit(args: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
# TODO add some cli options to change certain options without relying on a text editor
|
||||||
|
clan_flake = f"{args.folder}/flake.nix"
|
||||||
|
if os.path.isfile(clan_flake):
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
os.environ["EDITOR"],
|
||||||
|
clan_flake,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"{args.folder} has no flake.nix, so it does not seem to be the clan root folder",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild(args: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
# TODO get clients from zerotier cli?
|
||||||
|
if args.host:
|
||||||
|
print(f"would redeploy {args.host} from clan {args.folder}")
|
||||||
|
else:
|
||||||
|
print(f"would redeploy all hosts from clan {args.folder}")
|
||||||
|
|
||||||
|
|
||||||
|
def destroy(args: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
# TODO get clan folder & hosts from somwhere (maybe ~/.config/clan/$name /)
|
||||||
|
# send some kind of kill signal, then remove the folder
|
||||||
|
if args.yes:
|
||||||
|
print(f"would remove {args.folder}")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"are you really sure? this is non reversible and destructive, add --yes to confirm"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def backup(args: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
if args.host:
|
||||||
|
print(f"would backup {args.host} from clan {args.folder}")
|
||||||
|
else:
|
||||||
|
print(f"would backup all hosts from clan {args.folder}")
|
||||||
|
|
||||||
|
|
||||||
|
def git(args: argparse.Namespace) -> None: # pragma: no cover
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"-C",
|
||||||
|
args.folder,
|
||||||
|
] + args.git_args
|
||||||
|
)
|
||||||
|
|
||||||
|
# takes a (sub)parser and configures it
|
||||||
|
def make_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--folder",
|
||||||
|
help="the folder where the clan is defined, default to the current folder",
|
||||||
|
default=os.environ["PWD"],
|
||||||
|
)
|
||||||
|
subparser = parser.add_subparsers(
|
||||||
|
title="command",
|
||||||
|
description="the command to run",
|
||||||
|
help="the command to run",
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser_create = subparser.add_parser("create", help="create a new clan")
|
||||||
|
parser_create.set_defaults(func=create)
|
||||||
|
|
||||||
|
parser_edit = subparser.add_parser("edit", help="edit a clan")
|
||||||
|
parser_edit.set_defaults(func=edit)
|
||||||
|
|
||||||
|
parser_rebuild = subparser.add_parser(
|
||||||
|
"rebuild", help="build configuration of a clan and push it to the target"
|
||||||
|
)
|
||||||
|
parser_rebuild.add_argument(
|
||||||
|
"--host", help="specify single host to rebuild", default=None
|
||||||
|
)
|
||||||
|
parser_rebuild.set_defaults(func=rebuild)
|
||||||
|
|
||||||
|
parser_destroy = subparser.add_parser(
|
||||||
|
"destroy", help="destroy a clan, including all the machines"
|
||||||
|
)
|
||||||
|
parser_destroy.add_argument(
|
||||||
|
"--yes", help="specify single host to rebuild", action="store_true"
|
||||||
|
)
|
||||||
|
parser_destroy.set_defaults(func=destroy)
|
||||||
|
|
||||||
|
parser_backup = subparser.add_parser(
|
||||||
|
"backup", help="backup all the state of all machines in a clan or just a single one"
|
||||||
|
)
|
||||||
|
parser_backup.add_argument(
|
||||||
|
"--host", help="specify single host to rebuild", default=None
|
||||||
|
)
|
||||||
|
parser_backup.set_defaults(func=backup)
|
||||||
|
|
||||||
|
parser_git = subparser.add_parser("git", help="control the clan repo via git")
|
||||||
|
parser_git.add_argument("git_args", nargs="*")
|
||||||
|
parser_git.set_defaults(func=git)
|
||||||
|
|
||||||
|
|
||||||
|
def clan_admin() -> None: # pragma: no cover
|
||||||
|
parser = argparse.ArgumentParser(description="clan-admin")
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
# entry point if this file is executed directly
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
clan_admin()
|
||||||
@@ -1,10 +1,71 @@
|
|||||||
{ symlinkJoin
|
{ pkgs ? import <nixpkgs> { }
|
||||||
, writers
|
, lib ? pkgs.lib
|
||||||
|
, python3 ? pkgs.python3
|
||||||
|
, ruff ? pkgs.ruff
|
||||||
|
, runCommand ? pkgs.runCommand
|
||||||
|
, installShellFiles ? pkgs.installShellFiles
|
||||||
|
,
|
||||||
}:
|
}:
|
||||||
symlinkJoin {
|
let
|
||||||
name = "clan";
|
pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml);
|
||||||
paths = [
|
name = pyproject.project.name;
|
||||||
(writers.writePython3Bin "clan" { } ./clan.py)
|
|
||||||
(writers.writePython3Bin "clan-admin" { flakeIgnore = [ "E501" ]; } ./clan-admin.py)
|
src = lib.cleanSource ./.;
|
||||||
];
|
|
||||||
}
|
dependencies = lib.attrValues {
|
||||||
|
inherit (python3.pkgs)
|
||||||
|
argcomplete
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
devDependencies = lib.attrValues {
|
||||||
|
inherit (pkgs) ruff;
|
||||||
|
inherit (python3.pkgs)
|
||||||
|
black
|
||||||
|
mypy
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pytest-subprocess
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
package = python3.pkgs.buildPythonPackage {
|
||||||
|
inherit name src;
|
||||||
|
format = "pyproject";
|
||||||
|
nativeBuildInputs = [
|
||||||
|
python3.pkgs.setuptools
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
propagatedBuildInputs =
|
||||||
|
dependencies
|
||||||
|
++ [ ];
|
||||||
|
passthru.tests = { inherit check; };
|
||||||
|
passthru.devDependencies = devDependencies;
|
||||||
|
postInstall = ''
|
||||||
|
installShellCompletion --bash --name clan \
|
||||||
|
<(${python3.pkgs.argcomplete}/bin/register-python-argcomplete --shell bash clan)
|
||||||
|
installShellCompletion --fish --name clan.fish \
|
||||||
|
<(${python3.pkgs.argcomplete}/bin/register-python-argcomplete --shell fish clan)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPython = python3.withPackages (_ps: devDependencies ++ dependencies);
|
||||||
|
|
||||||
|
check = runCommand "${name}-check" { } ''
|
||||||
|
cp -r ${src} ./src
|
||||||
|
chmod +w -R ./src
|
||||||
|
cd src
|
||||||
|
export PYTHONPATH=.
|
||||||
|
echo -e "\x1b[32m## run ruff\x1b[0m"
|
||||||
|
${ruff}/bin/ruff check .
|
||||||
|
echo -e "\x1b[32m## run mypy\x1b[0m"
|
||||||
|
${checkPython}/bin/mypy .
|
||||||
|
echo -e "\x1b[32m## run pytest\x1b[0m"
|
||||||
|
${checkPython}/bin/pytest
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
in
|
||||||
|
package
|
||||||
|
|||||||
12
pkgs/clan-cli/flake-module.nix
Normal file
12
pkgs/clan-cli/flake-module.nix
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
perSystem = { pkgs, ... }:
|
||||||
|
let
|
||||||
|
pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml);
|
||||||
|
name = pyproject.project.name;
|
||||||
|
package = pkgs.callPackage ./default.nix { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.${name} = package;
|
||||||
|
checks.${name} = package.tests.check;
|
||||||
|
};
|
||||||
|
}
|
||||||
65
pkgs/clan-cli/pyproject.toml
Normal file
65
pkgs/clan-cli/pyproject.toml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["clan.py"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "clan"
|
||||||
|
description = "cLAN CLI tool"
|
||||||
|
dynamic = ["version"]
|
||||||
|
scripts = {clan = "clan:clan"}
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--cov . --cov-report term --cov-fail-under=100 --no-cov-on-fail"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_redundant_casts = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
exclude = [
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "setuptools.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "pytest.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "argcomplete.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
|
||||||
|
select = ["E", "F", "I"]
|
||||||
|
ignore = [ "E501" ]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 88
|
||||||
|
target-version = ['py310']
|
||||||
|
include = '\.pyi?$'
|
||||||
|
exclude = '''
|
||||||
|
/(
|
||||||
|
\.git
|
||||||
|
| \.hg
|
||||||
|
| \.mypy_cache
|
||||||
|
| \.tox
|
||||||
|
| \.venv
|
||||||
|
| _build
|
||||||
|
| buck-out
|
||||||
|
| build
|
||||||
|
| dist
|
||||||
|
# The following are specific to Black, you probably don't want those.
|
||||||
|
| blib2to3
|
||||||
|
| tests/data
|
||||||
|
| profiling
|
||||||
|
)/
|
||||||
|
'''
|
||||||
55
pkgs/clan-cli/shell.nix
Normal file
55
pkgs/clan-cli/shell.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { }
|
||||||
|
,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
lib = pkgs.lib;
|
||||||
|
python3 = pkgs.python3;
|
||||||
|
package = import ./default.nix {
|
||||||
|
inherit lib python3;
|
||||||
|
};
|
||||||
|
pythonWithDeps = python3.withPackages (
|
||||||
|
ps:
|
||||||
|
package.propagatedBuildInputs
|
||||||
|
++ package.devDependencies
|
||||||
|
++ [
|
||||||
|
ps.pip
|
||||||
|
]
|
||||||
|
);
|
||||||
|
checkScript = pkgs.writeScriptBin "check" ''
|
||||||
|
nix build -f . tests.check -L "$@"
|
||||||
|
'';
|
||||||
|
devShell = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.ruff
|
||||||
|
pythonWithDeps
|
||||||
|
];
|
||||||
|
# sets up an editable install and add enty points to $PATH
|
||||||
|
shellHook = ''
|
||||||
|
tmp_path=$(realpath ./.pythonenv)
|
||||||
|
repo_root=$(realpath .)
|
||||||
|
rm -rf $tmp_path
|
||||||
|
mkdir -p "$tmp_path/${pythonWithDeps.sitePackages}"
|
||||||
|
|
||||||
|
${pythonWithDeps.interpreter} -m pip install \
|
||||||
|
--quiet \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--no-index \
|
||||||
|
--no-build-isolation \
|
||||||
|
--prefix "$tmp_path" \
|
||||||
|
--editable $repo_root
|
||||||
|
|
||||||
|
export PATH="$tmp_path/bin:${checkScript}/bin:$PATH"
|
||||||
|
export PYTHONPATH="$repo_root:$tmp_path/${pythonWithDeps.sitePackages}"
|
||||||
|
|
||||||
|
export XDG_DATA_DIRS="$tmp_path/share''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}"
|
||||||
|
export fish_complete_path="$tmp_path/share/fish/vendor_completions.d''${fish_complete_path:+:$fish_complete_path}"
|
||||||
|
mkdir -p \
|
||||||
|
$tmp_path/share/fish/vendor_completions.d \
|
||||||
|
$tmp_path/share/bash-completion/completions \
|
||||||
|
$tmp_path/share/zsh/site-functions
|
||||||
|
register-python-argcomplete --shell fish clan > $tmp_path/share/fish/vendor_completions.d/clan.fish
|
||||||
|
register-python-argcomplete --shell bash clan > $tmp_path/share/bash-completion/completions/clan
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
devShell
|
||||||
17
pkgs/clan-cli/tests/test_clan_admin.py
Normal file
17
pkgs/clan-cli/tests/test_clan_admin.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
import clan_admin
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_parser():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
clan_admin.make_parser(parser)
|
||||||
|
|
||||||
|
# using fp fixture from pytest-subprocess
|
||||||
|
def test_create(fp):
|
||||||
|
cmd = ["nix", "flake", "init", "-t", fp.any()]
|
||||||
|
fp.register(cmd)
|
||||||
|
args = argparse.Namespace(folder="./my-clan")
|
||||||
|
clan_admin.create(args)
|
||||||
|
assert fp.call_count(cmd) == 1
|
||||||
|
|
||||||
19
pkgs/clan-cli/tests/test_cli.py
Normal file
19
pkgs/clan-cli/tests/test_cli.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import clan
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_args(capsys):
|
||||||
|
clan.clan()
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert captured.out.startswith("usage:")
|
||||||
|
|
||||||
|
|
||||||
|
def test_help(capsys, monkeypatch):
|
||||||
|
monkeypatch.setattr(sys, "argv", ["", "--help"])
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
clan.clan()
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert captured.out.startswith("usage:")
|
||||||
@@ -42,7 +42,7 @@ let
|
|||||||
passthru.devDependencies = devDependencies;
|
passthru.devDependencies = devDependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
checkPython = python3.withPackages (ps: devDependencies ++ dependencies);
|
checkPython = python3.withPackages (_ps: devDependencies ++ dependencies);
|
||||||
|
|
||||||
check = runCommand "${name}-check" { } ''
|
check = runCommand "${name}-check" { } ''
|
||||||
cp -r ${src} ./src
|
cp -r ${src} ./src
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
perSystem = { pkgs, ... }:
|
perSystem = { pkgs, ... }:
|
||||||
let
|
let
|
||||||
pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml);
|
|
||||||
name = pyproject.project.name;
|
|
||||||
package = pkgs.callPackage ./default.nix { };
|
package = pkgs.callPackage ./default.nix { };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{ pkgs ? import <nixpkgs> { }
|
{ pkgs ? import <nixpkgs> { }
|
||||||
, system ? builtins.currentSystem
|
|
||||||
,
|
,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
|||||||
Reference in New Issue
Block a user