summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--g2h/cli.py2
-rw-r--r--g2h/commit_page.py2
-rw-r--r--g2h/file.py16
-rw-r--r--g2h/file_page.py45
-rw-r--r--g2h/page_factory.py8
-rw-r--r--g2h/tree.py2
-rw-r--r--g2h/tree_page.py31
-rw-r--r--requirements.txt1
-rw-r--r--templates/file.md13
-rw-r--r--templates/tree.md2
-rw-r--r--test_repo/_gitignore1
-rw-r--r--test_repo/git.tgzbin22304 -> 41560 bytes
-rw-r--r--test_repo/pic.pngbin0 -> 16694 bytes
-rw-r--r--test_repo/src/include/lib.h9
-rw-r--r--test_repo/src/lib.cc9
-rw-r--r--test_repo/src/main.cc7
-rw-r--r--tests/1px.webpbin0 -> 34 bytes
-rw-r--r--tests/base_page_test.py (renamed from tests/page_test.py)0
-rw-r--r--tests/commit_page_test.py (renamed from tests/commit_test.py)2
-rw-r--r--tests/file_page_test.py81
-rw-r--r--tests/file_test.py26
-rw-r--r--tests/log_page_test.py (renamed from tests/log_test.py)0
-rw-r--r--tests/overview_page_test.py (renamed from tests/overview_test.py)0
-rw-r--r--tests/tree_page_test.py120
-rw-r--r--tests/tree_test.py106
26 files changed, 379 insertions, 111 deletions
diff --git a/README.md b/README.md
index cb09b0d..0e42056 100644
--- a/README.md
+++ b/README.md
@@ -54,14 +54,15 @@ For a full list of options, their defaults and how they're changing the behavior
Show the state of the tree at the current commit.
- [x] Folders should be traversable by clicking on their name.
- [ ] Clicking on a file should open that file's page.
-- [ ] Individual file page.
+- [x] Individual file page.
File content in a codeblock with file extension based language detection.
For binary files a note that it can't be displayed.
- - [ ] Don't treat pictures as binary files.
+ - [x] Don't treat pictures as binary files.
- [ ] Breadcrumbs for navigating.
+ - [ ] Add button to download raw file.
- [ ] Nav bar for navigating the pages mentioned above.
- [ ] Link to special readme, if a readme can be found, rendering markdown.
-- [ ] Wrapper script that takes care fo `venv` setup.
+- [ ] Wrapper script that takes care of `venv` setup.
- [ ] Limit to certain branch(es).
- [ ] Limit commit range.
- [ ] Parallelize generating output.
diff --git a/g2h/cli.py b/g2h/cli.py
index 52a2be7..fbc456e 100644
--- a/g2h/cli.py
+++ b/g2h/cli.py
@@ -34,6 +34,8 @@ def main():
tree = factory.new_tree(commit, '', git.get_repo())
for tree_page in tree.build_pages(factory):
tree_page.render()
+ for file in tree_page.get_files():
+ factory.new_file(commit, file).render()
if __name__ == '__main__':
main()
diff --git a/g2h/commit_page.py b/g2h/commit_page.py
index 6111013..a998417 100644
--- a/g2h/commit_page.py
+++ b/g2h/commit_page.py
@@ -17,7 +17,7 @@ def redact_email(email):
class CommitPage(Page):
def __init__(self, out_dir, name, commit, content_subdir):
- super(CommitPage, self).__init__('commit.md', out_dir, os.path.join(commit.hexsha, 'index.md'), name, content_subdir)
+ super(CommitPage, self).__init__('commit.md', out_dir, os.path.join(commit.hexsha, '_index.md'), name, content_subdir)
self.commit = commit
def context(self):
diff --git a/g2h/file.py b/g2h/file.py
new file mode 100644
index 0000000..8f683cd
--- /dev/null
+++ b/g2h/file.py
@@ -0,0 +1,16 @@
+import os
+
+def sanitize_file_name(file_name, files):
+ if not file_name.startswith('.'):
+ return file_name
+
+ file_names = [ f.name for f in files ]
+ for i in range(1, 10):
+ new_name = file_name.replace('.', '_' * i, 1)
+ if not new_name in file_names:
+ return new_name
+
+ raise RuntimeError('Couldn not find unused sanitized file name')
+
+def tree_join(commit, path):
+ return os.path.join(commit.hexsha, 't', path)
diff --git a/g2h/file_page.py b/g2h/file_page.py
new file mode 100644
index 0000000..b154ee5
--- /dev/null
+++ b/g2h/file_page.py
@@ -0,0 +1,45 @@
+import base64
+import filetype
+import os
+
+from .file import sanitize_file_name, tree_join
+from .page import Page
+from .tree import safe_join
+
+def bytes_to_content(file_bytes):
+ if not file_bytes:
+ return (None, 'empty')
+ try:
+ text = file_bytes.decode('utf-8')
+ return (text, 'text')
+ except UnicodeError:
+ if filetype.is_image(file_bytes):
+ # TODO: Refer to the static file once that's implemented.
+ img = base64.b64encode(file_bytes)
+ return (img.decode('utf-8'), 'image')
+
+ return (None, None)
+
+class FilePage(Page):
+ def __init__(self, out_dir, name, commit, file_name, file_path, content_subdir):
+ tree = safe_join(commit.tree, file_path)
+ super(FilePage, self).__init__('file.md', out_dir, os.path.join(tree_join(commit, file_path), sanitize_file_name(file_name, tree.blobs) + '.md'), name, content_subdir)
+ self.commit = commit
+ self.file_name = file_name
+ self.file_path = file_path
+ self.tree = tree
+
+ def read_file(self):
+ return self.tree.join(self.file_name).data_stream.read()
+
+ def context(self):
+ file_bytes = self.read_file()
+ content, file_type = bytes_to_content(file_bytes)
+
+ return {
+ 'content': content,
+ 'content_subdir': self.content_subdir,
+ 'file_extension': os.path.splitext(self.file_name)[1].lstrip('.'),
+ 'title': self.name + ': ' + self.file_name,
+ 'type': file_type
+ }
diff --git a/g2h/page_factory.py b/g2h/page_factory.py
index c4b89ed..da9c5d5 100644
--- a/g2h/page_factory.py
+++ b/g2h/page_factory.py
@@ -1,4 +1,7 @@
+import os
+
from .commit_page import CommitPage
+from .file_page import FilePage
from .overview_page import OverviewPage
from .log_page import LogPage
from .tree_page import TreePage
@@ -12,6 +15,11 @@ class PageFactory:
def new_commit(self, commit):
return CommitPage(self.out_dir, self.name, commit, self.content_subdir)
+ def new_file(self, commit, path):
+ filename = os.path.basename(path)
+ dir = os.path.dirname(path)
+ return FilePage(self.out_dir, self.name, commit, filename, dir, self.content_subdir)
+
def new_overview(self, branches, tags):
return OverviewPage(self.out_dir, self.name, branches, tags, self.content_subdir)
diff --git a/g2h/tree.py b/g2h/tree.py
new file mode 100644
index 0000000..bd8116c
--- /dev/null
+++ b/g2h/tree.py
@@ -0,0 +1,2 @@
+def safe_join(tree, path):
+ return tree.join(path) if path else tree
diff --git a/g2h/tree_page.py b/g2h/tree_page.py
index 977e981..9b7a0d5 100644
--- a/g2h/tree_page.py
+++ b/g2h/tree_page.py
@@ -1,14 +1,14 @@
import os
+from .file import sanitize_file_name, tree_join
from .page import Page
-
-def tree_prefix(sha, tree):
- return os.path.join(sha, 't', tree)
+from .tree import safe_join
class TreePage(Page):
def __init__(self, out_dir, name, commit, sub_tree, repo, content_subdir):
- super(TreePage, self).__init__('tree.md', out_dir, os.path.join(tree_prefix(commit.hexsha, sub_tree), 'index.md'), name, content_subdir)
- self.tree = commit.tree.join(sub_tree) if sub_tree else commit.tree
+ tree = safe_join(commit.tree, sub_tree)
+ super(TreePage, self).__init__('tree.md', out_dir, os.path.join(tree_join(commit, tree.path), '_index.md'), name, content_subdir)
+ self.tree = tree
self.repo = repo
self.commit = commit
self.sub_tree = sub_tree
@@ -17,8 +17,16 @@ class TreePage(Page):
entries = []
for node in nodes:
latest_commit = next(self.repo.iter_commits(self.commit, max_count=1, paths=node.path))
+ name = node.name
+ file_name = name
+ if node.type == 'tree':
+ name += '/'
+ file_name = name
+ else:
+ file_name = sanitize_file_name(name, self.tree.blobs)
entry = {
- 'name': node.name + str('/' if node.type == 'tree' else ''),
+ 'file_name': file_name,
+ 'name': name,
'modified': latest_commit.committed_datetime
}
entries.append(entry)
@@ -35,7 +43,8 @@ class TreePage(Page):
return {
'content_subdir': self.content_subdir,
'entries': self._get_entries(),
- 'subdir': '/'.join([self.content_subdir, tree_prefix(self.commit.hexsha, self.tree.path)]),
+ # TODO: Make this fit the rest of the system.
+ 'subdir': '/'.join([self.content_subdir, tree_join(self.commit, self.tree.path)]),
'title': self.name + ': Tree'
}
@@ -47,10 +56,16 @@ class TreePage(Page):
current = remaining.pop()
if not current in visited:
visited.add(current)
- tree = self.commit.tree.join(current) if current else self.commit.tree
+ tree = safe_join(self.commit.tree, current)
tree_pages.append(factory.new_tree(self.commit, tree.path, self.repo))
for sub_tree in tree.trees:
remaining.append(sub_tree.path)
return tree_pages
+ def get_files(self):
+ file_paths = []
+ for blob in self.tree.blobs:
+ file_paths.append(blob.path)
+
+ return file_paths
diff --git a/requirements.txt b/requirements.txt
index 65a012c..0a0a9e2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
GitPython==3.1.45
+filetype==1.2.0
Jinja2==3.1.6
pytest==8.4.1
diff --git a/templates/file.md b/templates/file.md
new file mode 100644
index 0000000..12a5a81
--- /dev/null
+++ b/templates/file.md
@@ -0,0 +1,13 @@
+{% include '_head.md' %}
+
+{% if type == 'text' %}
+```{{ file_extension }}
+{{ content }}
+```
+{% elif type == 'image' %}
+![](data:image/png;base64,{{ content }})
+{% elif type == 'empty' %}
+File is empty.
+{% else %}
+Can't display binary file.
+{% endif %}
diff --git a/templates/tree.md b/templates/tree.md
index 7a891ab..53644bc 100644
--- a/templates/tree.md
+++ b/templates/tree.md
@@ -4,7 +4,7 @@
| Name | Last modified |
|------|---------------|
{%- for entry in entries %}
-| [{{ entry.name }}](/{{ subdir }}/{{ entry.name}}) | {% if entry.modified %} {{ entry.modified.strftime('%Y-%m-%d %H:%M:%S') }} {% endif %} |{% endfor %}
+| [{{ entry.name }}](/{{ subdir }}/{{ entry.file_name}}) | {% if entry.modified %} {{ entry.modified.strftime('%Y-%m-%d %H:%M:%S') }} {% endif %} |{% endfor %}
{% else %}
No files.
{% endif %}
diff --git a/test_repo/_gitignore b/test_repo/_gitignore
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/test_repo/_gitignore
@@ -0,0 +1 @@
+test
diff --git a/test_repo/git.tgz b/test_repo/git.tgz
index df8887e..bac5752 100644
--- a/test_repo/git.tgz
+++ b/test_repo/git.tgz
Binary files differ
diff --git a/test_repo/pic.png b/test_repo/pic.png
new file mode 100644
index 0000000..f70142b
--- /dev/null
+++ b/test_repo/pic.png
Binary files differ
diff --git a/test_repo/src/include/lib.h b/test_repo/src/include/lib.h
new file mode 100644
index 0000000..28fbe8b
--- /dev/null
+++ b/test_repo/src/include/lib.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <string_view>
+
+namespace lib {
+
+std::string_view print();
+
+}
diff --git a/test_repo/src/lib.cc b/test_repo/src/lib.cc
new file mode 100644
index 0000000..2083478
--- /dev/null
+++ b/test_repo/src/lib.cc
@@ -0,0 +1,9 @@
+#include "lib.h"
+
+#include <string_view>
+
+namespace lib {
+std::string_view print() {
+ return "test";
+}
+}
diff --git a/test_repo/src/main.cc b/test_repo/src/main.cc
new file mode 100644
index 0000000..db3a36e
--- /dev/null
+++ b/test_repo/src/main.cc
@@ -0,0 +1,7 @@
+#include "lib.h"
+
+#include <iostream>
+
+int main() {
+ std::cout << lib::print() << std::endl
+}
diff --git a/tests/1px.webp b/tests/1px.webp
new file mode 100644
index 0000000..15e1c70
--- /dev/null
+++ b/tests/1px.webp
Binary files differ
diff --git a/tests/page_test.py b/tests/base_page_test.py
index c68e435..c68e435 100644
--- a/tests/page_test.py
+++ b/tests/base_page_test.py
diff --git a/tests/commit_test.py b/tests/commit_page_test.py
index b32901e..839d493 100644
--- a/tests/commit_test.py
+++ b/tests/commit_page_test.py
@@ -6,7 +6,7 @@ def test_init():
commit.hexsha = '123'
page = CommitPage('test/commit', 'commit test', commit, '')
assert(page.name == 'commit test')
- assert(page.out_file_path == 'test/commit/123/index.md')
+ assert(page.out_file_path == 'test/commit/123/_index.md')
assert(page.template.name == 'commit.md')
def test_context():
diff --git a/tests/file_page_test.py b/tests/file_page_test.py
new file mode 100644
index 0000000..a1123de
--- /dev/null
+++ b/tests/file_page_test.py
@@ -0,0 +1,81 @@
+from unittest.mock import MagicMock
+
+from g2h.file_page import FilePage, bytes_to_content
+
+def test_init():
+ commit = MagicMock()
+ commit.hexsha = '123'
+ page = FilePage('test/file', 'file test', commit, '.test_file', 'path/test', '')
+ assert(page.name == 'file test')
+ assert(page.out_file_path == 'test/file/123/t/path/test/_test_file.md')
+ assert(page.template.name == 'file.md')
+ assert(page.commit == commit)
+ assert(page.file_name == '.test_file')
+ assert(page.file_path == 'path/test')
+ commit.tree.join.assert_called_with('path/test')
+
+def test_read_file():
+ file = MagicMock()
+ tree = MagicMock()
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree = tree
+ commit.tree.join.return_value = file
+ page = FilePage('test/file', 'file test', commit, 'test_file', '', '')
+ page.read_file()
+ tree.join.assert_called_with('test_file')
+ file.data_stream.read.assert_called()
+
+def test_context():
+ file = MagicMock()
+ tree = MagicMock()
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree = tree
+ commit.tree.join.return_value = file
+ page = FilePage('test/file', 'file test', commit, 'test_file.txt', '', 'file/content')
+ file.data_stream.read.return_value = 'test'.encode()
+ context = page.context()
+ assert(context['content'] == 'test')
+ assert(context['content_subdir'] == 'file/content')
+ assert(context['file_extension'] == 'txt')
+ assert(context['title'] == 'file test: test_file.txt')
+ assert(context['type'] == 'text')
+
+def test_context_dot_file():
+ file = MagicMock()
+ tree = MagicMock()
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree = tree
+ commit.tree.join.return_value = file
+ page = FilePage('test/file', 'file test', commit, '.file', '', 'file/content')
+ file.data_stream.read.return_value = 'test'.encode()
+ context = page.context()
+ assert(context['content'] == 'test')
+ assert(context['content_subdir'] == 'file/content')
+ assert(context['file_extension'] == '')
+ assert(context['title'] == 'file test: .file')
+ assert(context['type'] == 'text')
+
+def test_bytes_to_content_text():
+ content, type = bytes_to_content('test'.encode())
+ assert(content == 'test')
+ assert(type == 'text')
+
+def test_bytes_to_content_pic():
+ webp_1px = bytes([0x52, 0x49, 0x46, 0x46, 0x1a, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x4c, 0x0e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x07, 0x10, 0x11, 0xfd, 0x0f, 0x44, 0x44, 0xff, 0x03, 0x0a])
+ content, type = bytes_to_content(webp_1px)
+ assert(content == 'UklGRhoAAABXRUJQVlA4TA4AAAAvAAAAAAcQEf0PRET/Awo=')
+ assert(type == 'image')
+
+def test_bytes_to_content_empty():
+ content, type = bytes_to_content(bytes())
+ assert(content == None)
+ assert(type == 'empty')
+
+def test_bytes_to_content_bin():
+ content, type = bytes_to_content(bytes([0xc3, 0x28]))
+ assert(content == None)
+ assert(type == None)
+
diff --git a/tests/file_test.py b/tests/file_test.py
new file mode 100644
index 0000000..838df71
--- /dev/null
+++ b/tests/file_test.py
@@ -0,0 +1,26 @@
+import os
+import pytest
+
+from unittest.mock import Mock
+
+from g2h.file import sanitize_file_name, tree_join
+
+def test_sanitize_not_necessary():
+ assert(sanitize_file_name('foo', None) == 'foo')
+
+def test_sanitize_success():
+ assert(sanitize_file_name('.test', []) == '_test')
+
+def test_sanitize_collissions():
+ entries = []
+ for i in range(1, 10):
+ entry = Mock()
+ entry.name = '_' * i + 'test'
+ entries.append(entry)
+ with pytest.raises(RuntimeError):
+ sanitize_file_name('.test', entries)
+
+def test_tree_join():
+ commit = Mock()
+ commit.hexsha = '123'
+ assert(tree_join(commit, 'test') == os.path.join('123', 't', 'test'))
diff --git a/tests/log_test.py b/tests/log_page_test.py
index 041544e..041544e 100644
--- a/tests/log_test.py
+++ b/tests/log_page_test.py
diff --git a/tests/overview_test.py b/tests/overview_page_test.py
index 06eea97..06eea97 100644
--- a/tests/overview_test.py
+++ b/tests/overview_page_test.py
diff --git a/tests/tree_page_test.py b/tests/tree_page_test.py
new file mode 100644
index 0000000..891e940
--- /dev/null
+++ b/tests/tree_page_test.py
@@ -0,0 +1,120 @@
+import os
+from pytest import fail
+from unittest.mock import MagicMock
+
+from g2h.tree_page import TreePage
+
+def test_init():
+ repo = MagicMock()
+ commit = MagicMock()
+ commit.hexsha = '123'
+ page = TreePage('test/tree', 'tree test', commit, 'test/sub/tree', repo, '')
+ assert(page.name == 'tree test')
+ assert(page.repo == repo)
+ assert(page.commit == commit)
+ commit.tree.join.assert_called_once_with('test/sub/tree')
+ commit_no_subtree = MagicMock()
+ commit_no_subtree.hexsha = '123'
+ commit_no_subtree.tree = MagicMock()
+ commit_no_subtree.tree.path = 'no sub tree'
+ page_no_subtree = TreePage('test/tree', 'tree test', commit_no_subtree, '', repo, '')
+ assert(page_no_subtree.name == 'tree test')
+ assert(page_no_subtree.repo == repo)
+ assert(page_no_subtree.commit == commit_no_subtree)
+ assert(page_no_subtree.tree.path == 'no sub tree')
+
+def add_nodes(parent, paths):
+ nodes = []
+ for node in paths:
+ nodes.append(add_node(parent, node))
+ return nodes
+
+def add_node(parent, path):
+ node = MagicMock()
+ node.trees = []
+ node.blobs = []
+ node.name = path
+ node.path = os.path.join(parent.path, path)
+ if path.endswith('/'):
+ parent.trees.append(node)
+ node.type = 'tree'
+ else:
+ parent.blobs.append(node)
+ node.type = 'blob'
+ return node
+
+def test_context_empty():
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree.path = 'tree/path'
+ page = TreePage('test/tree', 'tree test', commit, '', None, '')
+ context = page.context()
+ assert(context['entries'] == [])
+ assert(context['content_subdir'] == '')
+ assert(context['subdir'] == '/123/t/tree/path')
+ assert(context['title'] == 'tree test: Tree')
+
+def test_context_entries():
+ tree = MagicMock()
+ tree.trees = []
+ tree.blobs = []
+ repo = MagicMock()
+ sub_dir = add_node(tree, 'sub/')
+ add_node(sub_dir, 'file.txt')
+ add_node(tree, 'README')
+ add_node(tree, '.gitignore')
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree = tree
+ commit.committed_datetime = 'Date Time'
+ repo.iter_commits = MagicMock(return_value=iter([commit, commit, commit]))
+ page = TreePage('test/tree', 'tree test', commit, '', repo, '')
+ context = page.context()
+ assert(repo.iter_commits.called)
+ assert(context['entries'] == [{'file_name': 'sub//', 'name': 'sub//', 'modified': 'Date Time'}, {'file_name': '_gitignore', 'name': '.gitignore', 'modified': 'Date Time'}, {'file_name': 'README', 'name': 'README', 'modified': 'Date Time'}])
+
+def test_traversal():
+ factory = MagicMock()
+ def new_tree_sideeffect(*args, **kwargs):
+ return args[1]
+ factory.new_tree.side_effect = new_tree_sideeffect
+ tree = MagicMock()
+ tree.trees = []
+ tree.blobs = []
+ tree.path = ''
+ root_tree = add_nodes(tree, ['README', 'sub/'])
+ sub_tree = add_nodes(root_tree[1], ['main.cc', 'lib.cc', 'include/'])
+ add_node(sub_tree[2], 'lib.h')
+ def tree_join_sideeffect(*args, **kwargs):
+ if args[0] == 'sub/':
+ return root_tree[1]
+ elif args[0] == 'sub/include/':
+ return sub_tree[2]
+ fail('Unexpected tree join: ' + args[0])
+ tree.join.side_effect = tree_join_sideeffect
+ commit = MagicMock()
+ commit.tree = tree
+ page = TreePage('test/tree', 'tree test', commit, '', None, '')
+ pages = page.build_pages(factory)
+ assert(len(pages) == 3)
+ assert(pages[0] == '')
+ assert(pages[1] == 'sub/')
+ assert(pages[2] == 'sub/include/')
+
+def test_get_files():
+ tree = MagicMock()
+ tree.trees = []
+ tree.blobs = []
+ tree.path = ''
+ commit = MagicMock()
+ commit.hexsha = '123'
+ commit.tree = tree
+ expected = []
+ for case in [ None, '.file', 'README', 'main.cc' ]:
+ print(str(case))
+ if case:
+ add_node(tree, case)
+ expected.append(case)
+ page = TreePage('test/tree', 'tree test', commit, '', None, '')
+ assert(page.get_files() == expected)
+
diff --git a/tests/tree_test.py b/tests/tree_test.py
index e7d23a8..7ec0f0e 100644
--- a/tests/tree_test.py
+++ b/tests/tree_test.py
@@ -1,100 +1,12 @@
-import os
-from pytest import fail
-from unittest.mock import MagicMock
+from unittest.mock import Mock
-from g2h.tree_page import TreePage
+from g2h.tree import safe_join
-def test_init():
- repo = MagicMock()
- commit = MagicMock()
- commit.hexsha = '123'
- page = TreePage('test/tree', 'tree test', commit, 'test/sub/tree', repo, '')
- assert(page.name == 'tree test')
- assert(page.repo == repo)
- assert(page.commit == commit)
- commit.tree.join.assert_called_once_with('test/sub/tree')
- commit_no_subtree = MagicMock()
- commit_no_subtree.hexsha = '123'
- commit_no_subtree.tree = 'no sub tree'
- page_no_subtree = TreePage('test/tree', 'tree test', commit_no_subtree, '', repo, '')
- assert(page_no_subtree.name == 'tree test')
- assert(page_no_subtree.repo == repo)
- assert(page_no_subtree.commit == commit_no_subtree)
- assert(page_no_subtree.tree == 'no sub tree')
-
-def add_nodes(parent, paths):
- nodes = []
- for node in paths:
- nodes.append(add_node(parent, node))
- return nodes
-
-def add_node(parent, path):
- node = MagicMock()
- node.trees = []
- node.blobs = []
- node.name = path
- node.path = os.path.join(parent.path, path)
- if path.endswith('/'):
- parent.trees.append(node)
- else:
- parent.blobs.append(node)
- return node
-
-def test_context_empty():
- commit = MagicMock()
- commit.hexsha = '123'
- commit.tree.path = 'tree/path'
- page = TreePage('test/tree', 'tree test', commit, '', None, '')
- context = page.context()
- assert(context['entries'] == [])
- assert(context['content_subdir'] == '')
- assert(context['subdir'] == '/123/t/tree/path')
- assert(context['title'] == 'tree test: Tree')
-
-def test_context_entries():
- tree = MagicMock()
- tree.trees = []
- tree.blobs = []
- repo = MagicMock()
- sub_dir = add_node(tree, 'sub/')
- sub_file = add_node(sub_dir, 'file.txt')
- readme = add_node(tree, 'README')
- commit = MagicMock()
- commit.hexsha = '123'
- commit.author.email = 'unit@te.st'
- commit.tree = tree
- commit.committed_datetime = 'Date Time'
- repo.iter_commits = MagicMock(return_value=iter([commit, commit]))
- page = TreePage('test/tree', 'tree test', commit, '', repo, '')
- context = page.context()
- assert(repo.iter_commits.called)
- assert(context['entries'] == [{'name': 'sub/', 'modified': 'Date Time'}, {'name': 'README', 'modified': 'Date Time'}])
-
-def test_traversal():
- factory = MagicMock()
- def new_tree_sideeffect(*args, **kwargs):
- return args[1]
- factory.new_tree.side_effect = new_tree_sideeffect
- tree = MagicMock()
- tree.trees = []
- tree.blobs = []
- tree.path = ''
- root_tree = add_nodes(tree, ['README', 'sub/'])
- sub_tree = add_nodes(root_tree[1], ['main.cc', 'lib.cc', 'include/'])
- add_node(sub_tree[2], 'lib.h')
- def tree_join_sideeffect(*args, **kwargs):
- if args[0] == 'sub/':
- return root_tree[1]
- elif args[0] == 'sub/include/':
- return sub_tree[2]
- fail('Unexpected tree join: ' + args[0])
- tree.join.side_effect = tree_join_sideeffect
- commit = MagicMock()
- commit.tree = tree
- page = TreePage('test/tree', 'tree test', commit, '', None, '')
- pages = page.build_pages(factory)
- assert(len(pages) == 3)
- assert(pages[0] == '')
- assert(pages[1] == 'sub/')
- assert(pages[2] == 'sub/include/')
+def test_safe_join_empty():
+ tree = Mock()
+ assert(safe_join(tree, '') == tree)
+def test_safe_join_non_empty():
+ tree = Mock()
+ safe_join(tree, 'test')
+ tree.join.assert_called_with('test')