Inital commit with basic commands.
This commit is contained in:
18
src/bsv/__init__.py
Normal file
18
src/bsv/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from bsv._version import __version__, __version_tuple__
|
||||
21
src/bsv/__main__.py
Normal file
21
src/bsv/__main__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from bsv.main import main
|
||||
|
||||
|
||||
exit(main())
|
||||
64
src/bsv/command/__init__.py
Normal file
64
src/bsv/command/__init__.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from dataclasses import dataclass
|
||||
from textwrap import dedent
|
||||
from typing import Callable
|
||||
|
||||
|
||||
@dataclass
|
||||
class Command:
|
||||
init_parser: Callable[[ArgumentParser], None]
|
||||
function: Callable[..., int]
|
||||
|
||||
|
||||
commands: dict[str, Command] = {}
|
||||
def register_command(
|
||||
name: str,
|
||||
init_parser: Callable[[ArgumentParser], None],
|
||||
function: Callable[..., int],
|
||||
):
|
||||
commands[name] = Command(init_parser, function)
|
||||
|
||||
def command(init_parser: Callable[[ArgumentParser], None]=lambda p: None):
|
||||
def decorator(fn: Callable[..., int]):
|
||||
register_command(fn.__name__, init_parser, fn)
|
||||
return fn
|
||||
return decorator
|
||||
|
||||
|
||||
def init_commands(parser: ArgumentParser, parent_parsers: list[ArgumentParser]=[]):
|
||||
import bsv.command.info
|
||||
import bsv.command.init
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
metavar = "COMMAND",
|
||||
help = "Sub-command to run.",
|
||||
required = True,
|
||||
)
|
||||
|
||||
for name, command in commands.items():
|
||||
subparser = subparsers.add_parser(
|
||||
name,
|
||||
parents = parent_parsers,
|
||||
help = dedent(command.function.__doc__ or "").strip()
|
||||
)
|
||||
subparser.set_defaults(
|
||||
command = command.function,
|
||||
)
|
||||
command.init_parser(subparser)
|
||||
59
src/bsv/command/info.py
Normal file
59
src/bsv/command/info.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
from bsv import __version__
|
||||
from bsv.command import command
|
||||
from bsv.repository import Repository
|
||||
|
||||
|
||||
def init_parser(parser: ArgumentParser):
|
||||
parser.add_argument(
|
||||
"--verbose", "-v",
|
||||
action = "count",
|
||||
default = 0,
|
||||
help = "Verbosity level. Specify several times to increase verbosity.",
|
||||
dest = "verbosity",
|
||||
)
|
||||
|
||||
@command(init_parser)
|
||||
def info(repository_path: Path | None, verbosity: int=0) -> int:
|
||||
"""Print informations about bsv: config file used, known repository, file mapping...
|
||||
"""
|
||||
|
||||
print(f"bsv v{__version__}")
|
||||
|
||||
if repository_path is None:
|
||||
print("Repository path not found. Bsv is likely not setup on this device.")
|
||||
return 0
|
||||
else:
|
||||
print(f"Repository path: {repository_path}")
|
||||
|
||||
repo = Repository(repository_path)
|
||||
|
||||
print(f"Repository name: {repo.name}")
|
||||
|
||||
if repo.path_map:
|
||||
print("Path map: (bsv path <-> filesystem path)")
|
||||
for pair in sorted(repo.path_map):
|
||||
print(f" {pair.bsv} <-> {pair.fs}")
|
||||
else:
|
||||
print("Path map is empty.")
|
||||
|
||||
return 0
|
||||
105
src/bsv/command/init.py
Normal file
105
src/bsv/command/init.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from os import getlogin
|
||||
from pathlib import Path
|
||||
import platform
|
||||
|
||||
from bsv.command import command
|
||||
|
||||
|
||||
def init_parser(parser: ArgumentParser):
|
||||
parser.add_argument(
|
||||
"--name", "-d",
|
||||
help = "Name of the repository. Default to system hostname.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interactive", "-i",
|
||||
action = "store_true",
|
||||
help = "Prompt the user for configuration choices.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
type = Path,
|
||||
nargs = "?",
|
||||
help = "Path to a non-existing or empty folder where bsv data will be stored.",
|
||||
)
|
||||
|
||||
@command(init_parser)
|
||||
def init(
|
||||
repository_path: Path | None,
|
||||
destination: Path | None = None,
|
||||
name: str | None = None,
|
||||
interactive: bool = False,
|
||||
) -> int:
|
||||
"""Initialize a new bsv repository.
|
||||
"""
|
||||
from datetime import datetime as DateTime
|
||||
import tomlkit
|
||||
|
||||
if name is None:
|
||||
name = platform.node()
|
||||
|
||||
if destination is None:
|
||||
# TODO: Choose a sensible system-dependent path.
|
||||
destination = Path.cwd()
|
||||
|
||||
if interactive:
|
||||
name = input(f"Repository name: (default to {name})\n").strip() or name
|
||||
destination = Path(input(f"Destination: (default to {destination})\n").strip()) or destination
|
||||
if not destination.is_absolute():
|
||||
destination = Path.cwd() / destination
|
||||
|
||||
if not name:
|
||||
raise RuntimeError("repository name cannot be empty")
|
||||
if not destination.parent.exists():
|
||||
raise RuntimeError(f"destination directory {destination.parent} does not exists")
|
||||
if destination.exists() and not destination.is_dir():
|
||||
raise RuntimeError(f"destination {destination} exists but is not a directory")
|
||||
if destination.exists() and len(list(destination.iterdir())):
|
||||
raise RuntimeError(f"destination directory {destination} is not empty")
|
||||
|
||||
try:
|
||||
destination.mkdir(exist_ok=True)
|
||||
except:
|
||||
raise RuntimeError(f"failed to create destination directory {destination}")
|
||||
|
||||
bsv_table = tomlkit.table()
|
||||
bsv_table.add(tomlkit.comment("Name of the repository."))
|
||||
bsv_table.add(tomlkit.comment("Ideally, this should be unique among all connected repositories."))
|
||||
bsv_table.add("name", name)
|
||||
bsv_table.add(tomlkit.nl())
|
||||
bsv_table.add(tomlkit.comment("Mapping between bsv tree and the actual filesystem."))
|
||||
bsv_table.add("path_map", tomlkit.array())
|
||||
|
||||
doc = tomlkit.document()
|
||||
doc.add(tomlkit.comment("bsv repository configuration"))
|
||||
doc.add(tomlkit.comment(f"Created by {getlogin()} on {DateTime.now().isoformat()}."))
|
||||
doc.add(tomlkit.nl())
|
||||
doc.add("bsv", bsv_table)
|
||||
|
||||
config_path = destination / "bsv_config.toml"
|
||||
try:
|
||||
stream = config_path.open("w", encoding="utf-8")
|
||||
except:
|
||||
raise RuntimeError("failed to open configuration file {config_path}")
|
||||
|
||||
with stream:
|
||||
tomlkit.dump(doc, stream)
|
||||
|
||||
return 0
|
||||
83
src/bsv/main.py
Normal file
83
src/bsv/main.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
from bsv import __version__
|
||||
from bsv.command import init_commands
|
||||
|
||||
|
||||
def make_parser(
|
||||
prog: str = "",
|
||||
exit_on_error: bool = True,
|
||||
) -> ArgumentParser:
|
||||
parent_parser = ArgumentParser(add_help=False)
|
||||
parent_parser.add_argument(
|
||||
"--repository",
|
||||
type = Path,
|
||||
help = dedent("""
|
||||
Bsv repository path. Overides default paths and BSV_REPOSITORY environment variable.
|
||||
""").strip(),
|
||||
)
|
||||
|
||||
parser = ArgumentParser(
|
||||
prog = prog or os.path.basename(sys.argv[0]),
|
||||
description = dedent("""
|
||||
bsv - Backup, Synchronization, Versioning.
|
||||
""").strip(),
|
||||
parents = [parent_parser],
|
||||
exit_on_error = exit_on_error,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action = "version",
|
||||
version = f"bsv version {__version__}",
|
||||
)
|
||||
|
||||
init_commands(parser, [parent_parser])
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(
|
||||
args: list[str] | None = None,
|
||||
prog: str = "",
|
||||
exit_on_error: bool = True,
|
||||
) -> int:
|
||||
parser = make_parser(
|
||||
prog = prog or os.path.basename(sys.argv[0]),
|
||||
exit_on_error = exit_on_error,
|
||||
)
|
||||
arg_dict = vars(parser.parse_args(args or sys.argv[1:]))
|
||||
|
||||
repository_path: Path | None = arg_dict.pop("repository")
|
||||
if repository_path is None and "BSV_REPOSITORY" in os.environ:
|
||||
repository_path = Path(os.environ["BSV_REPOSITORY"])
|
||||
# else:
|
||||
# for path in get_config_dirs():
|
||||
# maybe_config_path = path / "config.toml"
|
||||
# if maybe_config_path.is_file():
|
||||
# config_path = maybe_config_path
|
||||
# break
|
||||
|
||||
command = arg_dict.pop("command")
|
||||
|
||||
return command(repository_path=repository_path, **arg_dict)
|
||||
86
src/bsv/repository.py
Normal file
86
src/bsv/repository.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path, PurePosixPath
|
||||
import platform
|
||||
import tomllib
|
||||
from typing import Any
|
||||
|
||||
from bsv import __version__
|
||||
|
||||
|
||||
class Repository:
|
||||
_path: Path
|
||||
_name: str
|
||||
_path_map: list[PathPair]
|
||||
# _remotes: list[object]
|
||||
|
||||
def __init__(self, path: Path):
|
||||
self._path = path
|
||||
|
||||
with self.config_file.open("rb") as stream:
|
||||
config = tomllib.load(stream)
|
||||
|
||||
bsv = config.get("bsv", {})
|
||||
|
||||
self._name = bsv.get("name") or platform.node()
|
||||
|
||||
self._path_map = [
|
||||
PathPair.from_obj(pair)
|
||||
for pair in bsv.get("path_map", [])
|
||||
]
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def config_file(self) -> Path:
|
||||
return self.path / "bsv_config.toml"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def path_map(self) -> list[PathPair]:
|
||||
return list(self._path_map)
|
||||
|
||||
|
||||
class PathPair:
|
||||
bsv: PurePosixPath
|
||||
fs: Path
|
||||
|
||||
def __init__(self, bsv: PurePosixPath, fs: Path):
|
||||
self.bsv = bsv
|
||||
self.fs = fs
|
||||
|
||||
@classmethod
|
||||
def from_obj(cls, obj: dict[str, Any]) -> PathPair:
|
||||
bsv = PurePosixPath(obj["bsv"])
|
||||
fs = Path(obj["fs"])
|
||||
|
||||
if not bsv.is_absolute() or not fs.is_absolute():
|
||||
raise ValueError("paths in path_map must be absolute")
|
||||
|
||||
return cls(
|
||||
bsv = obj["bsv"],
|
||||
fs = obj["fs"],
|
||||
)
|
||||
|
||||
def __lt__(self, rhs: PathPair) -> bool:
|
||||
return self.bsv < rhs.bsv
|
||||
16
src/bsv/simple_cas/__init__.py
Normal file
16
src/bsv/simple_cas/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# bsv - Backup, Synchronization, Versioning
|
||||
# Copyright (C) 2023 Simon Boyé
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from __future__ import annotations
|
||||
Reference in New Issue
Block a user