<reponame>yossioo/args
# -*- coding: utf-8 -*-

"""
args
~~~~
"""

import os
import sys
from sys import argv
from glob import glob
from collections import OrderedDict

# Python 3
if sys.version_info[0] == 3:
    string_type = str
else:
    string_type = basestring


def _expand_path(path):
    """Expands directories and globs in given path."""

    paths = []
    path = os.path.expanduser(path)
    path = os.path.expandvars(path)

    if os.path.isdir(path):

        for (dir, dirs, files) in os.walk(path):
            for file in files:
                paths.append(os.path.join(dir, file))
    else:
        paths.extend(glob(path))

    return paths


def _is_collection(obj):
    """Tests if an object is a collection. Strings don't count."""

    if isinstance(obj, string_type):
        return False

    return hasattr(obj, '__getitem__')


class ArgsList(object):
    """CLI Argument management."""

    def __init__(self, args=None, no_argv=False):
        if not args:
            if not no_argv:
                self._args = argv[1:]
            else:
                self._args = []
        else:
            self._args = args

    def __len__(self):
        return len(self._args)

    def __repr__(self):
        return '<args %s>' % (repr(self._args))

    def __getitem__(self, i):
        try:
            return self.all[i]
        except IndexError:
            return None

    def __contains__(self, x):
        return self.first(x) is not None

    def get(self, x):
        """Returns argument at given index, else none."""

        try:
            return self.all[x]
        except IndexError:
            return None

    def get_with(self, x):
        """Returns first argument that contains given string."""

        return self.all[self.first_with(x)]

    def remove(self, x):
        """Removes given arg (or list thereof) from Args object."""

        def _remove(x):
            found = self.first(x)
            if found is not None:
                self._args.pop(found)

        if _is_collection(x):
            for item in x:
                _remove(x)
        else:
            _remove(x)

    def pop(self, x):
        """Removes and Returns value at given index, else none."""

        try:
            return self._args.pop(x)
        except IndexError:
            return None

    def any_contain(self, x):
        """Tests if given string is contained in any stored argument."""

        return bool(self.first_with(x))

    def contains(self, x):
        """Tests if given object is in arguments list.
           Accepts strings and lists of strings."""

        return self.__contains__(x)

    def first(self, x):
        """Returns first found index of given value (or list of values)."""

        def _find(x):
            try:
                return self.all.index(str(x))
            except ValueError:
                return None

        if _is_collection(x):
            for item in x:
                found = _find(item)
                if found is not None:
                    return found
            return None
        else:
            return _find(x)

    def first_with(self, x):
        """Returns first found index containing value (or list of values)."""

        def _find(x):
            try:
                for arg in self.all:
                    if x in arg:
                        return self.all.index(arg)
            except ValueError:
                return None

        if _is_collection(x):
            for item in x:
                found = _find(item)
                if found:
                    return found
            return None
        else:
            return _find(x)

    def first_without(self, x):
        """Returns first found index not containing value
           (or list of values).
        """

        def _find(x):
            try:
                for arg in self.all:
                    if x not in arg:
                        return self.all.index(arg)
            except ValueError:
                return None

        if _is_collection(x):
            for item in x:
                found = _find(item)
                if found:
                    return found
            return None
        else:
            return _find(x)

    def start_with(self, x):
        """Returns all arguments beginning with given string
           (or list thereof).
        """

        _args = []

        for arg in self.all:
            if _is_collection(x):
                for _x in x:
                    if arg.startswith(x):
                        _args.append(arg)
                        break
            else:
                if arg.startswith(x):
                    _args.append(arg)

        return ArgsList(_args, no_argv=True)

    def contains_at(self, x, index):
        """Tests if given [list of] string is at given index."""

        try:
            if _is_collection(x):
                for _x in x:
                    if (_x in self.all[index]) or (_x == self.all[index]):
                        return True
                    else:
                        return False
            else:
                return (x in self.all[index])

        except IndexError:
            return False

    def has(self, x):
        """Returns true if argument exists at given index.
           Accepts: integer.
        """

        try:
            self.all[x]
            return True
        except IndexError:
            return False

    def value_after(self, x):
        """Returns value of argument after given found argument
           (or list thereof).
        """

        try:
            try:
                i = self.all.index(x)
            except ValueError:
                return None

            return self.all[i + 1]

        except IndexError:
            return None

    @property
    def grouped(self):
        """Extracts --flag groups from argument list.
           Returns {format: Args, ...}
        """

        collection = OrderedDict(_=ArgsList(no_argv=True))

        _current_group = None

        for arg in self.all:
            if arg.startswith('-'):
                _current_group = arg
                collection.setdefault(arg, ArgsList(no_argv=True))
            else:
                if _current_group:
                    collection[_current_group]._args.append(arg)
                else:
                    collection['_']._args.append(arg)

        return collection

    @property
    def last(self):
        """Returns last argument."""

        try:
            return self.all[-1]
        except IndexError:
            return None

    @property
    def all(self):
        """Returns all arguments."""

        return self._args

    def all_with(self, x):
        """Returns all arguments containing given string (or list thereof)."""

        _args = []

        for arg in self.all:
            if _is_collection(x):
                for _x in x:
                    if _x in arg:
                        _args.append(arg)
                        break
            else:
                if x in arg:
                    _args.append(arg)

        return ArgsList(_args, no_argv=True)

    def all_without(self, x):
        """Returns all arguments not containing given string
           (or list thereof).
        """

        _args = []

        for arg in self.all:
            if _is_collection(x):
                for _x in x:
                    if _x not in arg:
                        _args.append(arg)
                        break
            else:
                if x not in arg:
                    _args.append(arg)

        return ArgsList(_args, no_argv=True)

    @property
    def flags(self):
        """Returns Arg object including only flagged arguments."""

        return self.start_with('-')

    @property
    def not_flags(self):
        """Returns Arg object excluding flagged arguments."""

        return self.all_without('-')

    @property
    def files(self, absolute=False):
        """Returns an expanded list of all valid paths that were passed in."""

        _paths = []

        for arg in self.all:
            for path in _expand_path(arg):
                if os.path.exists(path):
                    if absolute:
                        _paths.append(os.path.abspath(path))
                    else:
                        _paths.append(path)

        return _paths

    @property
    def not_files(self):
        """Returns a list of all arguments that aren't files/globs."""

        _args = []

        for arg in self.all:
            if not len(_expand_path(arg)):
                if not os.path.exists(arg):
                    _args.append(arg)

        return ArgsList(_args, no_argv=True)

    @property
    def copy(self):
        """Returns a copy of Args object for temporary manipulation."""

        return ArgsList(self.all)

    @property
    def assignments(self):
        """Extracts assignment values from assignments."""

        collection = OrderedDict()

        for arg in self.all:
            if '=' in arg:
                collection.setdefault(
                    arg.split('=', 1)[0], ArgsList(no_argv=True))
                collection[arg.split('=', 1)[0]]._args.append(
                    arg.split('=', 1)[1])

        return collection


args = ArgsList()
get = args.get
get_with = args.get_with
remove = args.remove
pop = args.pop
any_contain = args.any_contain
contains = args.contains
first = args.first
first_with = args.first_with
first_without = args.first_without
start_with = args.start_with
contains_at = args.contains_at
has = args.has
value_after = args.value_after
grouped = args.grouped
last = args.last
all = args.all
all_with = args.all_with
all_without = args.all_without
flags = args.flags
not_flags = args.not_flags
files = args.files
not_files = args.not_files
copy = args.copy
assignments = args.assignments
