1 // inspect program -------------------------------------------------------------------//
3 // Copyright Beman Dawes 2002.
4 // Copyright Rene Rivera 2004-2006.
5 // Copyright Gennaro Prota 2006.
7 // Distributed under the Boost Software License, Version 1.0.
8 // (See accompanying file LICENSE_1_0.txt or copy at
9 // http://www.boost.org/LICENSE_1_0.txt)
11 // This program recurses through sub-directories looking for various problems.
12 // It contains some Boost specific features, like ignoring "CVS" and "bin",
13 // and the code that identifies library names assumes the Boost directory
16 // See http://www.boost.org/tools/inspect/ for more information.
18 const char* boost_no_inspect = "boost-" "no-inspect";
20 // Directories with a file name of the boost_no_inspect value are not inspected.
21 // Files that contain the boost_no_inspect value are not inspected.
29 #include "boost/shared_ptr.hpp"
30 #include "boost/lexical_cast.hpp"
31 #include "boost/filesystem/operations.hpp"
32 #include "boost/filesystem/fstream.hpp"
34 #include <stdio.h> // for popen, pclose
37 # define PCLOSE _pclose
40 # define PCLOSE pclose
43 #include "time_string.hpp"
45 #include "inspector.hpp"
48 #include "copyright_check.hpp"
49 #include "crlf_check.hpp"
50 #include "end_check.hpp"
51 #include "license_check.hpp"
52 #include "link_check.hpp"
53 #include "path_name_check.hpp"
54 #include "tab_check.hpp"
55 #include "ascii_check.hpp"
56 #include "apple_macro_check.hpp"
57 #include "assert_macro_check.hpp"
58 #include "deprecated_macro_check.hpp"
59 #include "minmax_check.hpp"
60 #include "unnamed_namespace_check.hpp"
62 //#include "cvs_iterator.hpp"
64 #if !defined(INSPECT_USE_BOOST_TEST)
65 #define INSPECT_USE_BOOST_TEST 0
68 #if INSPECT_USE_BOOST_TEST
69 #include "boost/test/included/prg_exec_monitor.hpp"
72 namespace fs = boost::filesystem;
74 using namespace boost::inspect;
78 fs::path search_root = fs::initial_path();
80 class inspector_element
82 typedef boost::shared_ptr< boost::inspect::inspector > inspector_ptr;
85 inspector_ptr inspector;
87 inspector_element( boost::inspect::inspector * p ) : inspector(p) {}
90 typedef std::list< inspector_element > inspector_list;
93 long directory_count = 0;
95 const int max_offenders = 5; // maximum "worst offenders" to display
97 boost::inspect::string_set content_signatures;
106 bool operator<( const error_msg & rhs ) const
108 if ( library < rhs.library ) return true;
109 if ( library > rhs.library ) return false;
110 if ( rel_path < rhs.rel_path ) return true;
111 if ( rel_path > rhs.rel_path ) return false;
112 if ( line_number < rhs.line_number ) return true;
113 if ( line_number > rhs.line_number ) return false;
114 return msg < rhs.msg;
118 typedef std::vector< error_msg > error_msg_vector;
119 error_msg_vector msgs;
121 struct lib_error_count
126 bool operator<( const lib_error_count & rhs ) const
128 return error_count > rhs.error_count;
132 typedef std::vector< lib_error_count > lib_error_count_vector;
133 lib_error_count_vector libs;
135 // run subversion to get revisions info ------------------------------------//
137 // implemented as function object that can be passed to boost::execution_monitor
138 // in order to swallow any errors from 'svn info'.
142 explicit svn_check(const fs::path & inspect_root) :
143 inspect_root(inspect_root), fp(0) {}
146 string rev("unknown");
147 string repos("unknown");
148 string command("cd ");
149 command += inspect_root.string() + " && svn info";
151 fp = (POPEN(command.c_str(), "r"));
154 static const int line_max = 128;
156 while (fgets(line, line_max, fp) != NULL)
159 string::size_type pos;
160 if ((pos = ln.find("Revision: ")) != string::npos)
161 rev = ln.substr(pos + 10);
162 else if ((pos = ln.find("URL: ")) != string::npos)
163 repos = ln.substr(pos + 5);
167 result = repos + " at revision " + rev;
171 ~svn_check() { if (fp) PCLOSE(fp); }
173 const fs::path & inspect_root;
177 svn_check(svn_check const&);
178 svn_check const& operator=(svn_check const&);
181 // Small helper class because svn_check can't be passed by copy.
182 template <typename F, typename R>
183 struct nullary_function_ref
185 explicit nullary_function_ref(F& f) : f(f) {}
186 R operator()() const { return f(); }
190 // get info (as a string) if inspect_root is svn working copy --------------//
192 string info( const fs::path & inspect_root )
194 svn_check check(inspect_root);
196 #if !INSPECT_USE_BOOST_TEST
201 boost::execution_monitor e;
202 e.execute(nullary_function_ref<svn_check, int>(check));
204 catch(boost::execution_exception const& e) {
205 if (e.code() == boost::execution_exception::system_error) {
206 // There was an error running 'svn info' - it probably
207 // wasn't run in a subversion repo.
208 return string("unknown");
220 // visit_predicate (determines which directories are visited) --------------//
222 typedef bool(*pred_type)(const path&);
224 bool visit_predicate( const path & pth )
226 string local( boost::inspect::relative_to( pth, search_root_path() ) );
227 string leaf( pth.leaf().string() );
228 if (leaf[0] == '.') // ignore hidden by convention directories such as
229 return false; // .htaccess, .git, .svn, .bzr, .DS_Store, etc.
232 // so we can inspect a CVS checkout
234 // don't look at binaries
237 // no point in checking doxygen xml output
238 && local.find("doc/xml") != 0
239 && local.find("doc\\xml") != 0
240 // ignore if tag file present
241 && !boost::filesystem::exists(pth / boost_no_inspect)
245 // library_from_content ----------------------------------------------------//
247 string library_from_content( const string & content )
249 const string unknown_library ( "unknown" );
250 const string lib_root ( "www.boost.org/libs/" );
251 string::size_type pos( content.find( lib_root ) );
253 string lib = unknown_library;
255 if ( pos != string::npos ) {
257 pos += lib_root.length();
259 const char delims[] = " " // space and...
262 string::size_type n = content.find_first_of( string(delims), pos );
263 if (n != string::npos)
264 lib = string(content, pos, n - pos);
271 // find_signature ----------------------------------------------------------//
273 bool find_signature( const path & file_path,
274 const boost::inspect::string_set & signatures )
276 string name( file_path.leaf().string() );
277 if ( signatures.find( name ) == signatures.end() )
279 string::size_type pos( name.rfind( '.' ) );
280 if ( pos == string::npos
281 || signatures.find( name.substr( pos ) )
282 == signatures.end() ) return false;
287 // load_content ------------------------------------------------------------//
289 void load_content( const path & file_path, string & target )
293 if ( !find_signature( file_path, content_signatures ) ) return;
295 fs::ifstream fin( file_path, std::ios_base::in|std::ios_base::binary );
297 throw string( "could not open input file: " ) + file_path.string();
298 std::getline( fin, target, '\0' ); // read the whole file
301 // check -------------------------------------------------------------------//
303 void check( const string & lib,
304 const path & pth, const string & content, const inspector_list & insp_list )
306 // invoke each inspector
307 for ( inspector_list::const_iterator itr = insp_list.begin();
308 itr != insp_list.end(); ++itr )
310 itr->inspector->inspect( lib, pth ); // always call two-argument form
311 if ( find_signature( pth, itr->inspector->signatures() ) )
313 itr->inspector->inspect( lib, pth, content );
318 // visit_all ---------------------------------------------------------------//
320 template< class DirectoryIterator >
321 void visit_all( const string & lib,
322 const path & dir_path, const inspector_list & insps )
324 static DirectoryIterator end_itr;
327 for ( DirectoryIterator itr( dir_path ); itr != end_itr; ++itr )
329 if ( fs::is_directory( *itr ) )
331 if ( visit_predicate( *itr ) )
333 string cur_lib( boost::inspect::impute_library( *itr ) );
334 check( cur_lib, *itr, "", insps );
335 visit_all<DirectoryIterator>( cur_lib, *itr, insps );
338 else if (itr->path().leaf().string()[0] != '.') // ignore if hidden
342 load_content( *itr, content );
343 if (content.find(boost_no_inspect) == string::npos)
344 check( lib.empty() ? library_from_content( content ) : lib,
345 *itr, content, insps );
350 // display -----------------------------------------------------------------//
352 enum display_format_type
354 display_html, display_text
356 display_format = display_html;
358 enum display_mode_type
360 display_full, display_brief
362 display_mode = display_full;
364 // display_summary_helper --------------------------------------------------//
366 void display_summary_helper( const string & current_library, int err_count )
368 if (display_format == display_text)
370 std::cout << " " << current_library << " (" << err_count << ")\n";
376 << current_library // what about malformed for URI refs? [gps]
377 << "\">" << current_library
379 << err_count << ")<br>\n";
383 // display_summary ---------------------------------------------------------//
385 void display_summary()
387 if (display_format == display_text)
389 std::cout << "Summary:\n";
399 string current_library( msgs.begin()->library );
401 for ( error_msg_vector::iterator itr ( msgs.begin() );
402 itr != msgs.end(); ++itr )
404 if ( current_library != itr->library )
406 display_summary_helper( current_library, err_count );
407 current_library = itr->library;
412 display_summary_helper( current_library, err_count );
414 if (display_format == display_text)
417 std::cout << "</blockquote>\n";
420 // html_encode -------------------------------------------------------------//
422 std::string html_encode(std::string const& text)
426 for(std::string::const_iterator it = text.begin(),
427 end = text.end(); it != end; ++it)
447 // display_details ---------------------------------------------------------//
449 void display_details()
451 if (display_format == display_text)
453 // display error messages with group indication
456 for ( error_msg_vector::iterator itr ( msgs.begin() );
457 itr != msgs.end(); ++itr )
459 if ( current.library != itr->library )
461 if ( display_full == display_mode )
462 std::cout << "\n|" << itr->library << "|\n";
464 std::cout << "\n\n|" << itr->library << '|';
467 if ( current.library != itr->library
468 || current.rel_path != itr->rel_path )
470 if ( display_full == display_mode )
472 std::cout << " " << itr->rel_path << ":\n";
476 path current_rel_path(current.rel_path);
477 path this_rel_path(itr->rel_path);
478 if (current_rel_path.branch_path() != this_rel_path.branch_path())
480 std::cout << "\n " << this_rel_path.branch_path().string() << '/';
482 std::cout << "\n " << this_rel_path.leaf() << ':';
485 if ( current.library != itr->library
486 || current.rel_path != itr->rel_path
487 || current.msg != itr->msg )
489 const string m = itr->msg;
491 if ( display_full == display_mode )
492 std::cout << " " << m << '\n';
494 std::cout << ' ' << m;
496 current.library = itr->library;
497 current.rel_path = itr->rel_path;
498 current.msg = itr->msg;
504 // display error messages with group indication
506 bool first_sep = true;
508 for ( error_msg_vector::iterator itr ( msgs.begin() );
509 itr != msgs.end(); ++itr )
511 if ( current.library != itr->library )
513 if ( !first ) std::cout << "</pre>\n";
514 std::cout << "\n<h3><a name=\"" << itr->library
515 << "\">" << itr->library << "</a></h3>\n<pre>";
517 if ( current.library != itr->library
518 || current.rel_path != itr->rel_path )
521 std::cout << itr->rel_path;
524 if ( current.library != itr->library
525 || current.rel_path != itr->rel_path
526 || current.msg != itr->msg )
530 if (itr->line_number) sep = ":<br> ";
533 if (itr->line_number) sep = "<br> ";
537 if (itr->line_number)
538 std::cout << sep << "(line " << itr->line_number << ") " << html_encode(itr->msg);
539 else std::cout << sep << html_encode(itr->msg);
543 current.library = itr->library;
544 current.rel_path = itr->rel_path;
545 current.msg = itr->msg;
548 std::cout << "</pre>\n";
553 // worst_offenders_count_helper --------------------------------------------------//
555 void worst_offenders_count_helper( const string & current_library, int err_count )
558 lec.library = current_library;
559 lec.error_count = err_count;
560 libs.push_back( lec );
562 // worst_offenders_count -----------------------------------------------------//
564 void worst_offenders_count()
570 string current_library( msgs.begin()->library );
572 for ( error_msg_vector::iterator itr ( msgs.begin() );
573 itr != msgs.end(); ++itr )
575 if ( current_library != itr->library )
577 worst_offenders_count_helper( current_library, err_count );
578 current_library = itr->library;
583 worst_offenders_count_helper( current_library, err_count );
586 // display_worst_offenders -------------------------------------------------//
588 void display_worst_offenders()
590 if (display_mode == display_brief)
592 if (display_format == display_text)
594 std::cout << "Worst Offenders:\n";
599 "<h2>Worst Offenders</h2>\n"
604 int display_count = 0;
605 int last_error_count = 0;
606 for ( lib_error_count_vector::iterator itr ( libs.begin() );
608 && (display_count < max_offenders
609 || itr->error_count == last_error_count);
610 ++itr, ++display_count )
612 if (display_format == display_text)
614 std::cout << itr->library << " " << itr->error_count << "\n";
621 << "\">" << itr->library
623 << itr->error_count << ")<br>\n";
625 last_error_count = itr->error_count;
628 if (display_format == display_text)
631 std::cout << "</blockquote>\n";
635 const char * options()
648 " -deprecated_macro\n"
651 " default is all checks on; otherwise options specify desired checks"
655 const char * doctype_declaration()
658 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
659 "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
663 std::string validator_link(const std::string & text)
666 // with link to validation service
667 "<a href=\"http://validator.w3.org/check?uri=referer\">"
673 } // unnamed namespace
680 // line_break --------------------------------------------------------------//
682 const char * line_break()
684 return display_format ? "\n" : "<br>\n";
687 // search_root_path --------------------------------------------------------//
689 path search_root_path()
694 // register_signature ------------------------------------------------------//
696 void inspector::register_signature( const string & signature )
698 m_signatures.insert( signature );
699 content_signatures.insert( signature );
702 // error -------------------------------------------------------------------//
704 void inspector::error( const string & library_name,
705 const path & full_path, const string & msg, int line_number )
709 err_msg.library = library_name;
710 err_msg.rel_path = relative_to( full_path, search_root_path() );
712 err_msg.line_number = line_number;
713 msgs.push_back( err_msg );
715 // std::cout << library_name << ": "
716 // << full_path.string() << ": "
721 source_inspector::source_inspector()
723 // C/C++ source code...
724 register_signature( ".c" );
725 register_signature( ".cpp" );
726 register_signature( ".css" );
727 register_signature( ".cxx" );
728 register_signature( ".h" );
729 register_signature( ".hpp" );
730 register_signature( ".hxx" );
731 register_signature( ".inc" );
732 register_signature( ".ipp" );
734 // Boost.Build BJam source code...
735 register_signature( "Jamfile" );
736 register_signature( ".jam" );
737 register_signature( ".v2" );
739 // Other scripts; Python, shell, autoconfig, etc.
740 register_signature( "configure.in" );
741 register_signature( "GNUmakefile" );
742 register_signature( "Makefile" );
743 register_signature( ".bat" );
744 register_signature( ".mak" );
745 register_signature( ".pl" );
746 register_signature( ".py" );
747 register_signature( ".sh" );
749 // Hypertext, Boost.Book, and other text...
750 register_signature( "news" );
751 register_signature( "readme" );
752 register_signature( "todo" );
753 register_signature( "NEWS" );
754 register_signature( "README" );
755 register_signature( "TODO" );
756 register_signature( ".boostbook" );
757 register_signature( ".htm" );
758 register_signature( ".html" );
759 register_signature( ".rst" );
760 register_signature( ".sgml" );
761 register_signature( ".shtml" );
762 register_signature( ".txt" );
763 register_signature( ".xml" );
764 register_signature( ".xsd" );
765 register_signature( ".xsl" );
766 register_signature( ".qbk" );
769 hypertext_inspector::hypertext_inspector()
771 register_signature( ".htm" );
772 register_signature( ".html" );
773 register_signature( ".shtml" );
776 // impute_library ----------------------------------------------------------//
778 // may return an empty string [gps]
779 string impute_library( const path & full_dir_path )
781 path relative( relative_to( full_dir_path, search_root_path() ) );
782 if ( relative.empty() ) return "boost-root";
783 string first( (*relative.begin()).string() );
784 string second = // borland 5.61 requires op=
785 ++relative.begin() == relative.end()
786 ? string() : (*++relative.begin()).string();
788 if ( first == "boost" )
791 return (( first == "libs" || first == "tools" ) && !second.empty())
795 } // namespace inspect
798 // cpp_main() --------------------------------------------------------------//
800 #if !INSPECT_USE_BOOST_TEST
801 int main( int argc_param, char * argv_param[] )
803 int cpp_main( int argc_param, char * argv_param[] )
806 // <hack> for the moment, let's be on the safe side
807 // and ensure we don't modify anything being pointed to;
808 // then we'll do some cleanup here
809 int argc = argc_param;
810 const char* const * argv = &argv_param[0];
812 if ( argc > 1 && (std::strcmp( argv[1], "-help" ) == 0
813 || std::strcmp( argv[1], "--help" ) == 0 ) )
815 //std::clog << "Usage: inspect [search-root] [-cvs] [-text] [-brief] [options...]\n\n"
816 std::clog << "Usage: inspect [search-root] [-text] [-brief] [options...]\n\n"
817 " search-root default is the current directory (i.e. '.')\n\n"
819 << options() << '\n';
823 bool license_ck = true;
824 bool copyright_ck = true;
828 bool path_name_ck = true;
830 bool ascii_ck = true;
831 bool apple_ck = true;
832 bool assert_ck = true;
833 bool deprecated_ck = true;
834 bool minmax_ck = true;
835 bool unnamed_ck = true;
838 if ( argc > 1 && *argv[1] != '-' )
840 search_root = fs::canonical(fs::absolute(argv[1], fs::initial_path()));
844 //if ( argc > 1 && std::strcmp( argv[1], "-cvs" ) == 0 )
850 if ( argc > 1 && std::strcmp( argv[1], "-text" ) == 0 )
852 display_format = display_text;
856 if ( argc > 1 && std::strcmp( argv[1], "-brief" ) == 0 )
858 display_mode = display_brief;
862 if ( argc > 1 && *argv[1] == '-' )
865 copyright_ck = false;
869 path_name_ck = false;
874 deprecated_ck = false;
879 bool invalid_options = false;
880 for(; argc > 1; --argc, ++argv )
882 if ( std::strcmp( argv[1], "-license" ) == 0 )
884 else if ( std::strcmp( argv[1], "-copyright" ) == 0 )
886 else if ( std::strcmp( argv[1], "-crlf" ) == 0 )
888 else if ( std::strcmp( argv[1], "-end" ) == 0 )
890 else if ( std::strcmp( argv[1], "-link" ) == 0 )
892 else if ( std::strcmp( argv[1], "-path_name" ) == 0 )
894 else if ( std::strcmp( argv[1], "-tab" ) == 0 )
896 else if ( std::strcmp( argv[1], "-ascii" ) == 0 )
898 else if ( std::strcmp( argv[1], "-apple_macro" ) == 0 )
900 else if ( std::strcmp( argv[1], "-assert_macro" ) == 0 )
902 else if ( std::strcmp( argv[1], "-deprecated_macro" ) == 0 )
903 deprecated_ck = true;
904 else if ( std::strcmp( argv[1], "-minmax" ) == 0 )
906 else if ( std::strcmp( argv[1], "-unnamed" ) == 0 )
910 std::cerr << "unknown option: " << argv[1] << '\n';
911 invalid_options = true;
914 if ( invalid_options ) {
915 std::cerr << "\nvalid options are:\n"
920 string inspector_keys;
922 { // begin reporting block
924 // since this is in its own block; reporting will happen
925 // automatically, from each registered inspector, when
926 // leaving, due to destruction of the inspector_list object
927 inspector_list inspectors;
930 inspectors.push_back( inspector_element( new boost::inspect::license_check ) );
932 inspectors.push_back( inspector_element( new boost::inspect::copyright_check ) );
934 inspectors.push_back( inspector_element( new boost::inspect::crlf_check ) );
936 inspectors.push_back( inspector_element( new boost::inspect::end_check ) );
938 inspectors.push_back( inspector_element( new boost::inspect::link_check ) );
940 inspectors.push_back( inspector_element( new boost::inspect::file_name_check ) );
942 inspectors.push_back( inspector_element( new boost::inspect::tab_check ) );
944 inspectors.push_back( inspector_element( new boost::inspect::ascii_check ) );
946 inspectors.push_back( inspector_element( new boost::inspect::apple_macro_check ) );
948 inspectors.push_back( inspector_element( new boost::inspect::assert_macro_check ) );
950 inspectors.push_back( inspector_element( new boost::inspect::deprecated_macro_check ) );
952 inspectors.push_back( inspector_element( new boost::inspect::minmax_check ) );
954 inspectors.push_back( inspector_element( new boost::inspect::unnamed_namespace_check ) );
956 //// perform the actual inspection, using the requested type of iteration
958 // visit_all<hack::cvs_iterator>( search_root.leaf().string(),
959 // search_root, inspectors );
961 visit_all<fs::directory_iterator>( search_root.leaf().string(),
962 search_root, inspectors );
965 for ( inspector_list::iterator itr = inspectors.begin();
966 itr != inspectors.end(); ++itr )
968 itr->inspector->close();
971 string run_date ( "n/a" );
972 boost::time_string( run_date );
974 if (display_format == display_text)
978 "Boost Inspection Report\n"
979 "Run Date: " << run_date << "\n"
985 << " " << file_count << " files scanned\n"
986 << " " << directory_count << " directories scanned (including root)\n"
987 << " " << error_count << " problems reported\n"
994 std::cout << doctype_declaration() << '\n';
999 "<style> body { font-family: sans-serif; } </style>\n"
1000 "<title>Boost Inspection Report</title>\n"
1004 // we should not use a table, of course [gps]
1007 "<td><img src=\"http://www.boost.org/boost.png\" alt=\"Boost logo\" />"
1010 "<h1>Boost Inspection Report</h1>\n"
1011 "<b>Run Date:</b> " << run_date << "\n"
1012 //" / " << validator_link( "validate me" ) << " /\n"
1017 "<p>This report is generated by an <a href=\"http://www.boost.org/tools/inspect/index.html\">inspection\n"
1018 "program</a> that checks files for the problems noted below.</p>\n"
1021 << "<p>The files checked were from "
1022 << info( search_root_path() )
1027 << "<h2>Totals</h2>\n"
1028 << file_count << " files scanned<br>\n"
1029 << directory_count << " directories scanned (including root)<br>\n"
1030 << error_count << " problems reported\n<p>";
1033 for ( inspector_list::iterator itr = inspectors.begin();
1034 itr != inspectors.end(); ++itr )
1037 inspector_keys += static_cast<string>(" ")
1038 + itr->inspector->name()
1039 + ' ' + itr->inspector->desc()
1044 if (display_format == display_text)
1045 std::cout << "\nProblem counts:\n";
1047 std::cout << "\n<h2>Problem counts</h2>\n<blockquote><p>\n" ;
1049 } // end of block: starts reporting
1051 if (display_format == display_text)
1054 std::cout << "</blockquote>\n";
1056 std::sort( msgs.begin(), msgs.end() );
1058 worst_offenders_count();
1059 std::stable_sort( libs.begin(), libs.end() );
1061 if ( !libs.empty() && display_mode != display_brief)
1062 display_worst_offenders();
1064 if ( !msgs.empty() )
1068 if (display_format == display_text)
1070 std::cout << "Details:\n" << inspector_keys;
1071 std::cout << "\nDirectories with a file named \"" << boost_no_inspect << "\" will not be inspected.\n"
1072 "Files containing \"" << boost_no_inspect << "\" will not be inspected.\n";
1076 std::cout << "<h2>Details</h2>\n" << inspector_keys;
1077 std::cout << "\n<p>Directories with a file named \"" << boost_no_inspect << "\" will not be inspected.<br>\n"
1078 "Files containing \"" << boost_no_inspect << "\" will not be inspected.</p>\n";
1083 if (display_format == display_text)
1085 std::cout << "\n\n" ;
1094 return error_count ? 1 : 0;