#!/usr/bin/python
# Python 2 or 3 compatible.

"""
This command drives ls(1) but only directories are listed, files are ignored.

Option processing is as in GNU ls(1).

Current BUGS or limitations (I don't have the motivation to fix these):
    - ls(1) options taking arguments must be specified in their long form
    - -a behavior is incorrect, it does what -A or --almost-all should do.
The second one could be easily fixed. The first could only be fixed if all of ls
options were explicitly made known to this program.

Example usages:
    list directories in the current directory:
        lsd
    list directories in home directory and /proc
        lsd ~ /proc
    list all directories in home directory and / with long listing and nice units
        lsd -lah ~ /
    list all directories in -dirname:
        lsd -a -- -dirname
"""

# Iain Murray, March 2007. Tweaks June 2012.

import os, re, sys

def hack_getopts_and_files():
    """
    Gets options and files from command line. Does not use getopt module,
    because that needs you to know what options you want. I'm generically
    passing them on to other programs and letting them throw errors if they
    don't like them.

    The problem is that you can not use short-style options that take arguments.
    This code will think the option is a filename.

    The POSIXLY_CORRECT environment variable is respected as by the getopt
    module and getopt(1).

    Usage:
    opts, files = hack_getopts_and_files()
    """

    if "POSIXLY_CORRECT" in os.environ:
        gnu_behaviour=False
    else:
        gnu_behaviour=True
    
    opts=[]
    files=[]
    had_non_option=False
    had_breaker=False
    for str in sys.argv[1:]:
        if gnu_behaviour and (not had_breaker) and str=='--':
            had_breaker=True
            continue
        if str[0]!='-':
            had_non_option=True
            files.append(str)
        else:
            trad_not_option=((not gnu_behaviour) and had_non_option)
            gnu_not_option=(gnu_behaviour and had_breaker)
            if trad_not_option or gnu_not_option:
                files.append(str)
            else:
                opts.append(str)
    return opts, files


opts, dirs = hack_getopts_and_files()
if len(dirs)==0:
   dirs=['.'] 
combined_opts=' '.join(opts)

# TODO should (strictly) distinguish -a and -A, but I don't care
allpat=re.compile(r'^-[a-zA-Z0-9]*[aA][a-zA-Z0-9]*$')
hidden_short_opt=len([x for x in opts if re.match(allpat,x)]) > 0
show_hidden=hidden_short_opt or ('--all' in opts) or ('--almost-all' in opts)

startdir=os.getcwd()

first=True

for dir in dirs:
    os.chdir(startdir)
    try:
        os.chdir(dir)
    except:
        print(sys.argv[0] + ": Can not change into", dir)
        continue
    if len(dirs)>1:
        if not first:
            print()
        else:
            first=False
        print(dir + ':')
    for root, subdirs, files in os.walk('.'):
        if not show_hidden:
            subdirs=[x for x in subdirs if x[0]!='.']
        os.system(r'ls -d --color=auto ' + combined_opts + ' -- ' + ' '.join(map(re.escape,subdirs)))
        break

