aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMykyta Holubakha <hilobakho@gmail.com>2017-06-19 04:24:06 +0300
committerMykyta Holubakha <hilobakho@gmail.com>2017-06-19 04:24:06 +0300
commit7334e41998fb99dfd10dcf9fd977967ceb0f79f2 (patch)
tree07d821e70b1d72de917d0f6470bbbc184afc159e
parentmajor refactoring of pomu package source module (diff)
downloadpomu-7334e41998fb99dfd10dcf9fd977967ceb0f79f2.tar.gz
pomu-7334e41998fb99dfd10dcf9fd977967ceb0f79f2.tar.bz2
pomu-7334e41998fb99dfd10dcf9fd977967ceb0f79f2.zip
Numerous improvements and fixes
Documented most of the functions and classes. Added an option to fetch a package into a specified directory. Added a merge_into method to the Package class, which would merge it into a directory, and refactored repo::merge_package to use that. Extended the package class to store category, version and slot metadata. Added tests of the portage package source module.
-rw-r--r--pomu/cli.py15
-rw-r--r--pomu/package.py48
-rw-r--r--pomu/repo/repo.py7
-rw-r--r--pomu/source/manager.py18
-rw-r--r--pomu/source/portage.py9
-rw-r--r--pomu/util/cache.py3
-rw-r--r--pomu/util/fs.py1
-rw-r--r--pomu/util/str.py6
-rw-r--r--tests/test_dispatch.py7
9 files changed, 97 insertions, 17 deletions
diff --git a/pomu/cli.py b/pomu/cli.py
index c17840e..4b09000 100644
--- a/pomu/cli.py
+++ b/pomu/cli.py
@@ -1,6 +1,8 @@
"""pomu command line interface"""
import click
+from os import path
+
from pomu.repo.init import init_plain_repo, init_portage_repo
from pomu.repo.repo import portage_repo_path, portage_repos, pomu_active_repo
from pomu.source import dispatcher
@@ -90,10 +92,17 @@ def uninstall(uri, package):
@main.command()
@click.argument('package', required=True)
-@needs_repo
-def fetch(package):
+@click.option('--into', default=None,
+ help='Specify fetch destination')
+def fetch(package, into):
pkg = dispatcher.get_package(package).expect()
- print('Fetched package', pkg.name, 'at', pkg.root)
+ print('Fetched package', pkg, 'at', pkg.root)
+ print('Contents:')
+ for f in pkg.files:
+ print(' ', path.join(*f))
+ if into:
+ pkg.merge_into(into).expect()
+ print('Copied to', into, 'successfully')
def main_():
try:
diff --git a/pomu/package.py b/pomu/package.py
index 470d76b..7b7ec55 100644
--- a/pomu/package.py
+++ b/pomu/package.py
@@ -4,12 +4,14 @@ A package can be installed into a repository.
A package is supposed to be created by a package source from a set of files.
"""
-from os import path, walk
+from os import path, walk, makedirs
+from shutil import copy2
from pomu.util.fs import strip_prefix
+from pomu.util.result import Result
class Package():
- def __init__(self, name, root, d_path=None, files=None):
+ def __init__(self, name, root, category=None, version=None, slot='0', d_path=None, files=None):
"""
Parameters:
name - name of the package
@@ -17,9 +19,13 @@ class Package():
d_path - a subdirectory of the root path, which would be sourced recursively.
could be a relative or an absolute path
files - a set of files to build a package from
+ category, version, slot - self-descriptive
"""
self.name = name
self.root = root
+ self.category = category
+ self.version = version
+ self.slot = slot
self.files = []
if d_path is None and files is None:
self.d_path = None
@@ -29,20 +35,44 @@ class Package():
self.read_path(path.join(self.root, self.d_path))
elif d_path is None:
for f in files:
- self.files.append(self.strip_root(f))
+ self.files.append(path.split(self.strip_root(f)))
else:
raise ValueError('You should specify either d_path, or files')
def strip_root(self, d_path):
- # the path should be either relative, or a child of root
- if d_path.startswith('/'):
- if path.commonprefix(d_path, self.root) != self.root:
- raise ValueError('Path should be a subdirectory of root')
- return strip_prefix(strip_prefix(d_path, self.root), '/')
- return d_path
+ """Strip the root component of a path"""
+ # the path should be either relative, or a child of root
+ if d_path.startswith('/'):
+ if path.commonprefix(d_path, self.root) != self.root:
+ raise ValueError('Path should be a subdirectory of root')
+ return strip_prefix(strip_prefix(d_path, self.root), '/')
+ return d_path
def read_path(self, d_path):
+ """Recursively add files from a subtree"""
for wd, dirs, files in walk(d_path):
wd = self.strip_root(wd)
self.files.extend([(wd, f) for f in files])
+ def merge_into(self, dst):
+ """Merges contents of the package into a specified directory"""
+ for wd, f in self.files:
+ dest = path.join(dst, wd)
+ try:
+ makedirs(dest, exist_ok=True)
+ copy2(path.join(self.root, wd, f), dest)
+ except PermissionError:
+ return Result.Err('You do not have enough permissions')
+ return Result.Ok()
+
+
+ def __str__(self):
+ s = ''
+ if self.category:
+ s = self.category + '/'
+ s += self.name
+ if self.version:
+ s += '-' + self.version
+ if self.slot != '0':
+ s += self.slot
+ return s
diff --git a/pomu/repo/repo.py b/pomu/repo/repo.py
index 076b2b7..6434ed3 100644
--- a/pomu/repo/repo.py
+++ b/pomu/repo/repo.py
@@ -25,11 +25,10 @@ class Repository():
return path.join(self.root, 'metadata/pomu')
def merge(self, package):
+ """Merge a package into the repository"""
r = self.repo
+ package.merge(self.root).expect('Failed to merge package')
for wd, f in package.files:
- dst = path.join(self.root, wd)
- makedirs(dst)
- copy2(path.join(package.root, wd, f), dst)
r.index.add(path.join(dst, f))
with open(path.join(self.pomu_dir, package.name), 'w') as f:
f.write('{}/{}'.format(wd, f))
@@ -38,6 +37,7 @@ class Repository():
return Result.Ok('Merged package ' + package.name + ' successfully')
def unmerge(self, package):
+ """Remove a package (by contents) from the repository"""
r = self.repo
for wd, f in package.files:
dst = path.join(self.root, wd)
@@ -52,6 +52,7 @@ class Repository():
return Result.Ok('Removed package ' + package.name + ' successfully')
def remove_package(self, name):
+ """Remove a package (by name) from the repository"""
r = self.repo
pf = path.join(self.pomu_dir, name)
if not path.isfile(pf):
diff --git a/pomu/source/manager.py b/pomu/source/manager.py
index f36eb90..9d3e2de 100644
--- a/pomu/source/manager.py
+++ b/pomu/source/manager.py
@@ -37,12 +37,22 @@ class PackageDispatcher():
self.handlers = []
def source(self, cls):
+ """
+ A decorator to mark package source modules
+ It would register all the methods of the class marked by @handler
+ with the dispatcher.
+ """
for m, obj in inspect.getmembers(cls):
if isinstance(obj, self.handler._handler):
self.register_package_handler(cls, obj.handler, obj.priority)
return cls
class handler():
+ """
+ A decorator to denote package source module handler, which
+ should attempt to parse a package descriptor. If it succeeds,
+ the result would be passed to the module for further processing.
+ """
class _handler():
def __init__(self, handler):
self.handler = handler
@@ -58,6 +68,10 @@ class PackageDispatcher():
return x
def register_package_handler(self, source, handler, priority):
+ """
+ Register a package handler for a specified source.
+ Handlers with lower priority get called first.
+ """
i = 0
for i in range(len(self.handlers)):
if self.handlers[0][0] > priority:
@@ -65,12 +79,14 @@ class PackageDispatcher():
self.handlers.insert(i, (priority, source, handler))
def get_package_source(self, uri):
+ """Get a source which accepts the package"""
for priority, source, handler in self.handlers:
if handler(uri).is_ok():
return source
return None
def get_package(self, uri):
+ """Fetch a package specified by the descriptor"""
for priority, source, handler in self.handlers:
res = handler(uri)
if res.is_ok():
@@ -78,9 +94,11 @@ class PackageDispatcher():
return Result.Err('No handler found for package ' + uri)
def install_package(self, uri):
+ """Install a package specified by the descriptor"""
pkg = self.get_package(uri).unwrap()
return pomu_active_repo().merge(pkg)
def uninstall_package(self, uri):
+ """Uninstall a package specified by the descriptor"""
pkg = self.get_package(uri).unwrap()
return pomu_active_repo().unmerge(pkg)
diff --git a/pomu/source/portage.py b/pomu/source/portage.py
index aaf5c1c..6e58873 100644
--- a/pomu/source/portage.py
+++ b/pomu/source/portage.py
@@ -26,6 +26,7 @@ class PortagePackage():
def fetch(self):
return Package(self.name, portage_repo_path(self.repo),
+ category=self.category, version=self.version, slot=self.slot,
files=[path.join(self.category, self.name, 'metadata.xml'),
path.join(self.category, self.name, self.name + '-' + self.version + '.ebuild')])
@@ -38,6 +39,7 @@ misc_dirs = ['profiles', 'licenses', 'eclass', 'metadata', 'distfiles', 'package
@dispatcher.source
class PortageSource():
+ """The source module responsible for fetching portage packages"""
@dispatcher.handler(priority=5)
def parse_spec(uri, repo=None):
# dev-libs/openssl-0.9.8z_p8-r100:0.9.8::gentoo
@@ -77,6 +79,10 @@ class PortageSource():
def sanity_check(repo, category, name, vernum, suff, rev, slot):
+ """
+ Checks whether a package descriptor is valid and corresponds
+ to a package in a configured portage repository
+ """
if not name:
return False
if repo and repo not in list(portage_repos()):
@@ -95,10 +101,11 @@ def sanity_check(repo, category, name, vernum, suff, rev, slot):
def ver_str(vernum, suff, rev):
+ """Gets the string representation of the version"""
return vernum + (suff if suff else '') + (rev if rev else '')
def best_ver(repo, category, name, ver=None):
- """Gets the best (newest) version of a package in the repo"""
+ """Gets the best (newest) version of a package in the repo"""
ebuilds = [category + '/' + name + x[len(name):-7] for x in
os.listdir(path.join(portage_repo_path(repo), category, name))
if x.endswith('.ebuild')]
diff --git a/pomu/util/cache.py b/pomu/util/cache.py
index b1d09b2..93502c5 100644
--- a/pomu/util/cache.py
+++ b/pomu/util/cache.py
@@ -2,6 +2,9 @@
Caches the return value of a function -> Result, regardless of input params
"""
class cached():
+ """
+ A decorator to make the function cache its return value, regardless of input
+ """
def __init__(self, fun):
self.fun = fun
def __call__(self, *args):
diff --git a/pomu/util/fs.py b/pomu/util/fs.py
index 3e69e2e..5d25ec5 100644
--- a/pomu/util/fs.py
+++ b/pomu/util/fs.py
@@ -11,5 +11,6 @@ def strip_prefix(string, prefix):
return string
def remove_file(repo, dst):
+ """Removes a file from a repository"""
repo.index.remove(dst)
os.remove(dst)
diff --git a/pomu/util/str.py b/pomu/util/str.py
index 419425b..96a7c81 100644
--- a/pomu/util/str.py
+++ b/pomu/util/str.py
@@ -1,4 +1,10 @@
+"""String processing utilities"""
def pivot(string, idx, keep_pivot=True):
+ """
+ A function to split a string in two, pivoting at string[idx].
+ If keep_pivot is set, the pivot character is included in the second string.
+ Alternatively, it is omitted.
+ """
if keep_pivot:
return (string[:idx], string[idx:])
else:
diff --git a/tests/test_dispatch.py b/tests/test_dispatch.py
index 7ccf830..5ccba6d 100644
--- a/tests/test_dispatch.py
+++ b/tests/test_dispatch.py
@@ -12,7 +12,7 @@ from pomu.util.result import Result
@dispatcher.source
class DummySource():
- @dispatcher.handler
+ @dispatcher.handler(priority=3)
@classmethod
def parse(cls, uri):
if uri.startswith('/'):
@@ -33,6 +33,7 @@ class DispatcherTests(unittest.TestCase):
def testDispatch(self):
self.assertEqual(dispatcher.get_package_source('/test').unwrap(), 'test')
self.assertTrue(dispatcher.get_package_source('test').is_err())
+ self.assertTrue(dispatcher.get_package('sys-apps/portage').is_ok())
def testFetch(self):
pkg = dispatcher.get_package('/test').unwrap()
@@ -64,6 +65,10 @@ class InstallTests(unittest.TestCase):
pkg = Package('test', self.source_path)
self.repo.merge(pkg).expect()
+ def testPortagePkg(self):
+ pkg = dispatcher.get_package('sys-apps/portage').expect()
+ self.repo.merge(pkg).expect()
+
def testPkgUnmerge(self):
pkg = Package('test', self.source_path)
self.repo.merge(pkg).expect()