1# -*- coding: utf-8 -*-
2#
3#  completion.py - commander
4#
5#  Copyright (C) 2010 - Jesse van den Kieboom
6#
7#  This program is free software; you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation; either version 2 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  You should have received a copy of the GNU General Public License
18#  along with this program; if not, write to the Free Software
19#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
20#  Boston, MA 02110-1301, USA.
21
22import commander.commands as commands
23import bisect
24import sys
25import os
26import re
27
28from xml.sax import saxutils
29
30__all__ = ['command', 'filename']
31
32def _common_prefix_part(first, second):
33    length = min(len(first), len(second))
34
35    for i in range(0, length):
36        if first[i] != second[i]:
37            return first[:i]
38
39    return first[:length]
40
41def common_prefix(args, sep=None):
42    # A common prefix can be something like
43    # first: some-thing
44    # second: sho-tar
45    # res: s-t
46    args = list(args)
47
48    if not args:
49        return ''
50
51    if len(args) == 1:
52        return str(args[0])
53
54    first = str(args[0])
55    second = str(args[1])
56
57    if not sep:
58        ret = _common_prefix_part(first, second)
59    else:
60        first = first.split(sep)
61        second = second.split(sep)
62        ret = []
63
64        for i in range(0, min(len(first), len(second))):
65            ret.append(_common_prefix_part(first[i], second[i]))
66
67        ret = sep.join(ret)
68
69    del args[0]
70    args[0] = ret
71
72    return common_prefix(args, sep)
73
74def _expand_commands(cmds):
75    if not cmds:
76        cmds.extend(commands.Commands().modules())
77        return
78
79    old = list(cmds)
80    del cmds[:]
81
82    # Expand 'commands' to all the respective subcommands
83
84    for cmd in old:
85        for c in cmd.commands():
86            bisect.insort(cmds, c)
87
88def _filter_command(cmd, subs):
89    parts = cmd.name.split('-')
90
91    if len(subs) > len(parts):
92        return False
93
94    for i in range(0, len(subs)):
95        if not parts[i].startswith(subs[i]):
96            return False
97
98    return True
99
100def _filter_commands(cmds, subs):
101    # See what parts of cmds still match the parts in subs
102    idx = bisect.bisect_left(cmds, subs[0])
103    ret = []
104
105    while idx < len(cmds):
106        if not cmds[idx].name.startswith(subs[0]):
107            break
108
109        if _filter_command(cmds[idx], subs):
110            ret.append(cmds[idx])
111
112        idx += 1
113
114    return ret
115
116def single_command(words, idx):
117    ret = command(words, idx)
118
119    if not ret:
120        return None
121
122    ret[0] = list(filter(lambda x: x.method, ret[0]))
123
124    if not ret[0]:
125        return None
126
127    return ret[0][0]
128
129def command(words, idx):
130    s = words[idx].strip()
131
132    parts = s.split('.')
133    cmds = []
134
135    for i in parts:
136        # Expand all the parents to their child commands
137        _expand_commands(cmds)
138
139        if not cmds:
140            return None
141
142        subs = i.split('-')
143        cmds = _filter_commands(cmds, subs)
144
145        if not cmds:
146            return None
147
148    if not cmds:
149        return None
150
151    if len(parts) == 1:
152        completed = common_prefix(cmds)
153    else:
154        completed = '.'.join(parts[0:-1]) + '.' + common_prefix(cmds, '-')
155
156    return [cmds, completed]
157
158def _file_color(path):
159    if os.path.isdir(path):
160        format = '<span color="#799ec6">%s</span>'
161    else:
162        format = '%s'
163
164    return format % (saxutils.escape(os.path.basename(path)),)
165
166def _sort_nicely(l):
167    convert = lambda text: int(text) if text.isdigit() else text
168    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
169
170    l.sort(key=alphanum_key)
171
172def filename(words, idx, view):
173    prefix = os.path.dirname(words[idx])
174    partial = os.path.expanduser(words[idx])
175
176    doc = view.get_buffer()
177
178    if not doc.is_untitled():
179        root = os.path.dirname(doc.get_file().get_location().get_path())
180    else:
181        root = os.path.expanduser('~/')
182
183    if not os.path.isabs(partial):
184        partial = os.path.join(root, partial)
185
186    dirname = os.path.dirname(partial)
187
188    try:
189        files = os.listdir(dirname)
190    except OSError:
191        return None
192
193    base = os.path.basename(partial)
194    ret = []
195    real = []
196
197    for f in files:
198        if f.startswith(base) and (base or not f.startswith('.')):
199            real.append(os.path.join(dirname, f))
200            ret.append(os.path.join(prefix, f))
201
202    _sort_nicely(real)
203
204    if len(ret) == 1:
205        if os.path.isdir(real[0]):
206            after = '/'
207        else:
208            after = ' '
209
210        return ret, ret[0], after
211    else:
212        return list(map(lambda x: _file_color(x), real), common_prefix(ret))
213
214def words(ret):
215    def decorator(words, idx):
216        rr = list(filter(lambda x: x.startswith(words[idx]), ret))
217        return rr, common_prefix(rr)
218
219    return decorator
220
221# ex:ts=4:et
222