Search code examples
ansible

Lowest common directory


i have this list in Ansible w dummy folder names:

  - /tmp/another/six
  - /tmp/another/five
  - /hi/ho/one/abc
  - /hi/ho/two/abc
  - /something

i want to create a new list, based on the lowest common directory name.
e.g. for

 - /tmp/another/six
 - /tmp/another/five

the common name is /tmp/another.
e.g. for

  - /hi/ho/one/abc
  - /hi/ho/two/abc 

it is: /hi/ho.

example output of the first example list:

  - /tmp/another
  - /hi/ho
  - /something

i tried something along the lines of: the_list | map('dirname') | unique | min.
but ofc, didn't provide me w the expected results.


Solution

  • With the help of 'a friend', I've written this Ansible plugin:

    from ansible.errors import AnsibleFilterError
    import os
    
    def lowest_common_directories(paths):
        if not isinstance(paths, list):
            raise AnsibleFilterError('Input should be a list of paths')
        paths = [os.path.normpath(path) for path in paths]
        common_dirs = {}
    
        for i, path in enumerate(paths):
            has_common = False
            for j in range(len(paths)):
                if i != j:
                    common_path = os.path.commonpath([path, paths[j]])
                    if common_path != '/':
                        has_common = True
                        if common_path in common_dirs:
                            common_dirs[common_path].add(path)
                            common_dirs[common_path].add(paths[j])
                        else:
                            common_dirs[common_path] = {path, paths[j]}
            
            if not has_common:
                common_dirs[path] = {path}
    
        final_common_dirs = set()
        for common in sorted(common_dirs, key=lambda x: len(x)):
            if all(not c.startswith(common + '/') for c in final_common_dirs):
                final_common_dirs.add(common)
    
        return list(final_common_dirs)
    
    class FilterModule(object):
        """ Ansible custom filter plugin """
        def filters(self):
            return {
                'lowest_common_dirs': lowest_common_directories
            }
    

    Call with:

    - debug:
        msg: "{{ the_paths | lowest_common_dirs }}"
    

    Provides me w the desired result.