"""
Programatic interface to package structure.
Use the :class:`Package` class.
"""
# pylint: disable=too-many-instance-attributes,too-many-locals,R0903,line-too-long
import configparser
from io import StringIO
from dkfileutils.path import Path
[docs]class DefaultPackage:
"""Default package directory layout (consider this abstract, both in the
sense that this class is abstract and in the sense that this is the
attribute names of this object, not neccessarily the actual directory
name)
::
<parent> # self.location (abspath)
|--<name> # self.root (abspath), self.package_name
|-- <name> # <name> == self.name, self.source
| |-- static # self.django_static
| `-- templates # self.django_templates
|-- js # self.source_js
|-- less # self.source_less
|-- docs # self.docs
+-- tests # self.tests
|-- build # self.build
| |-- coverage # self.build_coverage
| |-- docs # self.build_docs
| |-- lintscore # self.build_lintscore
| |-- meta # self.build_meta
| `-- pytest # self.build_pytest
+-- setup.py #
`-- requirements.txt #
"""
#: A set of all overridable keys
KEYS = {
'location',
'package_name',
'name',
'docs',
'tests',
'tests_js',
'build',
'source',
'source_js',
'source_less',
'django_templates',
'django_static',
'django_models',
'build_coverage',
'build_docs',
'build_lintscore',
'build_meta',
'build_pytest',
}
def __init__(self, root, **kw): # pylint:disable=too-many-statements
#: The abspath to the "working copy".
self.root = kw.get('root') or Path(root).abspath()
#: The abspath of the directory containing the root.
self.location = kw.get('location') or self.root.parent # pylint: disable=no-member
#: The pip-installable name.
self.package_name = kw.get('package_name') or self.root.basename()
#: The importable name.
self.name = kw.get('name') or self.package_name.replace('-', '')
#: The documentation source directory.
self.docs = kw.get('docs') or self.root / 'docs'
#: The tests directory.
self.tests = kw.get('tests') or self.root / 'tests'
#: the javascript tests directory
self.tests_js = kw.get('tests_js') or self.root / 'tests' / 'js'
#: The root of the build output directory.
self.build = kw.get('build') or self.root / 'build'
#: The source directory.
self.source = kw.get('source') or self.root / self.name
#: The javascript source directory.
self.source_js = kw.get('source_js') or self.root / 'js'
#: The less source directory.
self.source_less = kw.get('source_less') or self.root / 'less'
#: The django app template directory.
self.django_templates = kw.get('django_templates') or self.root / self.name / 'templates'
#: The django app static directory.
self.django_static = kw.get('django_static') or self.root / self.name / 'static'
#: The django models directory
self.django_models_dir = kw.get('django_models') or self.root / self.name / 'models'
#: the django models.py file
self.django_models_py = kw.get('django_models') or self.root / self.name / 'models.py'
#: Coverage output directory.
self.build_coverage = kw.get('build_coverage') or self.root / 'build' / 'coverage'
#: Documentation output directory.
self.build_docs = kw.get('build_docs') or self.root / 'build' / 'docs'
#: Lintscore output directory.
self.build_lintscore = kw.get('build_lintscore') or self.root / 'build' / 'lintscore'
#: Package meta output directory.
self.build_meta = kw.get('build_meta') or self.root / 'build' / 'meta'
#: Pytest output directory.
self.build_pytest = kw.get('build_pytest') or self.root / 'build' / 'pytest'
#: Gitlab public directory.
self.public_dir = kw.get('public_dir') or self.root / 'public'
for k, v in kw.items():
setattr(self, k, v)
[docs] def is_django(self):
"""Is this a Django package?
"""
return any(d is not None and d.exists() for d in self.django_dirs)
@property
def source_dirs(self):
"""Directories containing source.
"""
return [self.source, self.source_js, self.source_less]
@property
def django_models(self):
"""Return the path to the Django models.
"""
if self.django_models_dir.exists():
return self.django_models_dir
if self.django_models_py.exists():
return self.django_models_py
return None
@property
def django_dirs(self):
"""Directories containing/holding django specific files.
"""
return [self.django_static, self.django_templates, self.django_models]
@property
def build_dirs(self):
"""Directories containing build artifacts.
"""
return [self.build, self.build_coverage, self.build_docs,
self.build_lintscore, self.build_meta, self.build_pytest]
@property
def all_dirs(self):
"""Return all package directories.
"""
return ([self.docs, self.tests]
+ self.source_dirs
+ self.django_dirs
+ self.build_dirs)
[docs] def missing_dirs(self):
"""Return all missing directories.
"""
return [d for d in self.all_dirs if d is not None and not d.exists()]
[docs] def make_missing(self):
"""Create all missing directories.
"""
for d in self.missing_dirs():
d.makedirs()
def __repr__(self):
keylen = max(len(k) for k in self.__dict__) # pylint:disable=unused-variable
lines = []
for k, v in sorted(self.__dict__.items()):
lines.append(f'{k:>{keylen}} {v}')
return '\n'.join(lines)
[docs] def write_ini(self, _fname, section):
"""Write to ini file.
"""
cp = configparser.RawConfigParser()
cp.add_section(section)
vals = [
'root', 'location', 'name', 'docs', 'tests', 'source', 'source_js',
'source_less', 'build', 'build_coverage', 'build_docs',
'build_lintscore', 'build_meta', 'build_pytest',
'django_templates', 'django_static',
]
for val in vals:
cp.set(section, val, getattr(self, val))
out = StringIO()
cp.write(out)
return out.getvalue()
[docs]class Package(DefaultPackage):
"""Package layout with possible overrides.
"""
def __init__(self, root, **kw):
# pylint:disable=multiple-statements,too-many-statements,too-many-branches
super().__init__(root, **kw)
name = kw.get('name')
docs = kw.get('docs')
tests = kw.get('tests')
build = kw.get('build')
source = kw.get('source')
source_js = kw.get('source_js')
source_less = kw.get('source_less')
source_styles = kw.get('styles')
build_coverage = kw.get('build_coverage')
build_docs = kw.get('build_docs')
build_lintscore = kw.get('build_lintscore')
build_meta = kw.get('build_meta')
build_pytest = kw.get('build_pytest')
django_templates = kw.get('django_templates')
django_static = kw.get('django_static')
if name: self.name = name
if docs: self.docs = docs
if tests: self.tests = tests
self.source_js = self.root / 'js'
self.source_less = self.root / 'less'
if build:
self.build = build
self.build_coverage = self.build / 'coverage'
self.build_docs = self.build / 'docs'
self.build_lintscore = self.build / 'lintscore'
self.build_meta = self.build / 'meta'
self.build_pytest = self.build / 'pytest'
if source:
self.source = source
self.django_templates = self.source / 'templates'
self.django_static = self.source / 'static'
if source_js: self.source_js = source_js
if source_less: self.source_styles = source_less
if source_styles: self.source_styles = source_styles
if build_coverage: self.build_coverage = build_coverage
if build_docs: self.build_docs = build_docs
if build_lintscore: self.build_lintscore = build_lintscore
if build_meta: self.build_meta = build_meta
if build_pytest: self.build_pytest = build_pytest
if django_templates: self.django_templates = django_templates
if django_static: self.django_static = django_static
# dkcode.Package compatibility
self.build_dir = self.build
self.lintscore_dir = self.build_lintscore
self.meta_dir = self.build_meta
self.coverage = self.build_coverage
self.coverage_dir = self.build_coverage
self.docs_dir = self.docs
self.package_dir = self.root
self.tests_dir = self.tests
self.pyroot_dir = self.root
self.source_dir = self.source
self.public = self.public_dir
self.pytest_dir = self.build_pytest
self.static_dir = self.django_static
self.templates_dir = self.django_templates