-
Notifications
You must be signed in to change notification settings - Fork 1
/
dir_indexer.py
277 lines (231 loc) · 9.57 KB
/
dir_indexer.py
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import os.path
import time
import datetime
import sys
import argparse
import string
from pathlib import Path
DATE_FORMAT = '%d-%m-%Y %H:%M'
TABLE_START = '<table>'
TABLE_HEADING = ''' <tr>
<th>Name</th>
<th>Modified</th>
<th>Size</th>
</tr>'''
TABLE_DIR = ''' <tr>
<td class="name"><a href="{esc_name}/index.html">{name}</a></td>
<td class="modified">{modified}</td>
<td class="size"></td>
</tr>'''
TABLE_FILE = ''' <tr>
<td class="name"><a href="{esc_name}">{name}</a></td>
<td class="modified">{modified}</td>
<td class="size">{size}</td>
</tr>'''
TABLE_END = '</table>'
def format_size(size_b):
"""Format size given in bytes"""
units = ('B', 'KB', 'MB', 'GB')
size = float(size_b)
for unit in units:
if size <= 2048 or unit == units[-1]:
return '{:.2f} {}'.format(size, unit)
size /= 1024
def format_mtime(mtime):
"""Format time given in seconds"""
mtime = time.localtime(mtime)
return time.strftime(DATE_FORMAT, mtime)
def _is_excluded_name(name, excluded_names):
return name in excluded_names
def _is_excluded_path(path, excluded_paths):
for ex in excluded_paths:
try:
return os.path.samefile(path, ex)
except OSError:
pass
def _is_excluded_pattern(path, excluded_patterns):
for pattern in excluded_patterns:
return Path(path).match(pattern)
return False
def _should_exclude_hidden(name, show_hidden=False):
return show_hidden and os.path.basename(name).startswith('.')
def is_excluded(root, name,
excluded_paths, excluded_names, excluded_patterns,
show_hidden=False):
"""Check if 'path' is excluded in some way.
arguments:
path -- path to check
excluded_paths -- paths which are compared with 'path' as the same files
excluded_names -- basename of 'path' is compared with them
show_hidden -- show hidden files (starting with '.')
"""
path = os.path.join(root, name)
return _is_excluded_name(name, excluded_names) \
or _is_excluded_path(path, excluded_paths) \
or _should_exclude_hidden(name, show_hidden) \
or _is_excluded_pattern(path, excluded_patterns)
def escape_characters(s):
"""Espace spaces as %20"""
return s.replace(' ', '%20')
def create_index(root, dirnames, filenames, template,
excluded_paths=[], excluded_names=[], excluded_patterns=[],
show_hidden=False,
rel_dir=''):
"""Create the index for 'root' directory. Simply a table with
the content of 'root' is inserted into template.
The index is saved in root/index.html.
Existing index.html will be overwritten.
arguments:
root -- path to directory which should be indexed
dirnames -- names of directories in 'root'
filenames -- files in 'root'
template -- string.Template instance with:
$index - table with the content of 'root' will be placed here
$gen_date - generation date
$rel_dir - path from initial directory
excluded_paths -- paths which should be excluded from indexing
excluded_names -- {file,dir}names which should be excluded from indexing
show_hidden -- show hidden files (starting with '.')
rel_dir -- initial directory - 'root'
"""
# build table
table = [TABLE_START, TABLE_HEADING]
# is_excluded shortut for use of filter function
is_excluded_short = \
lambda name: not is_excluded(root, name,
excluded_paths, excluded_names, excluded_patterns,
show_hidden)
for d in sorted(
filter(is_excluded_short, dirnames),
key=str.lower):
table.append(TABLE_DIR.format(
esc_name=escape_characters(d),
name=d,
modified=format_mtime(os.path.getmtime(os.path.join(root, d)))))
for f in sorted(
filter(is_excluded_short, filenames),
key=str.lower):
table.append(TABLE_FILE.format(
esc_name=escape_characters(f),
name=f,
modified=format_mtime(os.path.getmtime(os.path.join(root, f))),
size=format_size(os.path.getsize(os.path.join(root, f)))))
table.append(TABLE_END)
gen_date = datetime.datetime.now().strftime(DATE_FORMAT)
with open(os.path.join(root, 'index.html'), 'w') as index_file:
# fill the template
index_file.write(template.safe_substitute(index='\n'.join(table),
gen_date=gen_date,
rel_dir=rel_dir))
def get_rel_dir(path, root):
"""Return path - root.
example:
path = '/home/user/sth1/sth2/sth3/sth4'
root = '/home/user/sth1/sth2'
result: /sth3/sth4
"""
path = os.path.normpath(path)
root = os.path.normpath(root)
if len(path) == len(root):
return '/'
else:
return path[len(root):]
def walk_level(path, level=-1):
"""Similar to os.walk function but with yielding current level
from 'path'.
Argument 'level' -- how deep the recusion will go (if less than 0 then
there is no limit).
from: http://stackoverflow.com/a/234329
"""
num_sep = path.count(os.sep)
for root, dirnames, filenames in os.walk(path):
cur_level = root.count(os.sep) - num_sep
yield root, dirnames, filenames, cur_level
if level >= 0 and cur_level >= level:
# it omits directories under some level
# you can read about this trick in python docs
del dirnames[:]
def generate(path, template_path, quiet=False, recursive=False, level=0,
excluded_paths=[], excluded_names=[], excluded_patterns=[],
show_hidden=False):
"""Create the index for 'path' and optionally deeper with a template.
arguments:
path -- where the indexing should start
template_path -- path to html template
quiet -- will print some information or no
recursive -- should directiory in 'path' be indexed recursively,
if 'recursive' is True then 'level' is ignored
level -- set the maximum level of recursion ('recursive' must be False
to 'level' has the effect)
excluded_paths -- paths which should be excluded from indexing
excluded_names -- {file,dir}names which should be excluded from indexing
show_hidden -- show hidden files (starting with '.')
"""
with open(template_path) as template_source:
template = string.Template(template_source.read())
# hide index.html
excluded_names += ['index.html']
norm_path = os.path.normpath(path)
abs_excluded_paths = [os.path.normpath(os.path.join(norm_path, e))
for e in excluded_paths]
for root, dirnames, filenames, cur_level in walk_level(
norm_path, -1 if recursive else level):
# check if current directory is the last to be indexed
# then hide dirnames
if not recursive and level <= cur_level:
dirnames = []
create_index(root, dirnames, filenames, template,
abs_excluded_paths, excluded_names, excluded_patterns,
show_hidden,
get_rel_dir(root, path))
if not quiet:
print('Directory {} indexed'.format(root))
def main():
parser = argparse.ArgumentParser(
description='Generate a html file with the content of directory',
epilog='All index.html files will be OVERWRITTEN')
parser.add_argument('path',
help='path to index')
parser.add_argument('-t', '--template',
help='path to HTML template file',
default='templates/default.html')
parser.add_argument('-q', '--quiet',
help='print nothing',
action='store_true')
group = parser.add_mutually_exclusive_group()
group.add_argument('-R', '-r', '--recursive',
help='generate pages recursively',
action='store_true')
group.add_argument('-l', '--level',
help='generate pages recursively with the maximum '
'recursion depth level',
type=int, default=0)
parser.add_argument('-e', '--exclude',
help='exclude path(s) from being indexed',
nargs='+', default=[], metavar='PATH')
parser.add_argument('--exclude-names',
help='exclude name(s) from being indexed '
'(basenames are being compared)',
nargs='+', default=[], metavar='NAME')
parser.add_argument('--exclude-patterns',
help='exclude GLOB pattern(s) from being indexed',
nargs='+', default=[], metavar='PATTERN')
parser.add_argument('--hidden',
help='show hidden files',
action='store_true')
args = parser.parse_args()
# check paths given by the user
if not os.path.isdir(args.path):
print('{} is not a directory'.format(args.path))
sys.exit(1)
if not (os.path.isfile(args.template)):
print('Given template path {} is not a file'.format(args.template))
sys.exit(1)
generate(args.path, args.template, args.quiet, args.recursive, args.level,
args.exclude, args.exclude_names, args.exclude_patterns,
args.hidden)
if __name__ == '__main__':
main()