Skip to content

Commit

Permalink
Merge pull request #17 from njgheorghita/uninstall
Browse files Browse the repository at this point in the history
Uninstall & list packages
  • Loading branch information
njgheorghita authored May 22, 2019
2 parents 6b8e84c + 24bb48b commit 85122af
Show file tree
Hide file tree
Showing 37 changed files with 277 additions and 99 deletions.
2 changes: 1 addition & 1 deletion ethpm_cli/_utils/ipfs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ethpm.backends.ipfs import BaseIPFSBackend, InfuraIPFSBackend, LocalIPFSBackend


def get_ipfs_backend(ipfs: bool = None) -> BaseIPFSBackend:
def get_ipfs_backend(ipfs: bool = False) -> BaseIPFSBackend:
if ipfs:
return LocalIPFSBackend()
return InfuraIPFSBackend()
26 changes: 26 additions & 0 deletions ethpm_cli/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from argparse import Namespace
from pathlib import Path

from ethpm_cli._utils.ipfs import get_ipfs_backend
from ethpm_cli.constants import ETHPM_DIR_NAME


class Config:
"""
Class to manage CLI config options
- IPFS Backend
- Target ethpm_dir
"""

def __init__(self, args: Namespace) -> None:
if "local_ipfs" in args:
self.ipfs_backend = get_ipfs_backend(args.local_ipfs)
else:
self.ipfs_backend = get_ipfs_backend()

if args.ethpm_dir is None:
self.ethpm_dir = Path.cwd() / ETHPM_DIR_NAME
if not self.ethpm_dir.is_dir():
self.ethpm_dir.mkdir()
else:
self.ethpm_dir = args.ethpm_dir
7 changes: 5 additions & 2 deletions ethpm_cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

from ethpm_cli import CLI_ASSETS_DIR

ETHPM_DIR_NAME = "ethpm_packages"
ETHPM_ASSETS_DIR = "packages"
ETHPM_DIR_NAME = "_ethpm_packages"
IPFS_ASSETS_DIR = "ipfs"
LOCKFILE_NAME = "ethpm.lock"
SRC_DIR_NAME = "_src"


VERSION_RELEASE_ABI = json.loads((CLI_ASSETS_DIR / "1.0.1.json").read_text())[
"contract_types"
]["Log"]["abi"]
Expand Down
132 changes: 102 additions & 30 deletions ethpm_cli/install.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
from argparse import Namespace
import json
import logging
import os
from pathlib import Path
import shutil
import tempfile
from typing import Iterable, Tuple
from typing import Any, Dict, Iterable, NamedTuple, Tuple

from eth_utils import to_dict, to_text
from eth_utils.toolz import assoc
from eth_utils import to_dict, to_text, to_tuple
from eth_utils.toolz import assoc, dissoc
from ethpm.backends.ipfs import BaseIPFSBackend
from ethpm.utils.ipfs import is_ipfs_uri

from ethpm_cli._utils.ipfs import get_ipfs_backend
from ethpm_cli.constants import ETHPM_DIR_NAME
from ethpm_cli.config import Config
from ethpm_cli.constants import ETHPM_DIR_NAME, LOCKFILE_NAME, SRC_DIR_NAME
from ethpm_cli.exceptions import InstallError
from ethpm_cli.package import Package
from ethpm_cli.validation import validate_parent_directory


class Config:
"""
Class to manage CLI config options
- IPFS Backend
- Target ethpm_dir
"""

def __init__(self, args: Namespace) -> None:
self.ipfs_backend = get_ipfs_backend(args.local_ipfs)
if args.ethpm_dir is None:
self.ethpm_dir = Path.cwd() / ETHPM_DIR_NAME
if not self.ethpm_dir.is_dir():
self.ethpm_dir.mkdir()
else:
self.ethpm_dir = args.ethpm_dir
logger = logging.getLogger("ethpm_cli.install")


def install_package(pkg: Package, config: Config) -> None:
if os.path.exists(config.ethpm_dir / pkg.alias):
if is_package_installed(pkg.alias, config):
raise InstallError(
"Installation conflict: A directory or file already exists at the install location "
f"for the package '{pkg.manifest['package_name']}' aliased to '{pkg.alias}' on the "
Expand All @@ -47,11 +32,90 @@ def install_package(pkg: Package, config: Config) -> None:
tmp_pkg_dir = Path(tempfile.mkdtemp())
write_pkg_installation_files(pkg, tmp_pkg_dir, config.ipfs_backend)

# Copy temp pacakge directory to ethpm dir namespace
# Copy temp package directory to ethpm dir namespace
dest_pkg_dir = config.ethpm_dir / pkg.alias
validate_parent_directory(config.ethpm_dir, dest_pkg_dir)
shutil.copytree(tmp_pkg_dir, dest_pkg_dir)
update_ethpm_lock(pkg, (config.ethpm_dir / "ethpm.lock"))
install_to_ethpm_lock(pkg, (config.ethpm_dir / LOCKFILE_NAME))


class InstalledPackageTree(NamedTuple):
depth: int
path: Path
manifest: Dict[str, Any]
children: Tuple[Any, ...] # Expects InstalledPackageTree
content_hash: str

@property
def package_name(self) -> str:
return self.manifest["package_name"]

@property
def package_version(self) -> str:
return self.manifest["version"]

@property
def format_for_display(self) -> str:
prefix = "- " * self.depth
main_info = f"{prefix}{self.package_name}=={self.package_version}"
hash_info = f"({self.content_hash})"
if self.children:
children = "\n" + "\n".join(
(child.format_for_display for child in self.children)
)
else:
children = ""
return f"{main_info} --- {hash_info}{children}"


def list_installed_packages(config: Config) -> None:
installed_packages = [
get_installed_package_tree(base_dir)
for base_dir in config.ethpm_dir.iterdir()
if base_dir.is_dir()
]
for pkg in sorted(installed_packages):
logger.info(pkg.format_for_display)


def get_installed_package_tree(base_dir: Path, depth: int = 0) -> InstalledPackageTree:
manifest = json.loads((base_dir / "manifest.json").read_text())
ethpm_lock = json.loads((base_dir.parent / LOCKFILE_NAME).read_text())
content_hash = ethpm_lock[base_dir.name]["resolved_uri"]
dependency_dirs = get_dependency_dirs(base_dir)
children = tuple(
get_installed_package_tree(dependency_dir, depth + 1)
for dependency_dir in dependency_dirs
)
return InstalledPackageTree(depth, base_dir, manifest, children, content_hash)


@to_tuple
def get_dependency_dirs(base_dir: Path) -> Iterable[Path]:
dep_dir = base_dir / ETHPM_DIR_NAME
if dep_dir.is_dir():
for ddir in dep_dir.iterdir():
if ddir.is_dir():
yield ddir


def is_package_installed(package_name: str, config: Config) -> bool:
return os.path.exists(config.ethpm_dir / package_name)


def uninstall_package(package_name: str, config: Config) -> None:
if not is_package_installed(package_name, config):
raise InstallError(
f"No package with the name {package_name} found installed under {config.ethpm_dir}."
)

tmp_pkg_dir = Path(tempfile.mkdtemp()) / ETHPM_DIR_NAME
shutil.copytree(config.ethpm_dir, tmp_pkg_dir)
shutil.rmtree(tmp_pkg_dir / package_name)
uninstall_from_ethpm_lock(package_name, (tmp_pkg_dir / LOCKFILE_NAME))

shutil.rmtree(config.ethpm_dir)
tmp_pkg_dir.replace(config.ethpm_dir)


def write_pkg_installation_files(
Expand All @@ -62,21 +126,21 @@ def write_pkg_installation_files(

write_sources_to_disk(pkg, tmp_pkg_dir, ipfs_backend)
write_build_deps_to_disk(pkg, tmp_pkg_dir, ipfs_backend)
tmp_ethpm_lock = tmp_pkg_dir.parent / "ethpm.lock"
update_ethpm_lock(pkg, tmp_ethpm_lock)
tmp_ethpm_lock = tmp_pkg_dir.parent / LOCKFILE_NAME
install_to_ethpm_lock(pkg, tmp_ethpm_lock)


def write_sources_to_disk(
pkg: Package, pkg_dir: Path, ipfs_backend: BaseIPFSBackend
) -> None:
sources = resolve_sources(pkg, ipfs_backend)
for path, source_contents in sources.items():
target_file = pkg_dir / "src" / path
target_file = pkg_dir / SRC_DIR_NAME / path
target_dir = target_file.parent
if not target_dir.is_dir():
target_dir.mkdir(parents=True)
target_file.touch()
validate_parent_directory((pkg_dir / "src"), target_file)
validate_parent_directory((pkg_dir / SRC_DIR_NAME), target_file)
target_file.write_text(source_contents)


Expand Down Expand Up @@ -107,7 +171,7 @@ def write_build_deps_to_disk(
write_pkg_installation_files(dep_pkg, tmp_dep_dir, ipfs_backend)


def update_ethpm_lock(pkg: Package, ethpm_lock: Path) -> None:
def install_to_ethpm_lock(pkg: Package, ethpm_lock: Path) -> None:
if ethpm_lock.is_file():
old_lock = json.loads(ethpm_lock.read_text())
else:
Expand All @@ -116,3 +180,11 @@ def update_ethpm_lock(pkg: Package, ethpm_lock: Path) -> None:
new_pkg_data = pkg.generate_ethpm_lock()
new_lock = assoc(old_lock, pkg.alias, new_pkg_data)
ethpm_lock.write_text(f"{json.dumps(new_lock, sort_keys=True, indent=4)}\n")


def uninstall_from_ethpm_lock(package_name: str, ethpm_lock: Path) -> None:
old_lock = json.loads(ethpm_lock.read_text())
new_lock = dissoc(old_lock, package_name)
temp_ethpm_lock = Path(tempfile.NamedTemporaryFile().name)
temp_ethpm_lock.write_text(f"{json.dumps(new_lock, sort_keys=True, indent=4)}\n")
temp_ethpm_lock.replace(ethpm_lock)
59 changes: 8 additions & 51 deletions ethpm_cli/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import argparse
import logging
from pathlib import Path
import sys

from eth_utils import humanize_hash
Expand All @@ -10,9 +9,11 @@
from web3.providers.auto import load_provider_from_uri

from ethpm_cli._utils.xdg import get_xdg_ethpmcli_root
from ethpm_cli.config import Config
from ethpm_cli.constants import INFURA_HTTP_URI
from ethpm_cli.install import Config, install_package
from ethpm_cli.install import install_package, list_installed_packages
from ethpm_cli.package import Package
from ethpm_cli.parser import ETHPM_PARSER
from ethpm_cli.scraper import scrape
from ethpm_cli.validation import validate_install_cli_args

Expand Down Expand Up @@ -53,56 +54,9 @@ def scraper(args: argparse.Namespace) -> None:
)


def parse_arguments() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="ethpm-cli")
subparsers = parser.add_subparsers(help="commands", dest="command")

scrape_parser = subparsers.add_parser(
"scrape", help="Scrape for new VersionRelease events."
)
scrape_parser.add_argument(
"--ipfs-dir",
dest="ipfs_dir",
action="store",
type=Path,
help="path to specific IPFS assets dir.",
)
scrape_parser.add_argument(
"--start-block",
dest="start_block",
action="store",
type=int,
help="Block number to begin scraping from.",
)
install_parser = subparsers.add_parser("install", help="Install uri")
install_parser.add_argument(
"uri",
action="store",
type=str,
help="IPFS / Github / Registry URI of package you want to install.",
)
install_parser.add_argument(
"--ethpm-dir",
dest="ethpm_dir",
action="store",
type=Path,
help="Path to specific ethpm_packages dir.",
)
install_parser.add_argument(
"--alias", action="store", type=str, help="Alias for installing package."
)
install_parser.add_argument(
"--local-ipfs",
dest="local_ipfs",
action="store_true",
help="Flag to use locally running IPFS node.",
)
return parser


def main() -> None:
logger = setup_cli_logger()
parser = parse_arguments()
parser = ETHPM_PARSER
logger.info(f"EthPM CLI v{__version__}\n")

args = parser.parse_args()
Expand All @@ -117,8 +71,11 @@ def main() -> None:
args.uri,
config.ethpm_dir,
)
if args.command == "scrape":
elif args.command == "scrape":
scraper(args)
elif args.command == "list":
config = Config(args)
list_installed_packages(config)
else:
parser.error(
"%s is an invalid command. Use `ethpmcli --help` to "
Expand Down
62 changes: 62 additions & 0 deletions ethpm_cli/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import argparse
from pathlib import Path


def get_ethpm_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="ethpm-cli")
subparsers = parser.add_subparsers(help="commands", dest="command")

scrape_parser = subparsers.add_parser(
"scrape", help="Scrape for new VersionRelease events."
)
scrape_parser.add_argument(
"--ipfs-dir",
dest="ipfs_dir",
action="store",
type=Path,
help="path to specific IPFS assets dir.",
)
scrape_parser.add_argument(
"--start-block",
dest="start_block",
action="store",
type=int,
help="Block number to begin scraping from.",
)

install_parser = subparsers.add_parser("install", help="Install uri")
install_parser.add_argument(
"uri",
action="store",
type=str,
help="IPFS / Github / Registry URI of package you want to install.",
)
install_parser.add_argument(
"--ethpm-dir",
dest="ethpm_dir",
action="store",
type=Path,
help="Path to specific ethpm_packages dir.",
)
install_parser.add_argument(
"--alias", action="store", type=str, help="Alias for installing package."
)
install_parser.add_argument(
"--local-ipfs",
dest="local_ipfs",
action="store_true",
help="Flag to use locally running IPFS node.",
)

list_parser = subparsers.add_parser("list", help="List installed packages")
list_parser.add_argument(
"--ethpm-dir",
dest="ethpm_dir",
action="store",
type=Path,
help="Path to specific ethpm_packages dir.",
)
return parser


ETHPM_PARSER = get_ethpm_parser()
Loading

0 comments on commit 85122af

Please sign in to comment.