-
Notifications
You must be signed in to change notification settings - Fork 2
/
todo
executable file
·190 lines (148 loc) · 5.9 KB
/
todo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python3
"""
Extracting "TODO", "FIXME" and "XXX" information from text files.
:todo: Add copyright info and write documentation.
"""
__author__ = "Christian Meesters, after template from Marc Rintsch"
__version__ = '0.1.0'
__date__ = '$Date$'
__revision__ = '$Rev$'
import os
import sys
import re
from itertools import filterfalse
from fnmatch import fnmatch
def fnmatch2(filename, patterns):
"""Check several `patterns` against the given `filename`.
See `fnmatch.fnmatch` for more information.
"""
for pattern in patterns:
if fnmatch(filename, pattern):
return True
return False
def filter_filenames(filenames, patterns):
"""Iterate over all `filenames` that do not match any of the `patterns`.
"""
return filterfalse(lambda f: fnmatch2(f, patterns), filenames)
class TodoItem:
"""An item for the `TodoList`.
`TodoItem` objects contain the location of the item as
`filename` and `line_nr`, the `text` and a `priority`.
The `line_nr` in a file starts with 1 and higher values of
`priority` are indicating more important items.
:ivar filename: where the item's `text` is located.
:ivar line_nr: where the item's `text` is located.
:ivar text: the text of the item.
:ivar priority: how urgent this item is.
:type filename text: str
:type line_nr priority: int
"""
def __init__(self, filename, line_nr, text):
"""Create a `TodoItem`."""
self.filename = filename
self.line_nr = line_nr
self.text = text
self.priority = text.count('!')
def __repr__(self):
return '%s(%r, %r, %r)' % (self.__class__.__name__, self.filename,
self.line_nr, self.text)
def __str__(self):
return '%s:%d:%s' % (self.filename, self.line_nr, self.text)
def __cmp__(self, other):
"""Compare `self` with `other`.
The `priority` is the most important value, followed by
`filename` and `line_nr`.
"""
return (cmp(other.priority, self.priority)
or cmp(self.filename, other.filename)
or cmp(self.line_nr, other.line_nr))
class TodoList:
"""Container for `TodoItem` objects.
Has a method for extracting "TODO" information from files and
allows iteration over its sorted content.
:ivar items: the `TodoItem` objects.
:type items: list
:ivar pattern: regular expression object that holds the pattern
for extracting "TODO" information from text lines.
:type pattern: compiled re
"""
def __init__(self, source_file=None, pattern='(todo|fixme|xxx)[ \t:]'):
"""Create a `TodoList`.
If a files or filename is given as `source_file`, the "TODO"
information will be extracted. Otherwise the `TodoList` is
empty.
The `pattern` is a regular expression that marks the start of
a "TODO" information. The match itself and the text up to the
end of the line is extracted and saved in a `TodoItem` when
reading files with `read_file()`.
:param source_file: file object or name of a file to extract
"TODO" information from.
:type source_file: file or str
:param pattern: regular expression for extracting "TODO"
information.
:type pattern: str
"""
self.items = list()
self.pattern = re.compile(pattern, re.IGNORECASE)
if source_file is not None:
self.read_file(source_file)
def __len__(self):
return len(self.items)
def __iter__(self):
"""Return iterator over the sorted content."""
# self.items.sort()
return iter(self.items)
def read_file(self, source_file):
"""Read `source_file` and extract "TODO" information.
:param source_file: the file to extract the "TODO" information
from.
:type source_file: file or str
"""
if isinstance(source_file, str):
with open(source_file) as infile:
#source_file = open(source_file, 'r')
filename = source_file
try:
line_nr = 0
for line in infile:
line_nr += 1
match = self.pattern.search(line)
if match:
item = TodoItem(filename, line_nr, line[match.start():-1])
self.items.append(item)
except:
pass
def walk(self, root, dir_exclude=('CVS', '.svn', '.git'),
file_exclude=('*~', '*.bak', '*.bck', '*.py[co]')):
"""Walk the directory hierarchy from `root` down and extract "TODO"
information from the files.
Directories and files can be excluded by filename patterns.
:todo: The exclude parameters should be part of the class.
"""
for (root, dirs, files) in os.walk(root):
dirs[:] = filter_filenames(dirs, dir_exclude)
for filename in filter_filenames(files, file_exclude):
self.read_file(os.path.join(root, filename))
def main():
"""Main function.
Iterate over the command line arguments, treat them as filenames or
directories and extract "TODO" information from the files or all files
in the subdirectories.
"""
if len(sys.argv) < 2:
print('Usage: todo file(s)', file=sys.stderr)
sys.exit(1)
todos = TodoList()
for filename in sys.argv[1:]:
if os.path.isdir(filename):
todos.walk(filename)
else:
todos.read_file(filename)
for item in todos:
print(item)
print("Found %d item(s)." % len(todos))
if __name__ == '__main__':
try:
main()
except Exception as error:
print(error)