Show More
@@ -0,0 +1,106 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2010-2016 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
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 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 <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import pytest | |
|
22 | ||
|
23 | from rhodecode.lib.vcs import nodes | |
|
24 | from rhodecode.model.repo import ReadmeFinder | |
|
25 | ||
|
26 | ||
|
27 | @pytest.fixture | |
|
28 | def commit_util(vcsbackend_stub): | |
|
29 | """ | |
|
30 | Provide a commit which has certain files in it's tree. | |
|
31 | ||
|
32 | This is based on the fixture "vcsbackend" and will automatically be | |
|
33 | parametrized for all vcs backends. | |
|
34 | """ | |
|
35 | return CommitUtility(vcsbackend_stub) | |
|
36 | ||
|
37 | ||
|
38 | class CommitUtility: | |
|
39 | ||
|
40 | def __init__(self, vcsbackend): | |
|
41 | self.vcsbackend = vcsbackend | |
|
42 | ||
|
43 | def commit_with_files(self, filenames): | |
|
44 | commits = [ | |
|
45 | {'message': 'Adding all requested files', | |
|
46 | 'added': [ | |
|
47 | nodes.FileNode(filename, content='') | |
|
48 | for filename in filenames | |
|
49 | ]}] | |
|
50 | repo = self.vcsbackend.create_repo(commits=commits) | |
|
51 | return repo.get_commit() | |
|
52 | ||
|
53 | ||
|
54 | def test_no_matching_file_returns_none(commit_util): | |
|
55 | commit = commit_util.commit_with_files(['LIESMICH']) | |
|
56 | finder = ReadmeFinder(default_renderer='rst') | |
|
57 | filename = finder.search(commit) | |
|
58 | assert filename is None | |
|
59 | ||
|
60 | ||
|
61 | def test_matching_file_returns_the_file_name(commit_util): | |
|
62 | commit = commit_util.commit_with_files(['README']) | |
|
63 | finder = ReadmeFinder(default_renderer='rst') | |
|
64 | filename = finder.search(commit) | |
|
65 | assert filename == 'README' | |
|
66 | ||
|
67 | ||
|
68 | def test_matching_file_with_extension(commit_util): | |
|
69 | commit = commit_util.commit_with_files(['README.rst']) | |
|
70 | finder = ReadmeFinder(default_renderer='rst') | |
|
71 | filename = finder.search(commit) | |
|
72 | assert filename == 'README.rst' | |
|
73 | ||
|
74 | ||
|
75 | def test_prefers_readme_without_extension(commit_util): | |
|
76 | commit = commit_util.commit_with_files(['README.rst', 'Readme']) | |
|
77 | finder = ReadmeFinder() | |
|
78 | filename = finder.search(commit) | |
|
79 | assert filename == 'Readme' | |
|
80 | ||
|
81 | ||
|
82 | @pytest.mark.parametrize('renderer, expected', [ | |
|
83 | ('rst', 'readme.rst'), | |
|
84 | ('markdown', 'readme.md'), | |
|
85 | ]) | |
|
86 | def test_prefers_renderer_extensions(commit_util, renderer, expected): | |
|
87 | commit = commit_util.commit_with_files( | |
|
88 | ['readme.rst', 'readme.md', 'readme.txt']) | |
|
89 | finder = ReadmeFinder(default_renderer=renderer) | |
|
90 | filename = finder.search(commit) | |
|
91 | assert filename == expected | |
|
92 | ||
|
93 | ||
|
94 | def test_finds_readme_in_subdirectory(commit_util): | |
|
95 | commit = commit_util.commit_with_files(['doc/README.rst', 'LIESMICH']) | |
|
96 | finder = ReadmeFinder() | |
|
97 | filename = finder.search(commit) | |
|
98 | assert filename == 'doc/README.rst' | |
|
99 | ||
|
100 | ||
|
101 | def test_prefers_subdirectory_with_priority(commit_util): | |
|
102 | commit = commit_util.commit_with_files( | |
|
103 | ['Doc/Readme.rst', 'Docs/Readme.rst']) | |
|
104 | finder = ReadmeFinder() | |
|
105 | filename = finder.search(commit) | |
|
106 | assert filename == 'Doc/Readme.rst' |
@@ -947,10 +947,97 b' class ReadmeFinder:' | |||
|
947 | 947 | different. |
|
948 | 948 | """ |
|
949 | 949 | |
|
950 | def __init__(self, default_renderer): | |
|
950 | readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE) | |
|
951 | path_re = re.compile(r'^docs?', re.IGNORECASE) | |
|
952 | ||
|
953 | default_priorities = { | |
|
954 | None: 0, | |
|
955 | '.text': 2, | |
|
956 | '.txt': 3, | |
|
957 | '.rst': 1, | |
|
958 | '.rest': 2, | |
|
959 | '.md': 1, | |
|
960 | '.mkdn': 2, | |
|
961 | '.mdown': 3, | |
|
962 | '.markdown': 4, | |
|
963 | } | |
|
964 | ||
|
965 | path_priority = { | |
|
966 | 'doc': 0, | |
|
967 | 'docs': 1, | |
|
968 | } | |
|
969 | ||
|
970 | FALLBACK_PRIORITY = 99 | |
|
971 | ||
|
972 | RENDERER_TO_EXTENSION = { | |
|
973 | 'rst': ['.rst', '.rest'], | |
|
974 | 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'], | |
|
975 | } | |
|
976 | ||
|
977 | def __init__(self, default_renderer=None): | |
|
951 | 978 | self._default_renderer = default_renderer |
|
979 | self._renderer_extensions = self.RENDERER_TO_EXTENSION.get( | |
|
980 | default_renderer, []) | |
|
952 | 981 | |
|
953 | def search(self, commit): | |
|
982 | def search(self, commit, path='/'): | |
|
983 | """ | |
|
984 | Find a readme in the given `commit`. | |
|
985 | """ | |
|
986 | nodes = commit.get_nodes(path) | |
|
987 | matches = self._match_readmes(nodes) | |
|
988 | matches = self._sort_according_to_priority(matches) | |
|
989 | if matches: | |
|
990 | return matches[0].path | |
|
991 | ||
|
992 | paths = self._match_paths(nodes) | |
|
993 | paths = self._sort_paths_according_to_priority(paths) | |
|
994 | for path in paths: | |
|
995 | match = self.search(commit, path=path) | |
|
996 | if match: | |
|
997 | return match | |
|
998 | ||
|
999 | return None | |
|
1000 | ||
|
1001 | def _match_readmes(self, nodes): | |
|
1002 | for node in nodes: | |
|
1003 | if not node.is_file(): | |
|
1004 | continue | |
|
1005 | path = node.path.rsplit('/', 1)[-1] | |
|
1006 | match = self.readme_re.match(path) | |
|
1007 | if match: | |
|
1008 | extension = match.group(1) | |
|
1009 | yield ReadmeMatch(node, match, self._priority(extension)) | |
|
1010 | ||
|
1011 | def _match_paths(self, nodes): | |
|
1012 | for node in nodes: | |
|
1013 | if not node.is_dir(): | |
|
1014 | continue | |
|
1015 | match = self.path_re.match(node.path) | |
|
1016 | if match: | |
|
1017 | yield node.path | |
|
1018 | ||
|
1019 | def _priority(self, extension): | |
|
1020 | renderer_priority = ( | |
|
1021 | 0 if extension in self._renderer_extensions else 1) | |
|
1022 | extension_priority = self.default_priorities.get( | |
|
1023 | extension, self.FALLBACK_PRIORITY) | |
|
1024 | return (renderer_priority, extension_priority) | |
|
1025 | ||
|
1026 | def _sort_according_to_priority(self, matches): | |
|
1027 | ||
|
1028 | def priority_and_path(match): | |
|
1029 | return (match.priority, match.path) | |
|
1030 | ||
|
1031 | return sorted(matches, key=priority_and_path) | |
|
1032 | ||
|
1033 | def _sort_paths_according_to_priority(self, paths): | |
|
1034 | ||
|
1035 | def priority_and_path(path): | |
|
1036 | return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path) | |
|
1037 | ||
|
1038 | return sorted(paths, key=priority_and_path) | |
|
1039 | ||
|
1040 | def search_old(self, commit): | |
|
954 | 1041 | """ |
|
955 | 1042 | Try to find a readme in the given `commit`. |
|
956 | 1043 | """ |
@@ -966,3 +1053,18 b' class ReadmeFinder:' | |||
|
966 | 1053 | continue |
|
967 | 1054 | |
|
968 | 1055 | return f |
|
1056 | ||
|
1057 | ||
|
1058 | class ReadmeMatch: | |
|
1059 | ||
|
1060 | def __init__(self, node, match, priority): | |
|
1061 | self._node = node | |
|
1062 | self._match = match | |
|
1063 | self.priority = priority | |
|
1064 | ||
|
1065 | @property | |
|
1066 | def path(self): | |
|
1067 | return self._node.path | |
|
1068 | ||
|
1069 | def __repr__(self): | |
|
1070 | return '<ReadmeMatch {} priority={}'.format(self.path, self.priority) |
General Comments 0
You need to be logged in to leave comments.
Login now