@@ -20,16 +20,18 @@ class PHPCtags
2020 );
2121
2222 private $ mParser ;
23-
24- private $ mStructs ;
25-
23+ private $ mLines ;
2624 private $ mOptions ;
25+ private $ tagdata ;
26+ private $ cachefile ;
27+ private $ filecount ;
2728
2829 public function __construct ($ options )
2930 {
3031 $ this ->mParser = new PHPParser_Parser (new PHPParser_Lexer );
31- $ this ->mStructs = array ();
32+ $ this ->mLines = array ();
3233 $ this ->mOptions = $ options ;
34+ $ this ->filecount = 0 ;
3335 }
3436
3537 public function setMFile ($ file )
@@ -59,6 +61,10 @@ public function addFile($file)
5961 $ this ->mFiles [realpath ($ file )] = 1 ;
6062 }
6163
64+ public function setCacheFile ($ file ) {
65+ $ this ->cachefile = $ file ;
66+ }
67+
6268 public function addFiles ($ files )
6369 {
6470 foreach ($ files as $ file ) {
@@ -250,12 +256,16 @@ private function struct($node, $reset=FALSE, $parent=array())
250256 return $ structs ;
251257 }
252258
253- private function render ()
259+ private function render ($ structure )
254260 {
255261 $ str = '' ;
256- foreach ($ this -> mStructs as $ struct ) {
262+ foreach ($ structure as $ struct ) {
257263 $ file = $ struct ['file ' ];
258264
265+ if (!in_array ($ struct ['kind ' ], $ this ->mOptions ['kinds ' ])) {
266+ continue ;
267+ }
268+
259269 if (!isset ($ files [$ file ]))
260270 $ files [$ file ] = file ($ file );
261271
@@ -357,19 +367,42 @@ private function render()
357367 $ str .= "\n" ;
358368 }
359369
360- // remove the last line ending
361- $ str = trim ($ str );
370+ // remove the last line ending and carriage return
371+ $ str = trim (str_replace ("\x0D" , "" , $ str ));
372+
373+ return $ str ;
374+ }
375+
376+ private function full_render () {
377+ // Files will have been rendered already, just join and export.
378+
379+ $ str = '' ;
380+ foreach ($ this ->mLines as $ file => $ data ) {
381+ $ str .= $ data .PHP_EOL ;
382+ }
362383
363384 // sort the result as instructed
364385 if (isset ($ this ->mOptions ['sort ' ]) && ($ this ->mOptions ['sort ' ] == 'yes ' || $ this ->mOptions ['sort ' ] == 'foldcase ' )) {
365386 $ str = self ::stringSortByLine ($ str , $ this ->mOptions ['sort ' ] == 'foldcase ' );
366387 }
367388
389+ // Save all tag information to a file for faster updates if a cache file was specified.
390+ if (isset ($ this ->cachefile )) {
391+ file_put_contents ($ this ->cachefile , serialize ($ this ->tagdata ));
392+ if ($ this ->mOptions ['v ' ]) {
393+ echo "Saved cache file. " .PHP_EOL ;
394+ }
395+ }
396+
397+ $ str = trim ($ str );
398+
368399 return $ str ;
369400 }
370401
371402 public function export ()
372403 {
404+ $ start = microtime (true );
405+
373406 if (empty ($ this ->mFiles )) {
374407 throw new PHPCtagsException ('No File specified. ' );
375408 }
@@ -378,14 +411,30 @@ public function export()
378411 $ this ->process ($ file );
379412 }
380413
381- return $ this ->render ();
414+ $ content = $ this ->full_render ();
415+
416+ $ end = microtime (true );
417+
418+ if ($ this ->mOptions ['V ' ]) {
419+ echo "It tooks " .($ end -$ start )." seconds. " .PHP_EOL ;
420+ }
421+
422+ return $ content ;
382423 }
383424
384425 private function process ($ file )
385426 {
427+ // Load the tag md5 data to skip unchanged files.
428+ if (!isset ($ this ->tagdata ) && isset ($ this ->cachefile ) && file_exists (realpath ($ this ->cachefile ))) {
429+ if ($ this ->mOptions ['v ' ]) {
430+ echo "Loaded cache file. " .PHP_EOL ;
431+ }
432+ $ this ->tagdata = unserialize (file_get_contents (realpath ($ this ->cachefile )));
433+ }
434+
386435 if (is_dir ($ file ) && isset ($ this ->mOptions ['R ' ])) {
387436 $ iterator = new RecursiveIteratorIterator (
388- new RecursiveDirectoryIterator (
437+ new ReadableRecursiveDirectoryIterator (
389438 $ file ,
390439 FilesystemIterator::SKIP_DOTS |
391440 FilesystemIterator::FOLLOW_SYMLINKS
@@ -404,31 +453,64 @@ private function process($file)
404453 }
405454
406455 try {
407- $ this ->setMFile ((string ) $ filename );
408- $ this ->mStructs = array_merge (
409- $ this ->mStructs ,
410- $ this ->struct ($ this ->mParser ->parse (file_get_contents ($ this ->mFile )), TRUE )
411- );
456+ $ this ->process_single_file ($ filename );
412457 } catch (Exception $ e ) {
413458 echo "PHPParser: {$ e ->getMessage ()} - {$ filename }" .PHP_EOL ;
414459 }
415460 }
416461 } else {
417462 try {
418- $ this ->setMFile ($ file );
419- $ this ->mStructs = array_merge (
420- $ this ->mStructs ,
421- $ this ->struct ($ this ->mParser ->parse (file_get_contents ($ this ->mFile )), TRUE )
422- );
463+ $ this ->process_single_file ($ file );
423464 } catch (Exception $ e ) {
424- echo "PHPParser: {$ e ->getMessage ()} - {$ filename }" .PHP_EOL ;
465+ echo "PHPParser: {$ e ->getMessage ()} - {$ file }" .PHP_EOL ;
466+ }
467+ }
468+ }
469+
470+ private function process_single_file ($ filename )
471+ {
472+ if ($ this ->mOptions ['v ' ] && $ this ->filecount > 1 && $ this ->filecount % 64 == 0 ) {
473+ echo " " .$ this ->filecount ." files " .PHP_EOL ;
474+ }
475+ $ this ->filecount ++;
476+
477+ $ this ->setMFile ((string ) $ filename );
478+ $ file = file_get_contents ($ this ->mFile );
479+ $ md5 = md5 ($ file );
480+ if (isset ($ this ->tagdata [$ this ->mFile ][$ md5 ])) {
481+ // The file is the same as the previous time we analyzed and saved.
482+ $ this ->mLines [$ this ->mFile ] = $ this ->tagdata [$ this ->mFile ][$ md5 ];
483+ if ($ this ->mOptions ['V ' ]) {
484+ echo ". " ;
425485 }
486+ return ;
487+ }
488+
489+ $ struct = $ this ->struct ($ this ->mParser ->parse ($ file ), TRUE );
490+ $ this ->mLines [$ this ->mFile ] = $ this ->render ($ struct );
491+ $ this ->tagdata [$ this ->mFile ][$ md5 ] = $ this ->mLines [$ this ->mFile ];
492+ if ($ this ->mOptions ['debug ' ]) {
493+ echo "Parse: " .($ finishfile - $ startfile ).", Merge: " .($ finishmerge -$ finishfile )."; ( " .$ this ->filecount .") " .$ this ->mFile .PHP_EOL ;
494+ } else if ($ this ->mOptions ['v ' ]) {
495+ echo "U " ;
426496 }
427497 }
498+
428499}
429500
430501class PHPCtagsException extends Exception {
431502 public function __toString () {
432- return "PHPCtags: {$ this ->message }\n" ;
503+ return "\nPHPCtags: {$ this ->message }\n" ;
504+ }
505+ }
506+
507+ class ReadableRecursiveDirectoryIterator extends RecursiveDirectoryIterator {
508+ function getChildren () {
509+ try {
510+ return new ReadableRecursiveDirectoryIterator ($ this ->getPathname ());
511+ } catch (UnexpectedValueException $ e ) {
512+ file_put_contents ('php://stderr ' , "\nPHPPCtags: {$ e ->getMessage ()} - {$ this ->getPathname ()}\n" );
513+ return new RecursiveArrayIterator (array ());
514+ }
433515 }
434516}
0 commit comments