3
3
import os
4
4
from pathlib import Path
5
5
6
- from fortls .constants import KEYWORD_ID_DICT , KEYWORD_LIST , FRegex , log , sort_keywords
6
+ from fortls .constants import KEYWORD_ID_DICT , KEYWORD_LIST , FRegex , sort_keywords
7
7
from fortls .ftypes import Range
8
8
9
9
@@ -52,6 +52,22 @@ def detect_fixed_format(file_lines: list[str]) -> bool:
52
52
-------
53
53
bool
54
54
True if file_lines are of Fixed Fortran style
55
+
56
+ Examples
57
+ --------
58
+
59
+ >>> detect_fixed_format([' free format'])
60
+ False
61
+
62
+ >>> detect_fixed_format([' INTEGER, PARAMETER :: N = 10'])
63
+ False
64
+
65
+ >>> detect_fixed_format(['C Fixed format'])
66
+ True
67
+
68
+ Lines wih ampersands are not fixed format
69
+ >>> detect_fixed_format(['trailing line & ! comment'])
70
+ False
55
71
"""
56
72
for line in file_lines :
57
73
if FRegex .FREE_FORMAT_TEST .match (line ):
@@ -62,7 +78,7 @@ def detect_fixed_format(file_lines: list[str]) -> bool:
62
78
# Trailing ampersand indicates free or intersection format
63
79
if not FRegex .FIXED_COMMENT .match (line ):
64
80
line_end = line .split ("!" )[0 ].strip ()
65
- if len (line_end ) > 0 and line_end [ - 1 ] == "&" :
81
+ if len (line_end ) > 0 and line_end . endswith ( "&" ) :
66
82
return False
67
83
return True
68
84
@@ -136,21 +152,20 @@ def separate_def_list(test_str: str) -> list[str] | None:
136
152
137
153
Examples
138
154
--------
139
- >>> separate_def_list("var1, var2, var3")
140
- ["var1", "var2", "var3"]
141
-
155
+ >>> separate_def_list('var1, var2, var3')
156
+ ['var1', 'var2', 'var3']
142
157
143
- >>> separate_def_list(" var, init_var(3) = [1,2,3], array(3,3)" )
144
- [" var", " init_var", " array" ]
158
+ >>> separate_def_list(' var, init_var(3) = [1,2,3], array(3,3)' )
159
+ [' var', ' init_var(3) = [1,2,3]', ' array(3,3)' ]
145
160
"""
146
161
stripped_str = strip_strings (test_str )
147
162
paren_count = 0
148
- def_list = []
163
+ def_list : list [ str ] = []
149
164
curr_str = ""
150
165
for char in stripped_str :
151
- if ( char == "(" ) or ( char == "[" ):
166
+ if char in ( "(" , "[" ):
152
167
paren_count += 1
153
- elif ( char == ")" ) or ( char == "]" ):
168
+ elif char in ( ")" , "]" ):
154
169
paren_count -= 1
155
170
elif (char == "," ) and (paren_count == 0 ):
156
171
curr_str = curr_str .strip ()
@@ -208,17 +223,17 @@ def find_paren_match(string: str) -> int:
208
223
209
224
Examples
210
225
--------
211
- >>> find_paren_match(" a, b)" )
226
+ >>> find_paren_match(' a, b)' )
212
227
4
213
228
214
229
Multiple parenthesis that are closed
215
230
216
- >>> find_paren_match(" a, (b, c), d)" )
231
+ >>> find_paren_match(' a, (b, c), d)' )
217
232
12
218
233
219
234
If the outermost parenthesis is not closed function returns -1
220
235
221
- >>> find_paren_match(" a, (b, (c, d)" )
236
+ >>> find_paren_match(' a, (b, (c, d)' )
222
237
-1
223
238
"""
224
239
paren_count = 1
@@ -233,7 +248,9 @@ def find_paren_match(string: str) -> int:
233
248
return ind
234
249
235
250
236
- def get_line_prefix (pre_lines : list , curr_line : str , col : int , qs : bool = True ) -> str :
251
+ def get_line_prefix (
252
+ pre_lines : list [str ], curr_line : str , col : int , qs : bool = True
253
+ ) -> str :
237
254
"""Get code line prefix from current line and preceding continuation lines
238
255
239
256
Parameters
@@ -252,6 +269,11 @@ def get_line_prefix(pre_lines: list, curr_line: str, col: int, qs: bool = True)
252
269
-------
253
270
str
254
271
part of the line including any relevant line continuations before ``col``
272
+
273
+ Examples
274
+ --------
275
+ >>> get_line_prefix([''], '#pragma once', 0) is None
276
+ True
255
277
"""
256
278
if (curr_line is None ) or (col > len (curr_line )) or (curr_line .startswith ("#" )):
257
279
return None
@@ -293,47 +315,70 @@ def resolve_globs(glob_path: str, root_path: str = None) -> list[str]:
293
315
list[str]
294
316
Expanded glob patterns with absolute paths.
295
317
Absolute paths are used to resolve any potential ambiguity
318
+
319
+ Examples
320
+ --------
321
+
322
+ Relative to a root path
323
+ >>> import os, pathlib
324
+ >>> resolve_globs('test', os.getcwd()) == [str(pathlib.Path(os.getcwd()) / 'test')]
325
+ True
326
+
327
+ Absolute path resolution
328
+ >>> resolve_globs('test') == [str(pathlib.Path(os.getcwd()) / 'test')]
329
+ True
296
330
"""
297
331
# Resolve absolute paths i.e. not in our root_path
298
332
if os .path .isabs (glob_path ) or not root_path :
299
333
p = Path (glob_path ).resolve ()
300
- root = p .root
334
+ root = p .anchor # drive letter + root path
301
335
rel = str (p .relative_to (root )) # contains glob pattern
302
336
return [str (p .resolve ()) for p in Path (root ).glob (rel )]
303
337
else :
304
338
return [str (p .resolve ()) for p in Path (root_path ).resolve ().glob (glob_path )]
305
339
306
340
307
- def only_dirs (paths : list [str ], err_msg : list = [] ) -> list [str ]:
341
+ def only_dirs (paths : list [str ]) -> list [str ]:
308
342
"""From a list of strings returns only paths that are directories
309
343
310
344
Parameters
311
345
----------
312
346
paths : list[str]
313
347
A list containing the files and directories
314
- err_msg : list, optional
315
- A list to append error messages if any, else use log channel, by default []
316
348
317
349
Returns
318
350
-------
319
351
list[str]
320
352
A list containing only valid directories
353
+
354
+ Raises
355
+ ------
356
+ FileNotFoundError
357
+ A list containing all the non existing directories
358
+
359
+ Examples
360
+ --------
361
+
362
+ >>> only_dirs(['./test/', './test/test_source/', './test/test_source/test.f90'])
363
+ ['./test/', './test/test_source/']
364
+
365
+ >>> only_dirs(['/fake/dir/a', '/fake/dir/b', '/fake/dir/c'])
366
+ Traceback (most recent call last):
367
+ FileNotFoundError: /fake/dir/a
368
+ /fake/dir/b
369
+ /fake/dir/c
321
370
"""
322
371
dirs : list [str ] = []
372
+ errs : list [str ] = []
323
373
for p in paths :
324
374
if os .path .isdir (p ):
325
375
dirs .append (p )
326
376
elif os .path .isfile (p ):
327
377
continue
328
378
else :
329
- msg : str = (
330
- f"Directory '{ p } ' specified in Configuration settings file does not"
331
- " exist"
332
- )
333
- if err_msg :
334
- err_msg .append ([2 , msg ])
335
- else :
336
- log .warning (msg )
379
+ errs .append (p )
380
+ if errs :
381
+ raise FileNotFoundError ("\n " .join (errs ))
337
382
return dirs
338
383
339
384
@@ -388,12 +433,12 @@ def get_paren_substring(string: str) -> str | None:
388
433
389
434
Examples
390
435
--------
391
- >>> get_paren_substring(" some line(a, b, (c, d))" )
392
- " a, b, (c, d)"
436
+ >>> get_paren_substring(' some line(a, b, (c, d))' )
437
+ ' a, b, (c, d)'
393
438
394
439
If the line has incomplete parenthesis however, ``None`` is returned
395
- >>> get_paren_substring(" some line(a, b")
396
- None
440
+ >>> get_paren_substring(' some line(a, b') is None
441
+ True
397
442
"""
398
443
i1 = string .find ("(" )
399
444
i2 = string .rfind (")" )
@@ -419,15 +464,18 @@ def get_paren_level(line: str) -> tuple[str, list[Range]]:
419
464
420
465
Examples
421
466
--------
422
- >>> get_paren_level(" CALL sub1(arg1,arg2" )
467
+ >>> get_paren_level(' CALL sub1(arg1,arg2' )
423
468
('arg1,arg2', [Range(start=10, end=19)])
424
469
425
470
If the range is interrupted by parenthesis, another Range variable is used
426
471
to mark the ``start`` and ``end`` of the argument
427
472
428
- >>> get_paren_level(" CALL sub1(arg1(i),arg2" )
473
+ >>> get_paren_level(' CALL sub1(arg1(i),arg2' )
429
474
('arg1,arg2', [Range(start=10, end=14), Range(start=17, end=22)])
430
475
476
+ >>> get_paren_level('')
477
+ ('', [Range(start=0, end=0)])
478
+
431
479
"""
432
480
if line == "" :
433
481
return "" , [Range (0 , 0 )]
@@ -442,18 +490,18 @@ def get_paren_level(line: str) -> tuple[str, list[Range]]:
442
490
if char == string_char :
443
491
in_string = False
444
492
continue
445
- if ( char == "(" ) or ( char == "[" ):
493
+ if char in ( "(" , "[" ):
446
494
level -= 1
447
495
if level == 0 :
448
496
i1 = i
449
497
elif level < 0 :
450
498
sections .append (Range (i + 1 , i1 ))
451
499
break
452
- elif ( char == ")" ) or ( char == "]" ):
500
+ elif char in ( ")" , "]" ):
453
501
level += 1
454
502
if level == 1 :
455
503
sections .append (Range (i + 1 , i1 ))
456
- elif ( char == "'" ) or ( char == '"' ):
504
+ elif char in ( "'" , '"' ):
457
505
in_string = True
458
506
string_char = char
459
507
if level == 0 :
@@ -480,16 +528,19 @@ def get_var_stack(line: str) -> list[str]:
480
528
481
529
Examples
482
530
--------
483
- >>> get_var_stack("myvar%foo%bar")
484
- ["myvar", "foo", "bar"]
531
+ >>> get_var_stack('myvar%foo%bar')
532
+ ['myvar', 'foo', 'bar']
533
+
534
+ >>> get_var_stack('myarray(i)%foo%bar')
535
+ ['myarray', 'foo', 'bar']
485
536
486
- >>> get_var_stack("myarray(i)%foo%bar")
487
- ["myarray", "foo", "bar"]
537
+ In this case it will operate at the end of the string i.e. ``'this%foo'``
488
538
489
- In this case it will operate at the end of the string i.e. ``"this%foo"``
539
+ >>> get_var_stack('CALL self%method(this%foo')
540
+ ['this', 'foo']
490
541
491
- >>> get_var_stack("CALL self%method(this%foo" )
492
- ["this", "foo" ]
542
+ >>> get_var_stack('' )
543
+ ['' ]
493
544
"""
494
545
if len (line ) == 0 :
495
546
return ["" ]
0 commit comments