22import argparse
33import os
44import os .path
5+ import ansible
6+ from packaging import version
57import ansible .modules
68from ansible .utils .plugin_docs import get_docstring
79from ansible .plugins .loader import fragment_loader
1416 "priority -50" ,
1517]
1618MAX_DESCRIPTION_LENGTH = 512
19+ ANSIBLE_VERSION = ansible .release .__version__
1720
1821
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
2524
2625 Returns
2726 -------
@@ -32,47 +31,63 @@ def get_files(include_user: bool = False) -> List[str]:
3231
3332 file_names : List [str ] = []
3433 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 )
3538 file_names += [
3639 f"{ root } /{ file_name } "
37- for file_name in files
40+ for file_name in files_without_symlinks
3841 if file_name .endswith (".py" ) and not file_name .startswith ("__init__" )
3942 ]
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- ]
4743
4844 return sorted (file_names )
4945
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/
5049
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
5373
5474 Parameters
5575 ----------
56- file_names: List [str]
57- A list of strings representing file names
76+ file_names: file_path [str]
77+ string representing module file
5878
5979 Returns
6080 -------
61- List[ Any]
62- A list of AnsibleMapping objects , representing docstring information
81+ Any
82+ An AnsibleMapping object , representing docstring information
6383 (in dict form), excluding those that are marked as deprecated.
6484
6585 """
6686
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
7691
7792
7893def escape_strings (escapist : str ) -> str :
@@ -228,7 +243,7 @@ def module_options_to_snippet_options(module_options: Any) -> List[str]:
228243 return options
229244
230245
231- def convert_docstring_to_snippet (convert_docstring : Any ) -> List [str ]:
246+ def convert_docstring_to_snippet (convert_docstring : Any , collection_name ) -> List [str ]:
232247 """Converts data about an Ansible module into an UltiSnips snippet string
233248
234249 Parameters
@@ -250,17 +265,36 @@ def convert_docstring_to_snippet(convert_docstring: Any) -> List[str]:
250265 module_name = convert_docstring ["module" ]
251266 module_short_description = convert_docstring ["short_description" ]
252267
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+
253275 snippet += [f'snippet { module_name } "{ escape_strings (module_short_description )} " { snippet_options } ' ]
254276 if args .style == "dictionary" :
255- snippet += [f"{ module_name } : " ]
277+ snippet += [f"{ snippet_module_name } " ]
256278 else :
257- snippet += [f"{ module_name } :{ ' >' if convert_docstring .get ('options' ) else '' } " ]
279+ snippet += [f"{ snippet_module_name } :{ ' >' if convert_docstring .get ('options' ) else '' } " ]
258280 module_options = module_options_to_snippet_options (convert_docstring .get ("options" ))
259281 snippet += module_options
260282 snippet += ["endsnippet" ]
261283
262284 return snippet
263285
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+
264298
265299if __name__ == "__main__" :
266300
@@ -284,10 +318,36 @@ def convert_docstring_to_snippet(convert_docstring: Any) -> List[str]:
284318 )
285319 args = parser .parse_args ()
286320
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+
288348 with open (args .output , "w" ) as f :
289349 f .writelines (f"{ header } \n " for header in HEADER )
290- for docstring in docstrings :
350+ for docstring in modules_docstrings :
291351 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" ) )
293353 )
0 commit comments