gitar

Unknown changes

Author
Maarten Vangeneugden
Date
Oct. 18, 2017, 10:41 p.m.
Hash
d534dd31bbc46491e4a4d74c30ef9972f95aad9c
Parent
0d2a21c9e999ed1003cb6d81a1fcc0aece78bd44
Modified files
GitActions/RepoInfo.py
syntax.py
urls.py
views.py

GitActions/RepoInfo.py

11 additions and 0 deletions.

View changes Hide changes
1
1
    Copyright © 2016 Maarten "Vngngdn" Vangeneugden
2
2
3
3
    This program is free software: you can redistribute it and/or modify
4
4
    it under the terms of the GNU Affero General Public License as
5
5
    published by the Free Software Foundation, either version 3 of the
6
6
    License, or (at your option) any later version.
7
7
8
8
    This program is distributed in the hope that it will be useful,
9
9
    but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
    GNU Affero General Public License for more details.
12
12
13
13
    You should have received a copy of the GNU Affero General Public License
14
14
    along with this program. If not, see https://www.gnu.org/licenses/agpl.html.
15
15
"""
16
16
17
17
from ..models import Repository
18
18
import git
19
19
20
20
def get_description(repository):
21
21
    """ Returns the Git repo description of the given repository.
22
22
    """
23
23
    if isinstance(repository, Repository):
24
24
        repository = git.Repo(repository.directory_path)
25
25
26
26
    return repository.description
27
27
28
28
def get_repository_object(repository_name):
+
29
    """ Returns the log message that was attached to the given commit.
+
30
+
31
    Keyword arguments:
+
32
    repository -- the repository in which to search
+
33
    commit     -- the specific commit hash
+
34
    simple     -- whether to return a oneliner, or the entire message (default True)
+
35
    """
+
36
    pass
+
37
+
38
+
39
def get_repository_object(repository_name):
29
40
    """ Checks the database for a repository with the same name, and returns a
30
41
    GitPython Repo object.
31
42
32
43
    Given the name of the repository, this function will search the database for
33
44
    a repository whoms name corresponds with the given name. When it found one, 
34
45
35
46
    Keyword arguments:
36
47
    repository_name -- The name of the repository
37
48
    """
38
49
    # Next line raises a Repository.DoesNotExist exception if not found, so it's
39
50
    # not necessary to check whether it was found or not.
40
51
    repository = Repository.objects.get(directory_path__endswith=repository_name+".git")
41
52
42
53
    return git.Repo(repository.directory_path)
43
54
44
55
def get_repository_model_object(repository_name):
45
56
    """ Functions identical to the get_repository_object, except that this
46
57
    function returns the Django model representation.
47
58
48
59
    Keyword arguments:
49
60
    repository_name -- The name of the repository
50
61
    """
51
62
    return Repository.objects.get(directory_path__endswith=repository_name+".git")
52
63
53
64
def read_file(file_blob):
54
65
    """ Reads the contents of the given file, and returns it in a list of
55
66
    strings.
56
67
57
68
    Reading the contents of a file using GitPython is a bit cumbersome. This
58
69
    function takes care of the hassle, and returns a list of unicode strings,
59
70
    allowing easy operations on the file's contents.
60
71
    """
61
72
62
73
    file_data_stream = file_blob.data_stream
63
74
    file_content = file_data_stream.read().decode("utf-8")
64
75
    file_formatted_content = []
65
76
    line = ""
66
77
    for character in file_content:
67
78
        if character != "\n":
68
79
            line = line + character
69
80
        else:
70
81
            file_formatted_content.append(line)
71
82
            line = ""
72
83
    return file_formatted_content
73
84

syntax.py

30 additions and 8 deletions.

View changes Hide changes
1
1
2
2
In essence, this means that in this module, a file's contents are being parsed
3
3
to Pygments, which will then return the appropriate HTML output, which can then
4
4
be directly parsed in a Django template.
5
5
"""
6
6
7
7
from pygments import highlight
8
8
from pygments.lexers import get_lexer_by_name
9
9
from pygments.formatters import HtmlFormatter
10
10
from pygments.lexers import guess_lexer_for_filename
11
11
12
12
def code_to_HTML(code, file_name):
13
13
    """ Turns the given list of code strings in HTML, ready for output.
14
14
15
15
    Please note that the lexer that will be used for the syntax coloring, is
16
16
    determined by the given file name, so assert that the given code comes from
17
17
    the given file.
18
18
    Keyword arguments:
19
19
    code -- A non-empty list of code strings.
20
20
    file_name -- The name of the file from where the given code originates.
21
21
    """
22
22
    lexer = guess_lexer_for_filename(file_name, code, stripall=True)
23
-
    # linenos (line-n°'s) adds line numbering to the front of the output. I'm
24
-
    # doing that myself, so False.
25
-
    # cssclass sets the enclosing <div>'s class to the given CSS class name.
26
-
    formatter = HtmlFormatter(linenos=False, cssclass="source")
27
-
    #result = highlight(code, lexer, formatter)
28
-
    unicode_data = decode_to_unicode(highlight(code, lexer, formatter))
29
-
    return unicode_data
30
-
+
23
    # indentation.
+
24
    try:
+
25
        lexer = guess_lexer_for_filename(file_name, code, stripall=False)
+
26
        # linenos (line-n°'s) adds line numbering to the front of the output. I'm
+
27
        # doing that myself, so False.
+
28
        # cssclass sets the enclosing <div>'s class to the given CSS class name.
+
29
        formatter = HtmlFormatter(linenos=False, cssclass="source")
+
30
        #result = highlight(code, lexer, formatter)
+
31
        unicode_data = decode_to_unicode(highlight(code, lexer, formatter))
+
32
        return unicode_data
+
33
    #except pygments.ClassNotFound as exception:
+
34
    except:
+
35
        # This happens with files lacking a file extension, and MarkDown files.
+
36
        # In that case, no lexer should be used, but Simple parse the same code.
+
37
        return no_syntax(code)
+
38
+
39
def no_syntax(data):
+
40
    """ Decodes a given bytearray to a list of unicode strings."""
+
41
    decoded_data = data.decode("utf-8")
+
42
    formatted_data = []
+
43
    line = ""
+
44
    for character in decoded_data:
+
45
        if character != "\n":
+
46
            line = line + character
+
47
        else:
+
48
            formatted_data.append(line)
+
49
            line = ""
+
50
    return formatted_data
+
51
+
52
31
53
def decode_to_unicode(data):
32
54
    """ Decodes a given bytearray to a list of unicode strings."""
33
55
    #decoded_data = data.decode("utf-8")
34
56
    decoded_data = data
35
57
    formatted_data = []
36
58
    line = ""
37
59
    for character in decoded_data:
38
60
        if character != "\n":
39
61
            line = line + character
40
62
        else:
41
63
            formatted_data.append(line)
42
64
            line = ""
43
65
    return formatted_data
44
66

urls.py

1 addition and 1 deletion.

View changes Hide changes
1
1
    Copyright © 2016 Maarten "Vngngdn" Vangeneugden
2
2
3
3
    This program is free software: you can redistribute it and/or modify
4
4
    it under the terms of the GNU Affero General Public License as
5
5
    published by the Free Software Foundation, either version 3 of the
6
6
    License, or (at your option) any later version.
7
7
8
8
    This program is distributed in the hope that it will be useful,
9
9
    but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
    GNU Affero General Public License for more details.
12
12
13
13
    You should have received a copy of the GNU Affero General Public License
14
14
    along with this program. If not, see https://www.gnu.org/licenses/agpl.html.
15
15
"""
16
16
17
17
from django.conf.urls import url
18
18
19
19
from . import views # Imports the views from the same directory (which is views.py).
20
20
21
21
urlpatterns = [
22
22
        url(r'^$', views.index, name='gitar-index'),
23
23
        url(r'^(?P<repository_name>\w+)$', views.repositories, name='gitar-repository'),
24
-
        url(r'^(?P<repository_name>\w+)/(?P<path>([^/]+/)+)$', views.path_explorer, name='gitar-path-explorer'),
+
24
        url(r'^(?P<repository_name>\w+)/(?P<path>([^/]+/)+)$', views.path_explorer, name='gitar-path-explorer'),
25
25
26
26
        ]
27
27

views.py

46 additions and 36 deletions.

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