summaryrefslogtreecommitdiffstats
path: root/askbot/deployment/path_utils.py
blob: 1229cf1bde3ef7d844f7ee7b37e6a4ec6c9154ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
"""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