1 //
2 // aegis - project change supervisor
3 // Copyright (C) 2012 Peter Miller
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or (at
8 // your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program. If not, see <http://www.gnu.org/licenses/>.
17 //
18
19 #include <common/ac/string.h>
20
21 #include <common/nstring/list.h>
22 #include <libaegis/attribute.h>
23 #include <libaegis/change/file.h>
24 #include <libaegis/sub.h>
25
26 #include <aede-policy/validation/man_pages.h>
27
28
~validation_man_pages()29 validation_man_pages::~validation_man_pages()
30 {
31 }
32
33
validation_man_pages()34 validation_man_pages::validation_man_pages()
35 {
36 }
37
38
39 validation_man_pages::pointer
create(void)40 validation_man_pages::create(void)
41 {
42 return pointer(new validation_man_pages());
43 }
44
45
46 static nstring
extract_man_page_details(const nstring_list & part,size_t idx)47 extract_man_page_details(const nstring_list &part, size_t idx)
48 {
49 if (idx + 2 != part.size())
50 return nstring();
51 if (!part[idx].gmatch("man[18nl]*"))
52 return nstring();
53 nstring section = "." + part[idx].substr(3, 1);
54 nstring progname = part[idx + 1];
55 const char *s = progname.c_str();
56 const char *extn = strstr(s, section.c_str());
57 if (!extn || extn == s)
58 return nstring();
59 progname = nstring(s, extn - s);
60 return progname;
61 }
62
63
64 static nstring
extract_man_page_details(const nstring & filename)65 extract_man_page_details(const nstring &filename)
66 {
67 nstring_list part;
68 part.split(filename, "/");
69
70 // e.g. man/man1/fred.1
71 if (part.size() == 3 && part[0] == "man")
72 {
73 nstring progname = extract_man_page_details(part, 1);
74 if (!progname.empty())
75 return progname;
76 }
77 // e.g. "man1/fred.1"
78 if (part.size() == 2)
79 {
80 nstring progname = extract_man_page_details(part, 0);
81 if (!progname.empty())
82 return progname;
83 }
84 // e.g. "lib/en/man1/fred.1"
85 if (part.size() >= 3 && part[part.size() - 3] == "en")
86 {
87 nstring progname = extract_man_page_details(part, part.size() - 2);
88 if (!progname.empty())
89 return progname;
90 }
91 return nstring();
92 }
93
94
95 static nstring
extract_progname_details(const nstring & filename)96 extract_progname_details(const nstring &filename)
97 {
98 nstring_list part;
99 part.split(filename, "/");
100 if (part.size() != 2)
101 return nstring();
102
103 // look for C and C++ program entry points
104 if (part[1] == "main.c" || part[1] == "main.cc")
105 return part[0];
106
107 // look for sripts
108 if (part[0] == "script" || part[0] == "scripts")
109 {
110 nstring progname = part[1];
111 if (progname.ends_with(".in"))
112 progname = progname.substr(0, progname.size() - 3);
113 if (progname.ends_with(".sh") || progname.ends_with(".pl"))
114 return progname.substr(0, progname.size() - 3);
115 }
116
117 // i gots nufink
118 return nstring();
119 }
120
121
122 static nstring
extract_progname_details_generous(const nstring & filename)123 extract_progname_details_generous(const nstring &filename)
124 {
125 nstring_list part;
126 part.split(filename, "/");
127 if (part.size() < 2)
128 return nstring();
129
130 // looks for scripts
131 if (part[0] == "script" || part[0] == "scripts")
132 {
133 nstring progname = part[1];
134 if (progname.ends_with(".in"))
135 progname = progname.substr(0, progname.size() - 3);
136 if (progname.ends_with(".sh") || progname.ends_with(".pl"))
137 return progname.substr(0, progname.size() - 3);
138 }
139
140 // looks for C and C++ programs
141 if (part[1].ends_with(".c") || part[1].ends_with(".cc"))
142 return part[0];
143
144 // i gots nufink
145 return nstring();
146 }
147
148
149 static bool
file_attr_noinst(fstate_src_ty * src)150 file_attr_noinst(fstate_src_ty *src)
151 {
152 return
153 (
154 src
155 &&
156 src->attribute
157 &&
158 attributes_list_find_boolean(src->attribute, "aemakegen:noinst")
159 );
160 }
161
162
163 bool
run(change::pointer cp)164 validation_man_pages::run(change::pointer cp)
165 {
166 //
167 // Don't check branches, only individual changes have control over
168 // the presence of man pages.
169 //
170 if (cp->was_a_branch())
171 return true;
172
173 //
174 // Don't perform this check for changes downloaded and applied by
175 // aedist, because the original developer is no longer in control.
176 //
177 if (was_downloaded(cp))
178 return true;
179
180 //
181 // Don't perform this check for change sets marked as owning a
182 // foreign copyright.
183 //
184 if (cp->attributes_get_boolean("foreign-copyright"))
185 return true;
186
187 //
188 // Look at every file in the (project + change set), building lists
189 // of program names and man page names.
190 //
191 nstring_list man_pages;
192 nstring_list programs;
193 for (long j = 0; ; ++j)
194 {
195 fstate_src_ty *src = change_file_nth(cp, j, view_path_extreme);
196 if (!src)
197 break;
198 nstring filename(src->file_name);
199
200 // remember man page names, but only sections 1 and 8
201 {
202 nstring progname = extract_man_page_details(filename);
203 if (!progname.empty())
204 {
205 man_pages.push_back_unique(progname);
206 }
207 }
208
209 // remember program names, but only installable ones
210 {
211 nstring progname = extract_progname_details(filename);
212 if
213 (
214 !progname.empty()
215 &&
216 !file_attr_noinst(src)
217 &&
218 !progname.starts_with("test_")
219 &&
220 !progname.starts_with("test-")
221 &&
222 !progname.starts_with("noinst_")
223 &&
224 !progname.starts_with("noinst-")
225 )
226 {
227 programs.push_back_unique(progname);
228 }
229 }
230 }
231
232 //
233 // Check each file in the change set.
234 // If a file is a part of a specific program,
235 // verify that the program has a man page.
236 //
237 unsigned number_of_errors = 0;
238 for (long j = 0; ; ++j)
239 {
240 fstate_src_ty *src = change_file_nth(cp, j, view_path_first);
241 if (!src)
242 break;
243 nstring filename(src->file_name);
244
245 nstring candidate = extract_progname_details_generous(filename);
246 if (!candidate.empty() && programs.member(candidate))
247 {
248 if (!man_pages.member(candidate))
249 {
250 sub_context_ty sc;
251 sc.var_set_string("File_Name", candidate);
252 change_error(cp, &sc, i18n("prog $filename has no man page"));
253 ++number_of_errors;
254 }
255 // only check once per program
256 programs.remove(candidate);
257 }
258 }
259
260 return (number_of_errors == 0);
261 }
262
263
264 // vim: set ts=8 sw=4 et :
265