csscom.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/usr/bin/env python
  2. from argparse import ArgumentParser
  3. import logging
  4. import sys
  5. from parse import parse_groups
  6. from generate import generate_group
  7. def compress_css(css, combine_blocks=True, compress_whitespace=True,
  8. compress_color=True, compress_font=True,
  9. compress_dimension=True, sort_properties=True, tab='\t'):
  10. groups = parse_groups(css)
  11. options = dict(combine_blocks=combine_blocks,
  12. compress_whitespace=compress_whitespace,
  13. compress_color=compress_color,
  14. compress_font=compress_font,
  15. compress_dimension=compress_dimension,
  16. sort_properties=sort_properties,
  17. tab=tab)
  18. compressed_groups = [generate_group(selectors, blocks, **options)
  19. for selectors, blocks in groups]
  20. newlines = '' if compress_whitespace else '\n\n'
  21. return newlines.join(compressed_groups)
  22. def parse_options():
  23. parser = ArgumentParser(description='Just another CSS compressor. '
  24. 'If none of the compression options below (those starting with '
  25. '"-c") are specified, all are enabled by default. If any are '
  26. 'specified, the others are not enabled.')
  27. parser.add_argument('files', metavar='FILE', nargs='*',
  28. help='CSS files to compress (CSS is read from stdin '
  29. 'if no files are specified)')
  30. parser.add_argument('-cw', '--compress-whitespace', action='store_true',
  31. help='omit unnecessary whitespaces and semicolons')
  32. parser.add_argument('-cc', '--compress-color', action='store_true',
  33. help='replace color codes/names with shorter synonyms')
  34. parser.add_argument('-cf', '--compress-font', action='store_true',
  35. help='replace separate font statements with shortcut '
  36. 'font statement where possible')
  37. parser.add_argument('-cd', '--compress-dimension', action='store_true',
  38. help='replace separate margin/padding statements with '
  39. 'shortcut statements where possible')
  40. parser.add_argument('-cb', '--combine-blocks', action='store_true',
  41. help='combine or split blocks into blocks with '
  42. 'comma-separated selectors if it results in less '
  43. 'css code')
  44. parser.add_argument('-nc', '--no-compression', action='store_true',
  45. help='don\'t apply any compression, just generate CSS')
  46. parser.add_argument('-ns', '--no-sort', action='store_false',
  47. dest='sort_properties', help='sort property names')
  48. parser.add_argument('-s', '--spaces', type=int, metavar='NUMBER=4',
  49. nargs='?', const=4,
  50. help='number of spaces to use for indenting (indent '
  51. 'defaults to a single tab [\\t])')
  52. parser.add_argument('-o', '--output', metavar='FILE',
  53. help='filename for compressed output (default is '
  54. 'stdout)')
  55. parser.add_argument('-ow', '--overwrite', action='store_true',
  56. help='use the first CSS file as output file')
  57. parser.add_argument('-v', '--verbose', action='store_true',
  58. help='show which compressions are performed')
  59. parser.add_argument('-d', '--debug', action='store_true',
  60. help='show debug statements')
  61. parser.add_argument('--logfile', metavar='FILE',
  62. help='file to write verbose/debug statements to '
  63. '(defaults to stderr)')
  64. args = parser.parse_args()
  65. # Enable all compression options if none are explicitely enabled
  66. if not any([args.compress_whitespace, args.compress_color,
  67. args.compress_font, args.compress_dimension,
  68. args.combine_blocks]) and not args.no_compression:
  69. args.compress_whitespace = args.compress_color = args.compress_font \
  70. = args.compress_dimension = args.combine_blocks = True
  71. return args
  72. def _content(filename):
  73. handle = open(filename, 'r')
  74. content = handle.read()
  75. handle.close()
  76. return content
  77. if __name__ == '__main__':
  78. args = parse_options()
  79. options = dict(args._get_kwargs())
  80. files = options.pop('files')
  81. del options['no_compression']
  82. spaces = options.pop('spaces')
  83. options['tab'] = '\t' if spaces is None else spaces * ' '
  84. outfile = options.pop('output')
  85. if options.pop('overwrite'):
  86. if not files:
  87. print 'error: cannot use --overwrite option wihthout filenames'
  88. sys.exit(1)
  89. outfile = files[0]
  90. log_level = logging.WARNING
  91. if options.pop('verbose'):
  92. log_level = logging.INFO
  93. if options.pop('debug'):
  94. log_level = logging.DEBUG
  95. logging.basicConfig(level=log_level, filename=options.pop('logfile'),
  96. #format='%(levelname)s: %(message)s')
  97. format='%(filename)s, line %(lineno)s: %(levelname)s: %(message)s')
  98. try:
  99. if files:
  100. css = '\n'.join(_content(filename) for filename in files)
  101. else:
  102. css = sys.stdin.read()
  103. compressed = compress_css(css, **options)
  104. if outfile:
  105. open(outfile, 'w').write(compressed)
  106. else:
  107. print compressed,
  108. except IOError as e:
  109. print e