views.py

1
""" views.py - Describes the different views that can be presented to the user.
2
    Copyright © 2016 Maarten "Vngngdn" Vangeneugden
3
4
    This program is free software: you can redistribute it and/or modify
5
    it under the terms of the GNU Affero General Public License as
6
    published by the Free Software Foundation, either version 3 of the
7
    License, or (at your option) any later version.
8
9
    This program is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
    GNU Affero General Public License for more details.
13
14
    You should have received a copy of the GNU Affero General Public License
15
    along with this program. If not, see https://www.gnu.org/licenses/agpl.html.
16
"""
17
18
from django.shortcuts import get_object_or_404, render  # This allows to render the template with the view here. It's pretty cool and important.
19
from django.http import HttpResponseRedirect, HttpResponse
20
from django.core.urlresolvers import reverse
21
from .models import *
22
23
from .GitActions import RepoInfo
24
25
from git import Repo  # GitPython functionality.
26
import git
27
28
from .syntax import *
29
30
# First, I list some standard variables that are common for most of the sites of this app.
31
32
def footer_description():
33
    return _("Gitar is a simple web app that allows its users to easily share Git repos in combination with the Django framework.")
34
35
def footer_links():
36
    footer_links = [
37
            #['Source', 'OHGODHELPNOTDONEYET'],
38
            [_('Home page'), reverse('about-index')],
39
            [_('Source code'), reverse('gitar-repository', 'gitar')],
40
            ]
41
    return footer_links
42
43
def standard_context():
44
    context = {
45
            'materialDesign_color': "blue-grey",
46
            'materialDesign_accentColor': "green",
47
            'navbar_title': "Gitar",
48
            'navbar_backArrow': False,
49
            'footer_title': "Gitar",
50
            'footer_description': footer_description(),
51
            'footer_links': footer_links(),
52
            }
53
    return context
54
55
# From here, the actual views start.
56
def index(request):
57
    """ The start page of Gitar.
58
59
    The goal of this view, is to collect all the available repositories,
60
    including some additional information, such as programming language,
61
    license, description, ... in order to give a fast overview of the most
62
    prominent information.
63
    """
64
65
    # Collecting the available repositories:
66
    # Template:
67
    template = "gitar/index.html"
68
    # Requesting the repositories:
69
    modelRepos = Repository.objects.all()
70
    # From here, we start collecting info about all the repositories:
71
    class BlankRepository: pass  # Blank object in which all data will be collected.
72
    repositories = []
73
    for modelRepo in modelRepos:
74
        repository = BlankRepository()
75
        # TODO: Find a way to add all of modelRepo's fields without having to
76
        # hardcode them. This is prone to errors and is redundant.
77
        repository.name = str(modelRepo)
78
        repository.programmingLanguage = modelRepo.programmingLanguage
79
        repository.license = modelRepo.license
80
        repository.description = RepoInfo.get_description(modelRepo)
81
82
        #gitRepo = Repo.init(modelRepo.directory(), bare=True)  # Connects to the Git Repo.
83
        # See tests.py, which assures all repositories exist. Tests are handy.
84
        #repository.description = gitRepo.description
85
        # This is mostly personal taste, but I like to show the amount of files.
86
        #repoTree = gitRepo.heads.master.commit.tree
87
        #repository.fileCount = len(repoTree.blobs)  # blobs are files.
88
        repositories.append(repository)
89
    # After that, I extend the standard context with the repositories:
90
    context = standard_context()
91
    context['repositories'] = repositories
92
    # And finally, sending everything back.
93
    return render(request, template, context)
94
95
def commit(request, repository, commit):
96
    pass  # TODO
97
98
def repositories(request, repository_name, branch="master"):
99
    # A repo's root is a directory by default, so this will automatically return
100
    # a directory view. But still, this is a bit nicer.
101
    return path_explorer(request, repository_name, branch, "")
102
103
def path_explorer(request, repository_name, branch, path):
104
    """ Checks whether the given path is a file or a directory, and calls the
105
    appropriate view function accordingly.
106
    """
107
    repository = RepoInfo.get_repository_object(repository_name)
108
    # From the GitPython documentation:
109
    # You can obtain the tree object of a repository, which is the directory of
110
    # that repo. This tree can be accessed as if it were a native Python list,
111
    # where the elements are the subdirectories and files. So, the idea to
112
    # determine whether a file, or a directory was requested, is simple:
113
    # 1. Split the path with "/" as seperator.
114
    # 2. Replace the current tree variable with the one retrieved from the
115
    # subtree element
116
    # 3. Repeat 2. until all parts of the given path are exhausted.
117
    # If we now still have a tree, we're looking at a directory, so display the
118
    # files (and subdirectories) of this directory.
119
    # Else, if we hit a blob, display the file contents.
120
    path_parts = path.split(sep="/")
121
    # FIXME: This is a bug at the URL regex part that I haven't been able to fix
122
    # yet. This serves as a temporary fix:
123
    # If the last part of the path is an empty string (which happens when the
124
    # last symbol was a '/'), remove that part from the list.
125
    # Of course, this is bad monkeypatching, but I suck at regex, so as long as
126
    # I don't find the solution, this'll have to do.
127
128
129
    print(path_parts)
130
131
    if path_parts[len(path_parts)-1] == "":
132
        path_parts.pop()
133
134
    if len(path_parts) == 0:
135
        directory = repository.heads[branch].commit.tree
136
        return directory_view(request, repository_name, branch, path, directory)
137
138
    assert len(path_parts) != 0
139
140
    # FIXME: If the user gives a "<something>/../<somethingElse>", that should
141
    # become "<something>". Obviously, although I think that's done by default
142
    # already.
143
    directory = repository.heads[branch].commit.tree
144
    for i in range(len(path_parts)):
145
        subdirectories = directory.trees
146
        #if len(subdirectories) == 0:
147
            # This can't happen, as this would imply there is a directory inside
148
            # a file.
149
        #    assert False
150
        #else:
151
        for subdirectory in subdirectories:
152
            if subdirectory.name == path_parts[i]:
153
                directory = subdirectory
154
                #break  # Useless optimization
155
    # When there are no more directories to traverse, check if the last part of
156
    # the path is either a file, or a directory:
157
    blobs = directory.blobs
158
    print(path_parts)
159
    last_part = path_parts[len(path_parts)-1]
160
    for blob in directory.blobs:
161
        print(blob.name)
162
        if blob.name == last_part:
163
            file_blob = blob
164
            print("Returning file view")
165
            return file_view(request, repository_name, branch, path, file_blob)
166
        else:
167
            print("blob name: " + blob.name)
168
            print("last part: " + last_part)
169
    return directory_view(request, repository_name, branch, path, directory)
170
171
def directory_view(request, repository_name, branch, path, directory):
172
    """ Collects the given directories's files and subdirectories, and renders a
173
    template to display this data.
174
    """
175
176
    # Collecting files in this directory
177
    repository = RepoInfo.get_repository_object(repository_name)
178
    files = []
179
    for file in directory.blobs:
180
        files.append({
181
            "name":file.name,
182
            "path":file.path,
183
            "commit":"",#FileInfo.last_commit(repository, file).hexsha[:20],
184
            })
185
    # Collecting commits for this branch
186
    commits = []
187
    for commit in repository.iter_commits(branch):
188
        commits.append({
189
            "hash":commit.hexsha[:20],
190
            "author":commit.author,
191
            "description":commit.summary,
192
            })
193
    # Collecting subdirectories
194
    subdirectories = []
195
    for subdirectory in directory.trees:
196
        subdirectories.append({
197
            "path":subdirectory.path,
198
            "name":subdirectory.name,
199
            })
200
    # Collecting rendering information:
201
    template = "gitar/directory.html"
202
    context = standard_context()
203
    context["files"] = files
204
    context["subdirectories"] = subdirectories
205
    context["commits"] = commits
206
    context["branch"] = branch
207
    context["repository_name"] = repository_name
208
    context["repository_description"] = repository.description
209
    # Collection repo information
210
    for repo in Repository.objects.all():
211
        if str(repo) == repository_name:
212
            context["repository_language"] = repo.programmingLanguage
213
            context["repository_license"] = repo.license
214
            break
215
    branches = []
216
    for bbranch in repository.heads:
217
        branches.append(bbranch.name)
218
    context["branches"] = branches
219
    return render(request, template, context)
220
221
222
def file_view(request, repository_name, branch, path, file):
223
    """ Collects the file contents of the given file path, and returns it to the
224
    template, with the file contents already formatted in HTML using Pygments.
225
    """
226
227
    # Turning the file's contents in HTML ready output:
228
    raw_file_data = file.data_stream.read()
229
    html_code = code_to_HTML(raw_file_data, file.name)
230
    # Collecting rendering information:
231
    template = "gitar/file.html"
232
    context = standard_context()
233
    context["content"] = html_code
234
    context["file_name"] = file.name
235
    context["repository_name"] = repository_name
236
    return render(request, template, context)
237
    
238