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