Skip to content

Commit

Permalink
Create prototpye crater cli
Browse files Browse the repository at this point in the history
Create a prototype cli tool called crater. This tool is responsible
for creating Hipaacrate files, which store all the information for a given
Crate. To support this, create the Crate API, and include tests.
  • Loading branch information
Miller Wilt committed Jul 24, 2018
1 parent 0aeb2d6 commit 0ae85d5
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ name = "pypi"

typing = "*"
typing-extensions = "*"
pyyaml = "*"
click = "*"
filelock = "*"


[dev-packages]
Expand Down
32 changes: 31 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions hipaacrates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import crate
from . import version

__version__ = version.__version__
104 changes: 104 additions & 0 deletions hipaacrates/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import hashlib
import os
import tempfile

import click
from filelock import FileLock, Timeout

from . import crate
from . import version

def _get_lock_file_name() -> str:
return os.path.join(
tempfile.gettempdir(),
_hash_cwd(),
)

def _hash_cwd() -> str:
return hashlib.sha256(os.getcwdb()).hexdigest()

CRATE_FILE = "Hipaacrate"
LOCK_FILE = FileLock(_get_lock_file_name(), timeout=0.1)

@click.group()
@click.version_option(version.__version__, prog_name="crater")
def crater():
pass

@crater.command()
@click.argument("bundles", nargs=-1, metavar="BUNDLE [BUNDLE]...")
@click.pass_context
def add(ctx, bundles):
if not bundles:
ctx.fail("Expected one or more Bundle name")

try:
LOCK_FILE.acquire()
c = crate.read_yaml(CRATE_FILE)
except FileNotFoundError:
ctx.fail("Could not open the Hipaacrate file - have you run 'crater init'?")
except Timeout:
ctx.fail("The Hipaacrate file is currently locked - a separate process must be using it")
else:
combined = set(c.bundles) | set(bundles)
c.bundles = list(combined)
c.bundles.sort()
c.to_yaml(CRATE_FILE)
LOCK_FILE.release()

@crater.command()
@click.option("-n", "--name", prompt=True, help="Crate name", metavar="NAME")
@click.option("-v", "--version", prompt=True, help="Crate version", metavar="VERSION")
@click.pass_context
def init(ctx, name, version) -> None:
try:
LOCK_FILE.acquire()
except Timeout:
ctx.fail("The Hipaacrate file is currently locked - a separate process must be using it")
else:
c = crate.new(name, version)
c.to_yaml(CRATE_FILE)
LOCK_FILE.release()

@crater.command()
@click.argument("bundles", nargs=-1, metavar="BUNDLE [BUNDLE]...")
@click.pass_context
def remove(ctx, bundles):
if not bundles:
ctx.fail("Expected one or more Bundle name")

try:
LOCK_FILE.acquire()
c = crate.read_yaml(CRATE_FILE)
except FileNotFoundError:
ctx.fail("Could not open the Hipaacrate file - have you run 'crater init'?")
except Timeout:
ctx.fail("The Hipaacrate file is currently locked - a separate process must be using it")
else:
to_remove = set(bundles)
existing = set(c.bundles)
if not existing >= to_remove:
ctx.fail("No Bundles named {} added".format(", ".join(to_remove - existing)))
else:
c.bundles = list(existing - to_remove)
c.bundles.sort()
c.to_yaml(CRATE_FILE)
LOCK_FILE.release()

@crater.command()
@click.argument("value", type=click.Choice(["author", "bundles", "name", "version"]))
@click.pass_context
def show(ctx, value) -> None:
try:
LOCK_FILE.acquire()
c = crate.read_yaml(CRATE_FILE)
except FileNotFoundError:
ctx.fail("Could not open the Hipaacrate file - have you run 'crater init'?")
except Timeout:
ctx.fail("The Hipaacrate file is currently locked - a separate process must be using it")
else:
click.echo(getattr(c, value))
LOCK_FILE.release()

if __name__ == '__main__':
crater()
60 changes: 60 additions & 0 deletions hipaacrates/crate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from typing import Iterable, List

import yaml

class Crate(object):
def __init__(self, name: str, version: str, author: str, bundles: List[str]) -> None:
self.author = author
self.bundles = bundles
self.name = name
self.version = version

def __str__(self) -> str:
return "Crate(name={}, version={}, author={}, bundles={}".format(
self.name, self.version, self.author, self.bundles,
)

def to_yaml(self, filepath: str = None) -> str:
yaml_text = yaml.safe_dump(dict(
name=self.name,
author=self.author,
version=self.version,
bundles=self.bundles,
), default_flow_style=False)

if filepath is not None:
with open(filepath, "w") as f:
f.write(yaml_text)

return yaml_text

def new(name: str, version: str, author: str = None, bundles: Iterable[str] = None) -> Crate:
"""
Create a new Crate
"""
if author is None:
author = ""
if bundles is None:
bundles = []
else:
bundles = list(bundles)
return Crate(name=name, version=version, author=author, bundles=bundles)

def parse(text: str) -> Crate:
"""
Parse and load a Crate from a YAML string
"""
parsed = yaml.safe_load(text)
return Crate(
name=parsed["name"],
version=parsed["version"],
author=parsed.get("author"),
bundles=parsed.get("bundles")
)

def read_yaml(filepath: str) -> Crate:
"""
Load a Crate from a YAML file
"""
with open(filepath) as f:
return parse(f.read())
3 changes: 3 additions & 0 deletions hipaacrates/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Declare package version."""

__version__ = "0.0.1"
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def find_version(*file_paths):
],
entry_points={
"console_scripts": [
"crater = hipaacrates.__main__:crater"
],
},
classifiers=[
Expand Down
89 changes: 89 additions & 0 deletions tests/crates_file_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import pytest
import yaml

from hipaacrates import crate

def test_crate_creation_minimal():
name = "mycrate"
version = "0.0.1"

actual = crate.new(name=name, version=version)
assert isinstance(actual, crate.Crate)

assert actual.name == name
assert actual.version == version
assert actual.bundles == []
assert actual.author == ""

def test_crate_creation_with_bundles():
name = "mycrate"
version = "0.0.1"
bundles = ["mybundle", "foobundle"]

actual = crate.new(name=name, version=version, bundles=bundles)
assert isinstance(actual, crate.Crate)

assert actual.name == name
assert actual.version == version
assert actual.bundles == bundles
assert actual.author == ""

def test_crate_creation_with_author():
name = "mycrate"
version = "0.0.1"
author = "me"

actual = crate.new(name=name, version=version, author=author)
assert isinstance(actual, crate.Crate)

assert actual.name == name
assert actual.version == version
assert actual.bundles == []
assert actual.author == author

def test_crate_to_yaml():
name = "mycrate"
version = "0.0.1"
author = "me"
bundles = ["mybundle", "foobundle"]

c = crate.new(name=name, version=version, author=author, bundles=bundles)
actual = c.to_yaml()

items = [s.strip() for s in actual.splitlines()]
assert "name: {}".format(name) in items
assert "version: {}".format(version) in items
assert "author: {}".format(author) in items
assert "bundles:" in items
assert "- mybundle" in items
assert "- foobundle" in items

@pytest.fixture
def cratetext():
return """
name: mycrate
version: 0.0.1
author: me
bundles:
- mybundle
- foobundle
"""

def test_crate_parse(cratetext):
c = crate.parse(cratetext)
assert isinstance(c, crate.Crate)

assert c.name == "mycrate"
assert c.version == "0.0.1"
assert c.author == "me"
assert c.bundles == ["mybundle", "foobundle"]

def test_read_yaml(tmpdir, cratetext):
p = tmpdir.join("example.yaml")
p.write(cratetext)

c = crate.read_yaml(str(p))
assert c.name == "mycrate"
assert c.version == "0.0.1"
assert c.author == "me"
assert c.bundles == ["mybundle", "foobundle"]

0 comments on commit 0ae85d5

Please sign in to comment.