1#!/usr/bin/ruby 2 3# This file is part of the aMule project. 4# 5# Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org ) 6# 7# This program is free software; you can redistribute it and/or 8# modify it under the terms of the GNU General Public License 9# as published by the Free Software Foundation; either 10# version 2 of the License, or (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20# 21 22 23# This function returns true if a file is filtered 24# and shound't be included in the sanity testing. 25def IsFiltered(filename) 26 (["./config.h", "./configWIN32.h"].index(filename) != nil) or 27 (filename =~ /^.\/intl\//) or 28 (filename =~ /CryptoPP/) 29end 30 31 32 33# This class represents lines of code, with line-number and text 34# It is used to store the source-files once they have been read 35# and afterwards to store the lines returned by the filters. 36class Line 37 def initialize( number, text ) 38 @number = number 39 @text = text 40 end 41 42 attr_reader :number 43 attr_reader :text 44end 45 46 47 48class Result 49 def initialize( type, file, line = nil ) 50 @type = type 51 @file = file 52 @line = line 53 end 54 55 def file_name 56 @file.slice( /[^\/]+$/ ) 57 end 58 59 def file_path 60 @file.slice( /^.*\// ) 61 end 62 63 attr_reader :type 64 attr_reader :file 65 attr_reader :line 66end 67 68 69 70# Base class for Sanity Checkers 71# 72# This class represents the basic sanity-check, which returns all 73# files as positive results, regardless of the contents. 74class SanityCheck 75 def initialize 76 @name = "None" 77 @title = nil 78 @type = "None" 79 @desc = "None" 80 @results = Array.new 81 end 82 83 attr_reader :name 84 attr_reader :type 85 attr_reader :desc 86 87 def title 88 if @title then 89 @title 90 else 91 @name 92 end 93 end 94 95 96 def results 97 @results 98 end 99 100 # This function will be called for each file, with the argument "file" as the 101 # name of the file and the argument "lines" being an array of Line objects for 102 # each line of the file. 103 # 104 def parse_file(file, lines) 105 raise "Missing parse_file() implementation for Filter: #{@name}" 106 end 107 108 109 private 110 111 def add_results( file, lines = [nil] ) 112 lines.each do |line| 113 @results << Result.new( self, file, line ) 114 end 115 end 116end 117 118 119 120 121 122class CompareAgainstEmptyString < SanityCheck 123 def initialize 124 super 125 126 @name = "CmpEmptyString" 127 @title = "Comparing With Empty String" 128 @type = "Good Practice" 129 @desc = "Comparisons with empty strings, such as wxT(\"\"), wxEmptyString and " 130 @desc += "_(\"\") should be avoided since they force the creation of a temporary " 131 @desc += "string object. The proper method is to use the IsEmpty() member-function " 132 @desc += "of wxString." 133 end 134 135 def parse_file(file, lines) 136 results = lines.select do |line| 137 line.text =~ /[!=]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/ or 138 line.text =~ /(wxEmptyString|wxT\(""\)|_\(""\))\s*[!=]=/ 139 end 140 141 add_results( file, results ) 142 end 143end 144 145 146 147class AssignmentToEmptyString < SanityCheck 148 def initialize 149 super 150 151 @name = "EmptyStringAssignment" 152 @title = "Assigning The Empty String" 153 @type = "Good Practice" 154 @desc = "Assigning an empty string such as wxT(\"\"), wxEmptyString and _(\"\") " 155 @desc += "to a wxString should be avoided, since it forces the creation of a " 156 @desc += "temporary object which is assigned to the string. The proper way to " 157 @desc += "clear a string is to use the Clear() member-function of wxString." 158 end 159 160 def parse_file(file, lines) 161 if file =~ /\.cpp$/ 162 results = lines.select do |line| 163 line.text =~ /[^=!]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/ 164 end 165 166 add_results( file, results ) 167 end 168 end 169end 170 171 172 173class NoIfNDef < SanityCheck 174 def initialize 175 super 176 177 @name = "NoIfNDef" 178 @title = "No #ifndef in headerfile" 179 @type = "Good Practice" 180 @desc = "All header files should contain a #ifndef __<FILENAME>__. The purpose is to ensure " 181 @desc += "that the header can't be included twice, which would introduce a number of problems." 182 end 183 184 def parse_file(file, lines) 185 if file =~ /\.h$/ then 186 if not lines.find { |x| x.text =~ /^#ifndef.*_H/ } then 187 add_results( file ) 188 end 189 end 190 end 191end 192 193 194 195class ThisDeference < SanityCheck 196 def initialize 197 super 198 199 @name = "ThisDeference" 200 @title = "Dereference of \"this\"" 201 @type = "Good Practice" 202 @desc = "In all but the case of templates, using \"this->\" is unnecessary and " 203 @desc += "only decreases the readability of the code." 204 end 205 206 def parse_file(file, lines) 207 results = lines.select do |line| 208 line.text =~ /\bthis->/ 209 end 210 211 add_results( file, results ) 212 end 213end 214 215 216 217class Assert < SanityCheck 218 def initialize 219 super 220 221 @name = "Assert" 222 @type = "Consistency" 223 @desc = "wxASSERT()s should be used rather than normal assert()s " 224 @desc += "for the sake of consistency." 225 end 226 227 def parse_file(file, lines) 228 results = lines.select do |line| 229 line.text =~ /assert\s*\(/ 230 end 231 232 add_results( file, results ) 233 end 234end 235 236 237 238class WxFailUsage < SanityCheck 239 def initialize 240 super 241 242 @name = "WxFailUsagesy" 243 @title = "Always failing wxASSERT()" 244 @type = "Good Practice" 245 @desc = "Use wxFAIL instead of an always failing wxASSERT()." 246 end 247 248 def parse_file(file, lines) 249 results = lines.select do |line| 250 line.text =~ /\bwxASSERT\s*\(\s*(0|false)\s*\)/ or 251 line.text =~ /\bwxASSERT_MSG\s*\(\s*(0|false)\s*,/ 252 end 253 254 add_results( file, results ) 255 end 256end 257 258 259 260class PassByValue < SanityCheck 261 def initialize 262 super 263 264 @name = "PassByValue" 265 @title = "Pass By Value" 266 @type = "Good Practice" 267 @desc = "Passing objects by value means an extra overhead for large datatypes. " 268 @desc += "Therefore these should always be passed by const reference when possible. " 269 @desc += "Non-const references should only be used for functions where the function is " 270 @desc += "supposed to change the actual value of the argument and return another or no value." 271 end 272 273 def parse_file(file, lines) 274 results = Array.new 275 276 # Only handle header files 277 if file =~ /\.h$/ 278 # Items that should not be passed by value 279 items = [ "wxString", "wxRect", "wxPoint", "CMD4Hash", "CPath" ] 280 281 lines.each do |line| 282 # Try to identify function definitions 283 if line.text =~ /^\s*(virtual|static|inline|)\s*\w+\s+\w+\s*\(.*\)/ 284 # Split by arguments 285 if line.text =~ /\(.*\)\s*\{/ 286 args = line.text.match(/\(.*\)\s*\{/)[0] 287 else 288 args = line.text.match(/\(.*\)/)[0] 289 end 290 args.split(",").each do |str| 291 items.each do |item| 292 if str =~ /#{item}\s*[^\s\&\*\(]/ 293 results.push( line ) 294 end 295 end 296 end 297 end 298 end 299 end 300 301 add_results( file, results ) 302 end 303end 304 305 306 307class CStr < SanityCheck 308 def initialize 309 super 310 311 @name = "CStr" 312 @title = "C_Str or GetData" 313 @type = "Unicoding" 314 @desc = "Checks for usage of c_str() or GetData(). Using c_str will often result in " 315 @desc += "problems on Unicoded builds and should therefore be avoided. " 316 @desc += "Please note that the GetData check isn't that precise, because many other " 317 @desc += "classes have GetData members, so it does some crude filtering." 318 end 319 320 def parse_file(file, lines) 321 results = lines.select do |line| 322 if line.text =~ /c_str\(\)/ and line.text !~ /char2unicode\(/ 323 true 324 else 325 line.text =~ /GetData\(\)/ and line.text =~ /(wxT\(|wxString|_\()/ 326 end 327 end 328 329 add_results( file, results ) 330 end 331end 332 333 334 335class IfNotDefined < SanityCheck 336 def initialize 337 super 338 339 @name = "IfDefined" 340 @title = "#if (!)defined" 341 @type = "Consistency" 342 @desc = "Use #ifndef or #ifdef instead for reasons of simplicity." 343 end 344 345 def parse_file(file, lines) 346 results = lines.select do |line| 347 if line.text =~ /^#if.*[\!]?defined\(/ 348 not line.text =~ /(\&\&|\|\|)/ 349 end 350 end 351 352 add_results( file, results ) 353 end 354end 355 356 357 358class GPLLicense < SanityCheck 359 def initialize 360 super 361 362 @name = "MissingGPL" 363 @title = "Missing GPL License" 364 @type = "License" 365 @desc = "All header files should contain the proper GPL blorb." 366 end 367 368 def parse_file(file, lines) 369 if file =~ /\.h$/ 370 if lines.find { |x| x.text =~ /This (program|library) is free software;/ } == nil 371 add_results( file ) 372 end 373 end 374 end 375end 376 377 378 379class Copyright < SanityCheck 380 def initialize 381 super 382 383 @name = "MissingCopyright" 384 @title = "Missing Copyright Notice" 385 @type = "License" 386 @desc = "All files should contain the proper Copyright notice." 387 end 388 389 def parse_file(file, lines) 390 if file =~ /\.h$/ 391 found = lines.select do |line| 392 line.text =~ /Copyright\s*\([cC]\)\s*[-\d,]+ aMule (Project|Team)/ 393 end 394 395 if found.empty? then 396 add_results( file ) 397 end 398 end 399 end 400end 401 402 403 404class PartOfAmule < SanityCheck 405 def initialize 406 super 407 408 @name = "aMuleNotice" 409 @title = "Missing aMule notice" 410 @type = "License" 411 @desc = "All files should contain a notice that they are part of the aMule project." 412 end 413 414 def parse_file(file, lines) 415 if file =~ /\.h$/ 416 found = lines.select do |line| 417 line.text =~ /This file is part of the aMule Project/ or 418 line.text =~ /This file is part of aMule/ 419 end 420 421 if found.empty? then 422 add_results( file ) 423 end 424 end 425 end 426end 427 428 429 430class MissingBody < SanityCheck 431 def initialize 432 super 433 434 @name = "MissingBody" 435 @title = "Missing Body in Loop" 436 @type = "Garbage" 437 @desc = "This check looks for loops without any body. For example \"while(true);\" " 438 @desc += "In most cases this is a sign of either useless code or bugs. Only in a few " 439 @desc += "cases is it valid code, and in those it can often be represented clearer " 440 @desc += "in other ways." 441 end 442 443 def parse_file(file, lines) 444 results = lines.select do |line| 445 if line.text =~ /^[^}]*while\s*\(.*\)\s*;/ or 446 line.text =~ /^\s*for\s*\(.*\)\s*;[^\)]*$/ 447 # Avoid returning "for" spanning multiple lines 448 # TODO A better way to count instances 449 line.text.split("(").size == line.text.split(")").size 450 else 451 false 452 end 453 end 454 455 add_results( file, results ) 456 end 457end 458 459 460 461class Translation < SanityCheck 462 def initialize 463 super 464 465 @name = "Translation" 466 @type = "Consistency" 467 @desc = "Calls to AddLogLine should translate the message, whereas " 468 @desc += "calls to AddDebugLogLine shouldn't. This is because the user " 469 @desc += "is meant to see normal log lines, whereas the the debug-lines " 470 @desc += "are only meant for the developers and I don't know about you, but " 471 @desc += "I don't plan on learning every language we choose to translate " 472 @desc += "aMule to. :P" 473 end 474 475 def parse_file(file, lines) 476 results = lines.select do |line| 477 if line.text =~ /\"/ 478 line.text =~ /AddLogLine.?.?\(.*wxT\(/ or 479 line.text =~ /AddDebugLogLine.?\(.*_\(/ 480 else 481 false 482 end 483 end 484 485 add_results( file, results ) 486 end 487end 488 489 490 491class IfZero < SanityCheck 492 def initialize 493 super 494 495 @name = "PreIfConstant" 496 @title = "#if 0-9" 497 @type = "Garbage" 498 @desc = "Disabled code should be removed as soon as possible. If you wish to disable code " 499 @desc += "for only a short period, then please add a comment before the #if. Code with #if [1-9] " 500 @desc += "should be left, but the #ifs removed unless there is a pressing need for them." 501 end 502 503 def parse_file(file, lines) 504 results = lines.select do |line| 505 line.text =~ /#if\s+[0-9]/ 506 end 507 508 add_results( file, results ) 509 end 510end 511 512 513 514class InlinedIfTrue < SanityCheck 515 def initialize 516 super 517 518 @name = "InlinedIf" 519 @title = "Inlined If true/false" 520 @type = "Garbage" 521 @desc = "Using variations of (x ? true : false) or (x ? false : true) is just plain stupid." 522 end 523 524 def parse_file(file, lines) 525 results = lines.select do |line| 526 line.text =~ /\?\s*(true|false)\s*:\s*(true|false)/i 527 end 528 529 add_results( file, results ) 530 end 531end 532 533 534 535class LoopOnConstant < SanityCheck 536 def initialize 537 super 538 539 @name = "LoopingOnConstant" 540 @title = "Looping On Constant" 541 @type = "Garbage" 542 @desc = "This checks detects loops that evaluate constant values " 543 @desc += "(true,false,0..) or \"for\" loops with no conditionals. all " 544 @desc += "are often a sign of poor code and in most cases can be avoided " 545 @desc += "or replaced with more elegant code." 546 end 547 548 def parse_file(file, lines) 549 results = lines.select do |line| 550 line.text =~ /while\s*\(\s*([0-9]+|true|false)\s*\)/i or 551 line.text =~ /for\s*\([^;]*;\s*;[^;]*\)/ 552 end 553 554 add_results( file, results ) 555 end 556end 557 558 559 560class MissingImplementation < SanityCheck 561 def initialize 562 super 563 564 @name = "MissingImplementation" 565 @title = "Missing Function Implementation" 566 @type = "Garbage" 567 @desc = "Forgetting to remove a function-definition after having removed it " 568 @desc += "from the .cpp file only leads to cluttered header files. The only " 569 @desc += "case where non-implemented functions should be used is when it is " 570 @desc += "necessary to prevent usage of for instance assignment between " 571 @desc += "instances of a class." 572 573 @declarations = Array.new 574 @definitions = Array.new 575 end 576 577 def results 578 @definitions.each do |definition| 579 @declarations.delete_if do |pair| 580 pair.first == definition 581 end 582 end 583 584 585 return @declarations.map do |pair| 586 Result.new( self, pair.last.first, pair.last.last ) 587 end 588 end 589 590 def parse_file(file, lines) 591 if file =~ /\.h$/ then 592 level = 0 593 tree = Array.new 594 595 lines.each do |line| 596 level += line.text.count( "{" ) - line.text.count( "}" ) 597 598 tree.delete_if do |struct| 599 struct.first > level 600 end 601 602 603 if line.text !~ /^\s*\/\// and line.text !~ /^\s*#/ and line.text !~ /typedef/ then 604 if line.text =~ /^\s*(class|struct)\s+/ and line.text.count( ";" ) == 0 then 605 cur_level = level; 606 607 if line.text.count( "{" ) == 0 then 608 cur_level += 1 609 end 610 611 name = line.text.scan( /^\s*(class|struct)\s+([^\:{;]+)/ ) 612 if name != [] then 613 name = name.first.last.strip 614 else 615 name = "Unknown at line " + line.number.to_s + " in " + file 616 end 617 618 tree << [ cur_level, name ] 619 elsif line.text =~ /;\s*$/ and line.text.count( "{" ) == 0 then 620 # No pure virtual functions and no return(blah) calls (which otherwise can fit the requirements) 621 if line.text !~ /=\s*0\s*;\s*$/ and line.text !~ /return/ then 622 re = /^\s*(virtual\s+|static\s+|inline\s+|)\w+(\s+[\*\&]?|[\*\&]\s+)(\w+)\(/.match( line.text ) 623 624 if re and level > 0 and tree.last then 625 @declarations << [ tree.last.last + "::" + re[ re.length - 1 ], [ file, line ] ] 626 end 627 end 628 end 629 end 630 end 631 else 632 lines.each do |line| 633 if line.text =~ /\b\w+::\w+\s*\([^;]+$/ 634 @definitions << line.text.scan( /\b(\w+::\w+)\s*\([^;]+$/ ).first.first 635 end 636 end 637 end 638 end 639end 640 641 642 643 644 645 646 647# List of enabled filters 648filterList = Array.new 649filterList.push CompareAgainstEmptyString.new 650filterList.push AssignmentToEmptyString.new 651filterList.push NoIfNDef.new 652filterList.push ThisDeference.new 653filterList.push Assert.new 654filterList.push PassByValue.new 655filterList.push CStr.new 656filterList.push IfNotDefined.new 657filterList.push GPLLicense.new 658filterList.push Copyright.new 659filterList.push PartOfAmule.new 660filterList.push MissingBody.new 661filterList.push Translation.new 662filterList.push IfZero.new 663filterList.push InlinedIfTrue.new 664filterList.push LoopOnConstant.new 665filterList.push MissingImplementation.new 666filterList.push WxFailUsage.new 667 668 669# Sort enabled filters by type and name. The reason why this is done here is 670# because it's much easier than manually resorting every time I add a filter 671# or change the name or type of an existing filter. 672filterList.sort! do |x,y| 673 cmp = x.type <=> y.type 674 675 if cmp == 0 then 676 x.title <=> y.title 677 else 678 cmp 679 end 680end 681 682 683 684def parse_files( path, filters ) 685 filters = filters.dup 686 687 require "find" 688 689 Find.find( path ) do |filename| 690 if filename =~ /\.(cpp|h)$/ and not IsFiltered(filename) then 691 File.open(filename, "r") do |aFile| 692 # Read lines and add line-numbers 693 lines = Array.new 694 aFile.each_line do |line| 695 lines.push( Line.new( aFile.lineno, line ) ) 696 end 697 698 lines.freeze 699 700 # Check the file against each filter 701 filters.each do |filter| 702 # Process the file with this filter 703 filter.parse_file( filename, lines ) 704 end 705 end 706 end 707 end 708 709 results = Array.new 710 filters.each do |filter| 711 results += filter.results 712 end 713 714 results 715end 716 717 718 719 720# Helper-function 721def get_val( key, list ) 722 if not list.last or list.last.first != key then 723 list << [ key, Array.new ] 724 end 725 726 list.last.last 727end 728 729 730 731def create_result_tree( path, filters ) 732 # Gather the results 733 results = parse_files( path, filters ) 734 735 # Sort the results by the following sequence of variables: Path -> File -> Filter -> Line 736 results.sort! do |a, b| 737 if (a.file_path <=> b.file_path) == 0 then 738 if (a.file_name <=> b.file_name) == 0 then 739 if (a.type.title <=> b.type.title) == 0 then 740 a.line.number <=> b.line.number 741 else 742 a.type.title <=> b.type.title 743 end 744 else 745 a.file_name <=> b.file_name 746 end 747 else 748 a.file_path <=> b.file_path 749 end 750 end 751 752 753 # Create a tree of results: [ Path, [ File, [ Filter, [ Line ] ] ] ] 754 result_tree = Array.new 755 results.each do |result| 756 get_val( result.type, get_val( result.file_name, get_val( result.file_path, result_tree ) ) ) << result 757 end 758 759 760 result_tree 761end 762 763 764 765def create_filter_tree( filters ) 766 # Change the filterList to a tree: [ Type, [ Filter ] ] 767 filter_tree = Array.new 768 769 filters.each do |filter| 770 get_val( filter.type, filter_tree ) << filter 771 end 772 773 filter_tree 774end 775 776 777 778# Converts a number to a string and pads with zeros so that length becomes at least 5 779def PadNum( number ) 780 num = number.to_s 781 782 if ( num.size < 5 ) 783 ( "0" * ( 5 - num.size ) ) + num 784 else 785 num 786 end 787end 788 789 790 791# Helper-function that escapes some chars to HTML codes 792def HTMLEsc( str ) 793 str.gsub!( /\&/, "&" ) 794 str.gsub!( /\"/, """ ) 795 str.gsub!( /</, "<" ) 796 str.gsub!( />/, ">" ) 797 str.gsub!( /\n/, "<br>" ) 798 str.gsub( /\t/, " " ) 799end 800 801 802 803# Fugly output code goes here 804# ... Abandon hope, yee who read past here 805# TODO Enable use of templates. 806# TODO Abandon hope. 807def OutputHTML( filters, results ) 808 text = 809"<html> 810 <head> 811 <STYLE TYPE=\"text/css\"> 812 <!-- 813 .dir { 814 background-color: \#A0A0A0; 815 padding-left: 10pt; 816 padding-right: 10pt; 817 } 818 .file { 819 background-color: \#838383; 820 padding-left: 10pt; 821 padding-right: 10pt; 822 } 823 .filter { 824 background-color: #757575; 825 padding-left: 10pt; 826 padding-right: 10pt; 827 padding-top: 5pt; 828 } 829 --> 830 </STYLE> 831 </head> 832 <body bgcolor=\"\#BDBDBD\"> 833 834 <h1>Filters</h1> 835 <dl> 836" 837 # List the filters 838 filters.each do |filterType| 839 text += 840" <dt><b>#{filterType.first}</b></dt> 841 <dd> 842 <dl> 843" 844 filterType.last.each do |filter| 845 text += 846" <dt id=\"#{filter.name}\"><i>#{filter.title}</i></dt> 847 <dd> 848 #{HTMLEsc(filter.desc)}<p> 849 </dd> 850" 851 end 852 853 text += 854" </dl> 855 </dd> 856" 857 end 858 859 text += 860" </dl> 861 862 <p> 863 864 <h1>Directories</h1> 865 <ul> 866" 867 868 # List the directories 869 results.each do |dir| 870 text += 871" <li> 872 <a href=\"\##{dir.first}\">#{dir.first}</a> 873 </li> 874" 875 end 876 877 text += 878" </ul> 879 880 <p> 881 882 <h1>Results</h1> 883" 884 885 results.each do |dir| 886 text += 887" <div class=\"dir\"> 888 <h2 id=\"#{dir.first}\">#{dir.first}</h2> 889" 890 891 dir.last.each do |file| 892 text += 893" <div class=\"file\"> 894 <h3>#{file.first}</h3> 895 896 <ul> 897" 898 899 file.last.each do |filter| 900 text += 901" <li> 902 <div class=\"filter\"> 903 <b><a href=\"\##{filter.first.name}\">#{filter.first.title}</a></b> 904 905 <ul> 906" 907 908 filter.last.each do |result| 909 if result.line then 910 text += 911" <li><b>#{PadNum(result.line.number)}:</b> #{HTMLEsc(result.line.text.strip)}</li> 912" 913 end 914 end 915 916 text += 917" </ul> 918 </div> 919 </li> 920" 921 end 922 text += 923" </ul> 924 </div> 925" 926 end 927 928 text += 929" </div> 930 931 <p> 932" 933 end 934 935 text += 936" </body> 937</html>" 938 939 return text; 940end 941 942 943 944# Columnizing, using the http://www.rubygarden.org/ruby?UsingTestUnit example because I'm lazy 945# TODO Rewrite it to better support newlines and stuff 946def Columnize( text, width, indent ) 947 return indent + text.scan(/(.{1,#{width}})(?: |$)/).join("\n#{indent}") 948end 949 950 951 952# Fugly output code also goes here, this is a bit more sparse than the HTML stuff 953def OutputTEXT( filters, results ) 954 955 # List the filters 956 text = "Filters\n" 957 filters.each do |filterType| 958 text += "\t* #{filterType.first}\n" 959 960 filterType.last.each do |filter| 961 text += "\t\t- #{filter.title}\n" 962 963 text += Columnize( filter.desc, 80, "\t\t\t" ) + "\n\n" 964 end 965 end 966 967 # List the directories 968 text += "\n\nDirectories\n" 969 results.each do |dir| 970 text += "\t#{dir.first}\n" 971 end 972 973 text += "\n\nResults\n" 974 975 # To avoid bad readability, I only use fullpaths here instead of sections per dir 976 results.each do |dir| 977 dir.last.each do |file| 978 text += "\t#{dir.first}#{file.first}\n" 979 980 file.last.each do |filter| 981 text += "\t\t* #{filter.first.title}\n" 982 983 filter.last.each do |result| 984 if result.line then 985 text += "\t\t\t#{PadNum(result.line.number)}: #{result.line.text.strip}\n" 986 end 987 end 988 end 989 990 text += "\n" 991 end 992 end 993 994 return text; 995end 996 997 998 999#TODO Improved parameter-handling, add =<file> for the outputing to a file 1000ARGV.each do |param| 1001 case param 1002 when "--text" then 1003 puts OutputTEXT( create_filter_tree( filterList ), create_result_tree( ".", filterList ) ) 1004 when "--html" then 1005 puts OutputHTML( create_filter_tree( filterList ), create_result_tree( ".", filterList ) ) 1006 end 1007end 1008