2
2
import argparse
3
3
import os
4
4
import os .path
5
+ import ansible
6
+ from packaging import version
5
7
import ansible .modules
6
8
from ansible .utils .plugin_docs import get_docstring
7
9
from ansible .plugins .loader import fragment_loader
14
16
"priority -50" ,
15
17
]
16
18
MAX_DESCRIPTION_LENGTH = 512
19
+ ANSIBLE_VERSION = ansible .release .__version__
17
20
18
21
19
- def get_files (include_user : bool = False ) -> List [str ]:
20
- """Return the sorted list of all module files that ansible provides
21
- Parameters
22
- ----------
23
- include_user: bool
24
- Include modules from the user's ansible-galaxy
22
+ def get_files_builtin () -> List [str ]:
23
+ """Return the sorted list of all module files that ansible provides with the ansible package
25
24
26
25
Returns
27
26
-------
@@ -32,47 +31,63 @@ def get_files(include_user: bool = False) -> List[str]:
32
31
33
32
file_names : List [str ] = []
34
33
for root , dirs , files in os .walk (os .path .dirname (ansible .modules .__file__ )):
34
+ files_without_symlinks = []
35
+ for f in files :
36
+ if not os .path .islink (os .path .join (root , f )):
37
+ files_without_symlinks .append (f )
35
38
file_names += [
36
39
f"{ root } /{ file_name } "
37
- for file_name in files
40
+ for file_name in files_without_symlinks
38
41
if file_name .endswith (".py" ) and not file_name .startswith ("__init__" )
39
42
]
40
- if include_user :
41
- for root , dirs , files in os .walk (os .path .expanduser ('~/.ansible/collections/ansible_collections/' )):
42
- file_names += [
43
- f"{ root } /{ file_name } "
44
- for file_name in files
45
- if file_name .endswith (".py" ) and not file_name .startswith ("__init__" )
46
- ]
47
43
48
44
return sorted (file_names )
49
45
46
+ def get_files_user () -> List [str ]:
47
+ """Return the sorted list of all module files provided by collections installed in the
48
+ user home folder ~/.ansible/collections/
50
49
51
- def get_docstrings (file_names : List [str ]) -> List [Any ]:
52
- """Extract and return a list of docstring information from a list of files
50
+ Returns
51
+ -------
52
+ List[str]
53
+ A list of strings representing the Python module files installed in ~/.ansible/collections/
54
+ """
55
+
56
+ file_names : List [str ] = []
57
+ for root , dirs , files in os .walk (os .path .expanduser ('~/.ansible/collections/ansible_collections/' )):
58
+ files_without_symlinks = []
59
+ for f in files :
60
+ if not os .path .islink (os .path .join (root , f )):
61
+ files_without_symlinks .append (f )
62
+ file_names += [
63
+ f"{ root } /{ file_name } "
64
+ for file_name in files_without_symlinks
65
+ if file_name .endswith (".py" ) and not file_name .startswith ("__init__" ) and "plugins/modules" in root
66
+ ]
67
+
68
+ return sorted (file_names )
69
+
70
+
71
+ def get_module_docstring (file_path : str ) -> Any :
72
+ """Extract and return docstring information from a module file
53
73
54
74
Parameters
55
75
----------
56
- file_names: List [str]
57
- A list of strings representing file names
76
+ file_names: file_path [str]
77
+ string representing module file
58
78
59
79
Returns
60
80
-------
61
- List[ Any]
62
- A list of AnsibleMapping objects , representing docstring information
81
+ Any
82
+ An AnsibleMapping object , representing docstring information
63
83
(in dict form), excluding those that are marked as deprecated.
64
84
65
85
"""
66
86
67
- found_docstrings : List [Any ] = []
68
- found_docstrings += [
69
- get_docstring (file_name , fragment_loader )[0 ] for file_name in file_names
70
- ]
71
- return [
72
- current_docstring
73
- for current_docstring in found_docstrings
74
- if current_docstring and not current_docstring .get ("deprecated" )
75
- ]
87
+ docstring = get_docstring (file_path , fragment_loader )[0 ]
88
+
89
+ if docstring and not docstring .get ("deprecated" ):
90
+ return docstring
76
91
77
92
78
93
def escape_strings (escapist : str ) -> str :
@@ -228,7 +243,7 @@ def module_options_to_snippet_options(module_options: Any) -> List[str]:
228
243
return options
229
244
230
245
231
- def convert_docstring_to_snippet (convert_docstring : Any ) -> List [str ]:
246
+ def convert_docstring_to_snippet (convert_docstring : Any , collection_name ) -> List [str ]:
232
247
"""Converts data about an Ansible module into an UltiSnips snippet string
233
248
234
249
Parameters
@@ -250,17 +265,36 @@ def convert_docstring_to_snippet(convert_docstring: Any) -> List[str]:
250
265
module_name = convert_docstring ["module" ]
251
266
module_short_description = convert_docstring ["short_description" ]
252
267
268
+ # use only the module name if ansible version < 2.10
269
+ if version .parse (ANSIBLE_VERSION ) < version .parse ("2.10" ):
270
+ snippet_module_name = f"{ module_name } :"
271
+ # use FQCN if ansible version is 2.10 or higher
272
+ else :
273
+ snippet_module_name = f"{ collection_name } .{ module_name } :"
274
+
253
275
snippet += [f'snippet { module_name } "{ escape_strings (module_short_description )} " { snippet_options } ' ]
254
276
if args .style == "dictionary" :
255
- snippet += [f"{ module_name } : " ]
277
+ snippet += [f"{ snippet_module_name } " ]
256
278
else :
257
- snippet += [f"{ module_name } :{ ' >' if convert_docstring .get ('options' ) else '' } " ]
279
+ snippet += [f"{ snippet_module_name } :{ ' >' if convert_docstring .get ('options' ) else '' } " ]
258
280
module_options = module_options_to_snippet_options (convert_docstring .get ("options" ))
259
281
snippet += module_options
260
282
snippet += ["endsnippet" ]
261
283
262
284
return snippet
263
285
286
+ def get_collection_name (filepath :str ) -> str :
287
+ """ Returns the collection name for a full file path """
288
+
289
+ path_splitted = filepath .split ('/' )
290
+
291
+ collection_top_folder_index = path_splitted .index ('ansible_collections' )
292
+ collection_namespace = path_splitted [collection_top_folder_index + 1 ]
293
+ collection_name = path_splitted [collection_top_folder_index + 2 ]
294
+
295
+ # print(f"{collection_namespace}.{collection_name}")
296
+ return f"{ collection_namespace } .{ collection_name } "
297
+
264
298
265
299
if __name__ == "__main__" :
266
300
@@ -284,10 +318,36 @@ def convert_docstring_to_snippet(convert_docstring: Any) -> List[str]:
284
318
)
285
319
args = parser .parse_args ()
286
320
287
- docstrings = get_docstrings (get_files (include_user = args .user ))
321
+
322
+ if version .parse (ANSIBLE_VERSION ) < version .parse ("2.10" ):
323
+ print (f"ansible version { ANSIBLE_VERSION } doesn't support FQCN" )
324
+ print ("generated snippets will only use the module name e.g. 'yum' instead of 'ansible.builtin.yum'" )
325
+ else :
326
+ print (f"ansible version { ANSIBLE_VERSION } supports using FQCN" )
327
+ print ("Generated snippets will use FQCN e.g. 'ansible.builtin.yum' instead of 'yum'" )
328
+ print ("Still, you only need to type 'yum' to trigger the snippet" )
329
+
330
+ modules_docstrings = []
331
+
332
+ builtin_modules_paths = get_files_builtin ()
333
+ for f in builtin_modules_paths :
334
+ docstring_builtin = get_module_docstring (f )
335
+ if docstring_builtin and docstring_builtin not in modules_docstrings :
336
+ docstring_builtin ['collection_name' ] = "ansible.builtin"
337
+ modules_docstrings .append (docstring_builtin )
338
+
339
+ if args .user :
340
+ user_modules_paths = get_files_user ()
341
+ for f in user_modules_paths :
342
+ docstring_user = get_module_docstring (f )
343
+ if docstring_user and docstring_user not in modules_docstrings :
344
+ collection_name = get_collection_name (f )
345
+ docstring_user ['collection_name' ] = collection_name
346
+ modules_docstrings .append (docstring_user )
347
+
288
348
with open (args .output , "w" ) as f :
289
349
f .writelines (f"{ header } \n " for header in HEADER )
290
- for docstring in docstrings :
350
+ for docstring in modules_docstrings :
291
351
f .writelines (
292
- f"{ line } \n " for line in convert_docstring_to_snippet (docstring )
352
+ f"{ line } \n " for line in convert_docstring_to_snippet (docstring , docstring . get ( "collection_name" ) )
293
353
)
0 commit comments