@@ -3,36 +3,100 @@ class PHPCtags
33{
44 private $ mFile ;
55
6- private $ mParser ;
6+ private $ mFiles ;
77
88 private static $ mKinds = array (
9- 'c ' => 'class ' ,
10- 'm ' => 'method ' ,
11- 'f ' => 'function ' ,
12- 'p ' => 'property ' ,
13- 'd ' => 'constant ' ,
14- 'v ' => 'variable ' ,
15- 'i ' => 'interface ' ,
16- );
17-
18- public function __construct ()
9+ 'c ' => 'class ' ,
10+ 'm ' => 'method ' ,
11+ 'f ' => 'function ' ,
12+ 'p ' => 'property ' ,
13+ 'd ' => 'constant ' ,
14+ 'v ' => 'variable ' ,
15+ 'i ' => 'interface ' ,
16+ );
17+
18+ private $ mParser ;
19+
20+ private $ mStructs ;
21+
22+ private $ mOptions ;
23+
24+ public function __construct ($ options )
1925 {
2026 $ this ->mParser = new PHPParser_Parser (new PHPParser_Lexer );
27+ $ this ->mStructs = array ();
28+ $ this ->mOptions = $ options ;
29+ }
30+
31+ public function setMFile ($ file )
32+ {
33+ if (empty ($ file )) {
34+ throw new PHPCtagsException ('No File specified. ' );
35+ }
36+
37+ if (!file_exists ($ file )) {
38+ throw new PHPCtagsException ('Warning: cannot open source file " ' . $ file . '" : No such file ' );
39+ }
40+
41+ if (!is_readable ($ file )) {
42+ throw new PHPCtagsException ('Warning: cannot open source file " ' . $ file . '" : File is not readable ' );
43+ }
44+
45+ $ this ->mFile = realpath ($ file );
2146 }
2247
2348 public static function getMKinds ()
2449 {
2550 return self ::$ mKinds ;
2651 }
2752
53+ public function addFile ($ file )
54+ {
55+ $ this ->mFiles [realpath ($ file )] = 1 ;
56+ }
57+
58+ public function addFiles ($ files )
59+ {
60+ foreach ($ files as $ file ) {
61+ $ this ->addFile ($ file );
62+ }
63+ }
64+
2865 private function getNodeAccess ($ node )
2966 {
3067 if ($ node ->isPrivate ()) return 'private ' ;
3168 if ($ node ->isProtected ()) return 'protected ' ;
3269 return 'public ' ;
3370 }
3471
35- private static function helperSortByLine ($ a , $ b ) {
72+ /**
73+ * stringSortByLine
74+ *
75+ * Sort a string based on its line delimiter
76+ *
77+ * @author Techlive Zheng
78+ *
79+ * @access public
80+ * @static
81+ *
82+ * @param string $str string to be sorted
83+ * @param boolean $foldcse case-insensitive sorting
84+ *
85+ * @return string sorted string
86+ **/
87+ public static function stringSortByLine ($ str , $ foldcase =FALSE )
88+ {
89+ $ arr = explode ("\n" , $ str );
90+ if (!$ foldcase )
91+ sort ($ arr , SORT_STRING );
92+ else
93+ sort ($ arr , SORT_STRING | SORT_FLAG_CASE );
94+ $ str = implode ("\n" , $ arr );
95+ return $ str ;
96+ }
97+
98+ private static function helperSortByLine ($ a , $ b )
99+ {
36100 return $ a ['line ' ] > $ b ['line ' ] ? 1 : 0 ;
37101 }
38102
@@ -47,7 +111,7 @@ private function struct($node, $reset=FALSE, $parent=array())
47111
48112 $ kind = $ name = $ line = $ access = '' ;
49113
50- if (!empty ($ parent )) array_push ($ scope , $ parent );
114+ if (!empty ($ parent )) array_push ($ scope , $ parent );
51115
52116 if (is_array ($ node )) {
53117 foreach ($ node as $ subNode ) {
@@ -79,6 +143,10 @@ private function struct($node, $reset=FALSE, $parent=array())
79143 foreach ($ node as $ subNode ) {
80144 $ this ->struct ($ subNode , FALSE , array ('method ' => $ name ));
81145 }
146+ } elseif ($ node instanceof PHPParser_Node_Stmt_If) {
147+ foreach ($ node as $ subNode ) {
148+ $ this ->struct ($ subNode );
149+ }
82150 } elseif ($ node instanceof PHPParser_Node_Stmt_Const) {
83151 $ kind = 'd ' ;
84152 $ cons = $ node ->consts [0 ];
@@ -93,15 +161,17 @@ private function struct($node, $reset=FALSE, $parent=array())
93161 //@todo
94162 } elseif ($ node instanceof PHPParser_Node_Stmt_Declare) {
95163 //@todo
164+ } elseif ($ node instanceof PHPParser_Node_Stmt_TryCatch) {
165+ foreach ($ node as $ subNode ) {
166+ $ this ->struct ($ subNode );
167+ }
96168 } elseif ($ node instanceof PHPParser_Node_Stmt_Function) {
97169 $ kind = 'f ' ;
98170 $ name = $ node ->name ;
99171 $ line = $ node ->getLine ();
100172 foreach ($ node as $ subNode ) {
101173 $ this ->struct ($ subNode , FALSE , array ('function ' => $ name ));
102174 }
103- } elseif ($ node instanceof PHPParser_Node_Stmt_Trait) {
104- //@todo
105175 } elseif ($ node instanceof PHPParser_Node_Stmt_Interface) {
106176 $ kind = 'i ' ;
107177 $ name = $ node ->name ;
@@ -115,7 +185,7 @@ private function struct($node, $reset=FALSE, $parent=array())
115185 $ this ->struct ($ subNode );
116186 }
117187 } elseif ($ node instanceof PHPParser_Node_Expr_Assign) {
118- if (is_string ($ node ->var ->name )) {
188+ if (is_string ($ node ->var ->name )) {
119189 $ kind = 'v ' ;
120190 $ node = $ node ->var ;
121191 $ name = $ node ->name ;
@@ -136,6 +206,7 @@ private function struct($node, $reset=FALSE, $parent=array())
136206
137207 if (!empty ($ kind ) && !empty ($ name ) && !empty ($ line )) {
138208 $ structs [] = array (
209+ 'file ' => $ this ->mFile ,
139210 'kind ' => $ kind ,
140211 'name ' => $ name ,
141212 'line ' => $ line ,
@@ -144,66 +215,71 @@ private function struct($node, $reset=FALSE, $parent=array())
144215 );
145216 }
146217
147- if (!empty ($ parent )) array_pop ($ scope );
218+ if (!empty ($ parent )) array_pop ($ scope );
148219
149- usort ($ structs , 'self::helperSortByLine ' );
220+ // if no --sort is given, sort by occurrence
221+ if (!isset ($ this ->mOptions ['sort ' ]) || $ this ->mOptions ['sort ' ] == 'no ' ) {
222+ usort ($ structs , 'self::helperSortByLine ' );
223+ }
150224
151225 return $ structs ;
152226 }
153227
154- private function render ($ structs , $ options )
228+ private function render ()
155229 {
156230 $ str = '' ;
157- $ lines = file ($ this ->mFile );
158- foreach ($ structs as $ struct ) {
231+ foreach ($ this ->mStructs as $ struct ) {
232+ $ file = $ struct ['file ' ];
233+
234+ if (!isset ($ files [$ file ]))
235+ $ files [$ file ] = file ($ file );
236+
237+ $ lines = $ files [$ file ];
238+
159239 if (empty ($ struct ['name ' ]) || empty ($ struct ['line ' ]) || empty ($ struct ['kind ' ]))
160240 return ;
161241
162- if ($ struct ['kind ' ] == 'v ' ) {
163- $ str .= "$ " . $ struct ['name ' ];
164- } else {
165- $ str .= $ struct ['name ' ];
166- }
242+ $ str .= $ struct ['name ' ];
167243
168- $ str .= "\t" . $ this -> mFile ;
244+ $ str .= "\t" . $ file ;
169245
170- if ($ options ['excmd ' ] == 'number ' ) {
246+ if ($ this -> mOptions ['excmd ' ] == 'number ' ) {
171247 $ str .= "\t" . $ struct ['line ' ];
172248 } else { //excmd == 'mixed' or 'pattern', default behavior
173249 $ str .= "\t" . "/^ " . rtrim ($ lines [$ struct ['line ' ] - 1 ], "\n" ) . "$/ " ;
174250 }
175251
176- if ($ options ['format ' ] == 1 ) {
252+ if ($ this -> mOptions ['format ' ] == 1 ) {
177253 $ str .= "\n" ;
178254 continue ;
179255 }
180256
181257 $ str .= "; \"" ;
182258
183259 #field=k, kind of tag as single letter
184- if (in_array ('k ' , $ options ['fields ' ])) {
185- in_array ('z ' , $ options ['fields ' ]) && $ str .= "kind: " ;
260+ if (in_array ('k ' , $ this -> mOptions ['fields ' ])) {
261+ in_array ('z ' , $ this -> mOptions ['fields ' ]) && $ str .= "kind: " ;
186262 $ str .= "\t" . $ struct ['kind ' ];
187263 } else
188264 #field=K, kind of tag as fullname
189- if (in_array ('K ' , $ options ['fields ' ])) {
190- in_array ('z ' , $ options ['fields ' ]) && $ str .= "kind: " ;
265+ if (in_array ('K ' , $ this -> mOptions ['fields ' ])) {
266+ in_array ('z ' , $ this -> mOptions ['fields ' ]) && $ str .= "kind: " ;
191267 $ str .= "\t" . self ::$ mKinds [$ struct ['kind ' ]];
192268 }
193269
194270 #field=n
195- if (in_array ('n ' , $ options ['fields ' ])) {
271+ if (in_array ('n ' , $ this -> mOptions ['fields ' ])) {
196272 $ str .= "\t" . "line: " . $ struct ['line ' ];
197273 }
198274
199275 #field=s
200- if (in_array ('s ' , $ options ['fields ' ]) && !empty ($ struct ['scope ' ])) {
276+ if (in_array ('s ' , $ this -> mOptions ['fields ' ]) && !empty ($ struct ['scope ' ])) {
201277 $ scope = array_pop ($ struct ['scope ' ]);
202- list ($ type ,$ name ) = each ($ scope );
278+ list ($ type , $ name ) = each ($ scope );
203279 switch ($ type ) {
204280 case 'method ' :
205281 $ scope = array_pop ($ struct ['scope ' ]);
206- list ($ p_type ,$ p_name ) = each ($ scope );
282+ list ($ p_type , $ p_name ) = each ($ scope );
207283 $ scope = 'method: ' . $ p_name . ':: ' . $ name ;
208284 break ;
209285 default :
@@ -214,20 +290,77 @@ private function render($structs, $options)
214290 }
215291
216292 #field=a
217- if (in_array ('a ' , $ options ['fields ' ]) && !empty ($ struct ['access ' ])) {
293+ if (in_array ('a ' , $ this -> mOptions ['fields ' ]) && !empty ($ struct ['access ' ])) {
218294 $ str .= "\t" . "access: " . $ struct ['access ' ];
219295 }
220296
221297 $ str .= "\n" ;
222298 }
299+
300+ // remove the last line ending
301+ $ str = trim ($ str );
302+
303+ // sort the result as instructed
304+ if (isset ($ this ->mOptions ['sort ' ]) && ($ this ->mOptions ['sort ' ] == 'yes ' || $ this ->mOptions ['sort ' ] == 'foldcase ' )) {
305+ $ str = self ::stringSortByLine ($ str , $ this ->mOptions ['sort ' ] == 'foldcase ' );
306+ }
307+
223308 return $ str ;
224309 }
225310
226- public function export ($ file , $ options )
311+ public function export ()
312+ {
313+ if (empty ($ this ->mFiles )) {
314+ throw new PHPCtagsException ('No File specified. ' );
315+ }
316+
317+ foreach (array_keys ($ this ->mFiles ) as $ file ) {
318+ $ this ->process ($ file );
319+ }
320+
321+ return $ this ->render ();
322+ }
323+
324+ private function process ($ file )
227325 {
228- //@todo Check for existence
229- $ this ->mFile = $ file ;
230- $ structs = $ this ->struct ($ this ->mParser ->parse (file_get_contents ($ this ->mFile )), TRUE );
231- echo $ this ->render ($ structs , $ options );
326+ if (is_dir ($ file ) && isset ($ this ->mOptions ['R ' ])) {
327+ $ iterator = new RecursiveIteratorIterator (
328+ new RecursiveDirectoryIterator (
329+ $ file ,
330+ FilesystemIterator::SKIP_DOTS |
331+ FilesystemIterator::FOLLOW_SYMLINKS
332+ )
333+ );
334+
335+ $ extensions = array ('.php ' , '.php3 ' , '.php4 ' , '.php5 ' , '.phps ' );
336+
337+ foreach ($ iterator as $ filename ) {
338+ if (!in_array (substr ($ filename , strrpos ($ filename , '. ' )), $ extensions )) {
339+ continue ;
340+ }
341+
342+ if (isset ($ this ->mOptions ['exclude ' ]) && false !== strpos ($ filename , $ this ->mOptions ['exclude ' ])) {
343+ continue ;
344+ }
345+
346+ $ this ->setMFile ((string ) $ filename );
347+ $ this ->mStructs = array_merge (
348+ $ this ->mStructs ,
349+ $ this ->struct ($ this ->mParser ->parse (file_get_contents ($ this ->mFile )), TRUE )
350+ );
351+ }
352+ } else {
353+ $ this ->setMFile ($ file );
354+ $ this ->mStructs = array_merge (
355+ $ this ->mStructs ,
356+ $ this ->struct ($ this ->mParser ->parse (file_get_contents ($ this ->mFile )), TRUE )
357+ );
358+ }
359+ }
360+ }
361+
362+ class PHPCtagsException extends Exception {
363+ public function __toString () {
364+ return "PHPCtags: {$ this ->message }\n" ;
232365 }
233366}
0 commit comments