"""utilities in addition to os.path that * help to test existing paths on usability for the installation * create necessary directories * install deployment files """ import os import os.path import tempfile import re import glob import shutil import imp from askbot.utils import console from askbot.deployment.template_loader import SettingsTemplate FILES_TO_CREATE = ('__init__.py', 'manage.py', 'urls.py', 'django.wsgi') BLANK_FILES = ('__init__.py', 'manage.py') LOG_DIR_NAME = 'log' def split_at_break_point(directory): """splits directory path into two pieces first that exists and secon - that does not by determining a point at which path breaks exception will be raised if directory in fact exists """ assert(os.path.exists(directory) == False) head = directory tail_bits = list() while os.path.exists(head) == False: head, tail = os.path.split(head) tail_bits.insert(0, tail) return head, os.path.join(*tail_bits) def clean_directory(directory): """Returns normalized absolute path to the directory regardless of whether it exists or not or ``None`` - if the path is a file or if ``directory`` parameter is ``None``""" if directory is None: return None directory = os.path.normpath(directory) directory = os.path.abspath(directory) if os.path.isfile(directory): if options.verbosity >= 1 and os.path.isfile(directory): print messages.CANT_INSTALL_INTO_FILE % {'path':directory} sys.exit(1) return None return directory def directory_is_writable(directory): """returns True if directory exists and is writable, False otherwise """ tempfile.tempdir = directory try: #run writability test temp_path = tempfile.mktemp() assert(os.path.dirname(temp_path) == directory) temp_file = open(temp_path, 'w') temp_file.close() os.unlink(temp_path) return True except IOError: return False def can_create_path(directory): """returns True if user can write file into directory even if it does not exist yet and False otherwise """ if os.path.exists(directory): if not os.path.isdir(directory): return False else: directory = split_at_break_point(directory)[0] return directory_is_writable(directory) IMPORT_RE1 = re.compile(r'from django.*import') IMPORT_RE2 = re.compile(r'import django') def has_existing_django_project(directory): """returns True is any of the .py files in a given directory imports anything from django """ directory = os.path.normpath(directory) file_list = glob.glob(directory + os.path.sep + '*.py') for file_name in file_list: if file_name.endswith(os.path.sep + 'manage.py'): #a hack allowing to install into the distro directory continue py_file = open(file_name) for line in py_file: if IMPORT_RE1.match(line) or IMPORT_RE2.match(line): py_file.close() return True py_file.close() return False def find_parent_dir_with_django(directory): """returns path to Django project anywhere above the directory if nothing is found returns None """ parent_dir = os.path.dirname(directory) while parent_dir != directory: if has_existing_django_project(parent_dir): return parent_dir else: directory = parent_dir parent_dir = os.path.dirname(directory) return None def path_is_clean_for_django(directory): """returns False if any of the parent directories contains a Django project, otherwise True does not check the current directory """ django_dir = find_parent_dir_with_django(directory) return (django_dir is None) def create_path(directory): """equivalent to mkdir -p""" if os.path.isdir(directory): return elif os.path.exists(directory): raise ValueError('expect directory or a non-existing path') else: os.makedirs(directory) def touch(file_path, times = None): """implementation of unix ``touch`` in python""" #http://stackoverflow.com/questions/1158076/implement-touch-using-python fhandle = file(file_path, 'a') try: os.utime(file_path, times) finally: fhandle.close() SOURCE_DIR = os.path.dirname(os.path.dirname(__file__)) def get_path_to_help_file(): """returns path to the main plain text help file""" return os.path.join(SOURCE_DIR, 'doc', 'INSTALL') def deploy_into(directory, new_project = False, verbosity = 1, context = None): """will copy necessary files into the directory """ assert(isinstance(new_project, bool)) if new_project: copy_files = FILES_TO_CREATE blank_files = BLANK_FILES if verbosity >= 1: print 'Copying files: ' for file_name in copy_files: src = os.path.join(SOURCE_DIR, 'setup_templates', file_name) if os.path.exists(os.path.join(directory, file_name)): if file_name in blank_files: continue else: if file_name == 'urls.py' and new_project: #overwrite urls.py shutil.copy(src, directory) else: if verbosity >= 1: print '* %s' % file_name, print "- you already have one, please add contents of %s" % src else: if verbosity >= 1: print '* %s ' % file_name shutil.copy(src, directory) #copy log directory src = os.path.join(SOURCE_DIR, 'setup_templates', LOG_DIR_NAME) log_dir = os.path.join(directory, LOG_DIR_NAME) create_path(log_dir) touch(os.path.join(log_dir, 'askbot.log')) #creating settings file from template if verbosity >= 1: print "Creating settings file" settings_contents = SettingsTemplate(context).render() settings_path = os.path.join(directory, 'settings.py') if os.path.exists(settings_path) and new_project == False: if verbosity >= 1: print "* you already have a settings file please merge the contents" elif new_project == True: settings_file = open(settings_path, 'w+') settings_file.write(settings_contents) #Grab the file! if os.path.exists(context['local_settings']): local_settings = open(context['local_settings'], 'r').read() settings_file.write('\n') settings_file.write(local_settings) settings_file.close() if verbosity >= 1: print "settings file created" if verbosity >= 1: print '' app_dir = os.path.join(directory, 'askbot') copy_dirs = ('doc', 'cron', 'upfiles') dirs_copied = 0 for dir_name in copy_dirs: src = os.path.join(SOURCE_DIR, dir_name) dst = os.path.join(app_dir, dir_name) if os.path.abspath(src) != os.path.abspath(dst): if dirs_copied == 0: if verbosity >= 1: print 'copying directories: ', if verbosity >= 1: print '* ' + dir_name if os.path.exists(dst): if os.path.isdir(dst): if verbosity >= 1: print 'Directory %s not empty - skipped' % dst else: if verbosity >= 1: print 'File %s already exists - skipped' % dst continue shutil.copytree(src, dst) dirs_copied += 1 if verbosity >= 1: print '' def dir_name_unacceptable_for_django_project(directory): dir_name = os.path.basename(directory) if re.match(r'[_a-zA-Z][\w-]*$', dir_name): return False return True def dir_taken_by_python_module(directory): """True if directory is not taken by another python module""" dir_name = os.path.basename(directory) try: imp.find_module(dir_name) return True except ImportError: return False def get_install_directory(force = False): """returns a directory where a new django app/project can be installed. If ``force`` is ``True`` - will permit using a directory with an existing django project. """ from askbot.deployment import messages where_to_deploy_msg = messages.WHERE_TO_DEPLOY directory = raw_input(where_to_deploy_msg + ' ') if directory.strip() == '': return None directory = clean_directory(directory) if directory is None: return None if can_create_path(directory) == False: print messages.format_msg_dir_not_writable(directory) return None if os.path.exists(directory): if path_is_clean_for_django(directory): if has_existing_django_project(directory): if not force: print messages.CANNOT_OVERWRITE_DJANGO_PROJECT % \ {'directory': directory} return None else: print messages.format_msg_dir_unclean_django(directory) return None elif force == False: message = messages.format_msg_create(directory) should_create_new = console.choice_dialog( message, choices = ['yes','no'], invalid_phrase = messages.INVALID_INPUT ) if should_create_new == 'no': return None if dir_taken_by_python_module(directory): print messages.format_msg_bad_dir_name(directory) return None if dir_name_unacceptable_for_django_project(directory): print """\nDirectory %s is not acceptable for a Django project. Please use lower case characters, numbers and underscore. The first character cannot be a number.\n""" % os.path.basename(directory) return None return directory