Unknown changes
- Author
- Maarten Vangeneugden
- Date
- Oct. 18, 2017, 8: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 |