createHtmlTree.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env python
  2. import argparse
  3. import fnmatch
  4. import os
  5. import sys
  6. # Recursively generate index.html files for
  7. # all subdirectories in a directory tree
  8. index_file_name = 'index.html'
  9. CSS = """ <style>
  10. body {
  11. background: #f4f4f4;
  12. margin: 2em 1.5em;
  13. }
  14. /*
  15. body {
  16. margin: 20px;
  17. background: #f5f5f5;
  18. -webkit-box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  19. -moz-box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  20. box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  21. border-radius: 11px;
  22. -moz-border-radius: 11px;
  23. -webkit-border-radius: 11px;
  24. height: 100%;
  25. min-height: 100%;
  26. }
  27. */
  28. li {
  29. font-family: sans-serif;
  30. font-size: 12pt;
  31. line-height: 14pt;
  32. list-style:none;
  33. list-style-type:none;
  34. padding: 3px 10px;
  35. margin: 3px 15px;
  36. display: block;
  37. clear:both;
  38. }
  39. .content {
  40. width: 600px;
  41. background-color: white;
  42. margin-bottom: 5em;
  43. padding-bottom: 3em;
  44. -webkit-box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  45. -moz-box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  46. box-shadow: rgba(89, 89, 89, 0.449219) 2px 1px 9px 0px;
  47. border: 0;
  48. border-radius: 11px;
  49. -moz-border-radius: 11px;
  50. -webkit-border-radius: 11px;
  51. height: 96%;
  52. min-height: 90%;
  53. }
  54. .size {
  55. float: right;
  56. color: gray;
  57. }
  58. h1 {
  59. padding: 10px;
  60. margin: 15px;
  61. font-size:13pt;
  62. border-bottom: 1px solid lightgray;
  63. }
  64. a {
  65. font-weight: 500;
  66. perspective: 600px;
  67. perspective-origin: 50% 100%;
  68. transition: color 0.3s;
  69. text-decoration: none;
  70. color: #060606;
  71. }
  72. a:hover,
  73. a:focus {
  74. color: #e74c3c;
  75. }
  76. a::before {
  77. background-color: #fff;
  78. transition: transform 0.2s;
  79. transition-timing-function: cubic-bezier(0.7,0,0.3,1);
  80. transform: rotateX(90deg);
  81. transform-origin: 50% 100%;
  82. }
  83. a:hover::before,
  84. a:focus::before {
  85. transform: rotateX(0deg);
  86. }
  87. a::after {
  88. border-bottom: 2px solid #fff;
  89. }
  90. </style>
  91. """
  92. def process_dir(top_dir, opts):
  93. for parentdir, dirs, files in os.walk(unicode(top_dir)):
  94. if not opts.dryrun:
  95. index_file = open(os.path.join(parentdir, index_file_name), "w")
  96. index_file.write('''<!DOCTYPE html>
  97. <html>
  98. <head>{css}</head>
  99. <body>
  100. <div class="content">
  101. <h1>{curr_dir}</h1>
  102. <li><a style="display:block; width:100%" href="..">&#x21B0;</a></li>'''.format(
  103. css=CSS,
  104. curr_dir=os.path.basename(os.path.abspath(parentdir).encode('utf8'))
  105. )
  106. )
  107. for dirname in sorted(dirs):
  108. absolute_dir_path = os.path.join(parentdir, dirname)
  109. if not os.access(absolute_dir_path, os.W_OK):
  110. print("***ERROR*** folder {} is not writable! SKIPPING!".format(absolute_dir_path))
  111. continue
  112. if opts.verbose:
  113. print('DIR:{}'.format(absolute_dir_path))
  114. if not opts.dryrun:
  115. index_file.write("""
  116. <li><a style="display:block; width:100%" href="{link}">&#128193; {link_text}</a></li>""".format(
  117. link=dirname.encode('utf8'),
  118. link_text=dirname.encode('us-ascii', 'xmlcharrefreplace')))
  119. for filename in sorted(files):
  120. if opts.filter and not fnmatch.fnmatch(filename, opts.filter):
  121. if opts.verbose:
  122. print('SKIP: {}/{}'.format(parentdir, filename))
  123. continue
  124. if opts.verbose:
  125. print('{}/{}'.format(parentdir, filename))
  126. filename_escaped = filename.encode('us-ascii', 'xmlcharrefreplace')
  127. filename_utf8 = filename.encode('utf8')
  128. if filename.strip().lower() == index_file_name.lower():
  129. continue
  130. try:
  131. size = int(os.path.getsize(os.path.join(parentdir, filename)))
  132. if not opts.dryrun:
  133. index_file.write(
  134. """
  135. <li>&#x1f4c4; <a href="{link}">{link_text}</a><span class="size">{size}</span></li>""".format(
  136. link=filename_utf8,
  137. link_text=filename_escaped,
  138. size=pretty_size(size))
  139. )
  140. except Exception as e:
  141. print('ERROR writing file name:', e)
  142. print('filename_utf8:')
  143. repr(filename_utf8)
  144. print('filename_escaped:'),
  145. repr(filename_escaped)
  146. if not opts.dryrun:
  147. index_file.write("""
  148. </div>
  149. </body>
  150. </html>""")
  151. index_file.close()
  152. # bytes pretty-printing
  153. UNITS_MAPPING = [
  154. (1024 ** 5, ' PB'),
  155. (1024 ** 4, ' TB'),
  156. (1024 ** 3, ' GB'),
  157. (1024 ** 2, ' MB'),
  158. (1024 ** 1, ' KB'),
  159. (1024 ** 0, (' byte', ' bytes')),
  160. ]
  161. def pretty_size(bytes, units=UNITS_MAPPING):
  162. """Human-readable file sizes.
  163. ripped from https://pypi.python.org/pypi/hurry.filesize/
  164. """
  165. for factor, suffix in units:
  166. if bytes >= factor:
  167. break
  168. amount = int(bytes / factor)
  169. if isinstance(suffix, tuple):
  170. singular, multiple = suffix
  171. if amount == 1:
  172. suffix = singular
  173. else:
  174. suffix = multiple
  175. return str(amount) + suffix
  176. if __name__ == "__main__":
  177. parser = argparse.ArgumentParser(description='''DESCRIPTION:
  178. Generate directory index files recursively.
  179. Start from current dir or from folder passed as first positional argument.
  180. Optionally filter by file types with --filter "*.py". ''')
  181. parser.add_argument('top_dir',
  182. nargs='?',
  183. action='store',
  184. help='top folder from which to start generating indexes, '
  185. 'use current folder if not specified',
  186. default=os.getcwd())
  187. parser.add_argument('--filter', '-f',
  188. help='only include files matching glob',
  189. required=False)
  190. parser.add_argument('--verbose', '-v',
  191. action='store_true',
  192. help='***WARNING: this can take a very long time with complex file tree structures***'
  193. ' verbosely list every processed file',
  194. required=False)
  195. parser.add_argument('--dryrun', '-d',
  196. action='store_true',
  197. help="don't write any files, just simulate the traversal",
  198. required=False)
  199. config = parser.parse_args(sys.argv[1:])
  200. process_dir(config.top_dir, config)