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 | different. |
|
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 | self._default_renderer = default_renderer |
|
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 | Try to find a readme in the given `commit`. |
|
1042 | Try to find a readme in the given `commit`. | |
956 | """ |
|
1043 | """ | |
@@ -966,3 +1053,18 b' class ReadmeFinder:' | |||||
966 | continue |
|
1053 | continue | |
967 |
|
1054 | |||
968 | return f |
|
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