from __future__ import with_statement
from optparse import make_option
import re
import shutil
import os
from django.core.management.base import CommandError
from django.template import Context
from django.template.loader import get_template
from django.conf import settings
# external
import inflection
from subcommand.management.base import BaseVerboseCommand
from generate_scaffold.management.transactions import (
FilesystemTransaction,
FileModification,
FileCreation,
Filelike,
DirectoryCreation
)
from generate_scaffold.utils.cacheclear import (
reload_django_appcache,
clean_pyc_in_dir
)
# ...
from .base import (
BaseSubCommand
# AppSubCommand,
# LabelSubCommand,
# NoArgsSubCommand,
# TemplateSubCommand
)
from ..utils import strext
fields_ptn = re.compile("^-")
class FileDestroy(object):
def __init__(self, transaction, filename):
self.transaction = transaction
self.filename = filename
self.backup_path = None
def execute(self):
self.backup_path = self.transaction.generate_path()
if os.path.exists(self.filename):
shutil.copy2(self.filename, self.backup_path)
# self.transaction.msg("backup", self.filename)
else:
self.transaction.msg("notfound", self.filename)
def rollback(self):
if not self.transaction.is_dry_run:
shutil.copy2(self.backup_path, self.filename)
self.transaction.msg("revert", self.filename)
def commit(self):
if os.path.exists(self.filename):
self.transaction.msg("destroy", self.filename)
os.remove(self.filename)
os.remove(self.backup_path)
class DirectoryDestroy(object):
def __init__(self, transaction, dirname):
self.transaction = transaction
self.dirname = dirname
def execute(self):
self.backup_path = self.transaction.generate_path()
if os.path.exists(self.dirname):
os.mkdir(self.backup_path)
# self.transaction.msg("backup", self.dirname)
else:
self.transaction.msg("notfound", self.dirname)
def rollback(self):
if not self.transaction.is_dry_run:
shutil.copy2(self.backup_path, self.dirname)
self.transaction.msg("revert", self.dirname)
def commit(self):
if os.path.exists(self.dirname):
try:
os.rmdir(self.dirname)
self.transaction.msg("destroy", self.dirname)
except OSError:
self.transaction.msg("notempty", self.dirname)
os.rmdir(self.backup_path)
class FilesystemTransactionWrapper(FilesystemTransaction):
def __init__(self, is_dry_run=False, delegate=None, destroy=False):
super(FilesystemTransactionWrapper, self).__init__(is_dry_run, delegate)
self.destroy = destroy
def rollback(self):
for entry in self.log[::-1]:
entry.rollback()
def commit(self):
for entry in self.log[::-1] if self.destroy else self.log:
entry.commit()
def open(self, filename, mode):
if self.destroy:
modification = FileDestroy(self, filename)
elif os.path.exists(filename):
modification = FileModification(self, filename)
else:
modification = FileCreation(self, filename)
modification.execute()
self.log.append(modification)
if self.is_dry_run or self.destroy:
return Filelike()
else:
return open(filename, mode)
def mkdir(self, dirname):
if self.destroy:
modification = DirectoryDestroy(self, dirname)
modification.execute()
self.log.append(modification)
elif os.path.exists(dirname):
self.msg("exists", dirname)
else:
modification = DirectoryCreation(self, dirname)
modification.execute()
self.log.append(modification)
def transaction_wrapper(self, dry_run=False, destroy=False):
# TODO: implement decorator.
return FilesystemTransactionWrapper(dry_run, self, destroy)
class GenerateMixin(object):
def __init__(self, *arg, **kwargs):
super(GenerateMixin, self).__init__(*arg, **kwargs)
self.app_name = ""
self.app_dir = ""
self.app_module = None
self.class_name = ""
self.fields = []
self.verbose = 1
self.dry_run = False
self.nodes = []
self.destroy = False
self.inflect = inflection
def template(self, template, src, **options):
self.nodes.append({"src": src, "template": template, "options": options})
def empty_directory(self, src, **options):
self.nodes.append({"src": src, "template": False, "options": options})
def create_file(self, src, **options):
self.nodes.append({"src": src, "template": "dummy", "options": options})
def empty_package(self, src, **options):
self.empty_directory(src)
self.create_file(os.path.join(src, "__init__.py"))
def run(self, dry_run=False):
with transaction_wrapper(self, dry_run, self.destroy) as transaction:
for n in self.nodes:
src = n.get("src")
template = n.get("template")
options = n.get("options")
if template:
with transaction.open(src, "w+") as f:
data = self.render_template(template, **options)
f.seek(0)
f.write(options.get("write", "" if self.destroy else data))
self.log(f.read())
else:
transaction.mkdir(src)
reload_django_appcache()
clean_pyc_in_dir(self.app_dir)
def _dictstrmap(self, func, dic):
assert isinstance(dic, dict)
dict_ = dict([(k, func(v)) for k, v in dic.items()
if isinstance(v, str)])
dic.update(dict_)
def render_template(self, template, **options):
c = {"package": self.package,
"basecommand": self.basecommand,
"usercommand": self.usercommand,
"class_name": self.class_name,
"app_name": self.app_name,
"app_class": self.app_name,
"app_dir": self.app_dir,
"fields": map(lambda field: strext(field), self.fields),
"template": template,
"options": self._dictstrmap(strext, options)
}
self._dictstrmap(strext, c)
try:
return get_template("{0}/{1}".format(self.package, template)).render(Context(c))
except Exception:
return ""
def handle(self, *args, **options):
try:
app_name = args[0]
except IndexError:
raise CommandError("You must provide an app_name.")
if app_name not in settings.INSTALLED_APPS:
raise CommandError(
"{1}. App with label {0} could not be found. " \
"Are you sure your INSTALLED_APPS setting is correct?".format(
app_name, self.usercommand))
try:
app_module = __import__(app_name)
except ImportError:
raise CommandError(
"Could not import app with name: {0}".format(app_name))
self.app_name = app_name
self.app_module = app_module
self.app_dir = app_module.__path__[0]
self.class_name = args[1] if len(args) > 1 else ""
self.fields = [arg for arg in args[2:] if not fields_ptn.match(arg)] if len(args) > 2 else ""
self.package_dir = os.path.abspath(os.path.dirname(__import__(__package__).__file__))
self.destroy = self.basecommand == "destroy"
# handle
self._handle_generate(*args, **options)
exit()
[docs]class GenerateSubCommand(GenerateMixin, BaseSubCommand):
def _handle_generate(self, *args, **options):
self.handle_generate(*args, **options)
[docs]class GenerateCommand(GenerateMixin, BaseVerboseCommand):
help = ("generate template command generator.")
option_list = BaseVerboseCommand.option_list + (
make_option('--destroy', action='store_true', dest='destroy', default=False,
help='Destroy flg.'
),
)
def _handle_generate(self, *args, **options):
try:
self.subcommand_name = args[1]
except IndexError:
raise CommandError("You must provide an subcommand name.")
self.handle_generate(*args, **options)