1# Copyright 2013-2014 Sebastian Kreft
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Common function used across modules."""
15
16import io
17import os
18import re
19
20
21def filter_lines(lines, filter_regex, groups=None):
22    """Filters out the lines not matching the pattern.
23
24    Args:
25      lines: list[string]: lines to filter.
26      pattern: string: regular expression to filter out lines.
27
28    Returns: list[string]: the list of filtered lines.
29    """
30    pattern = re.compile(filter_regex)
31    for line in lines:
32        match = pattern.search(line)
33        if match:
34            if groups is None:
35                yield line
36            elif len(groups) == 1:
37                yield match.group(groups[0])
38            else:
39                matched_groups = match.groupdict()
40                yield tuple(matched_groups.get(group) for group in groups)
41
42
43# TODO(skreft): add test
44def which(program):
45    """Returns a list of paths where the program is found."""
46    if (os.path.isabs(program) and os.path.isfile(program) and
47            os.access(program, os.X_OK)):
48        return [program]
49
50    candidates = []
51    locations = os.environ.get("PATH").split(os.pathsep)
52    for location in locations:
53        candidate = os.path.join(location, program)
54        if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
55            candidates.append(candidate)
56    return candidates
57
58
59def programs_not_in_path(programs):
60    """Returns all the programs that are not found in the PATH."""
61    return [program for program in programs if not which(program)]
62
63
64def _open_for_write(filename):
65    """Opens filename for writing, creating the directories if needed."""
66    dirname = os.path.dirname(filename)
67    if not os.path.exists(dirname):
68        os.makedirs(dirname)
69
70    return io.open(filename, 'w')
71
72
73def _get_cache_filename(name, filename):
74    """Returns the cache location for filename and linter name."""
75    filename = os.path.abspath(filename)[1:]
76    home_folder = os.path.expanduser('~')
77    base_cache_dir = os.path.join(home_folder, '.git-lint', 'cache')
78
79    return os.path.join(base_cache_dir, name, filename)
80
81
82def get_output_from_cache(name, filename):
83    """Returns the output from the cache if still valid.
84
85    It checks that the cache file is defined and that its modification time is
86    after the modification time of the original file.
87
88    Args:
89      name: string: name of the linter.
90      filename: string: path of the filename for which we are retrieving the
91        output.
92
93    Returns: a string with the output, if it is still valid, or None otherwise.
94    """
95    cache_filename = _get_cache_filename(name, filename)
96    if (os.path.exists(cache_filename) and
97            os.path.getmtime(filename) < os.path.getmtime(cache_filename)):
98        with io.open(cache_filename) as f:
99            return f.read()
100
101    return None
102
103
104def save_output_in_cache(name, filename, output):
105    """Saves output in the cache location.
106
107    Args:
108      name: string: name of the linter.
109      filename: string: path of the filename for which we are saving the output.
110      output: string: full output (not yet filetered) of the lint command.
111    """
112    cache_filename = _get_cache_filename(name, filename)
113    with _open_for_write(cache_filename) as f:
114        f.write(output)
115