this is a green box
this is a blue box
this is paragraph
Repository: ccampbell/html-muncher Branch: master Commit: 86c425b0a3db Files: 16 Total size: 51.6 KB Directory structure: gitextract_o1tbrn8m/ ├── .gitignore ├── README ├── demo/ │ ├── css/ │ │ ├── stylesheet1.css │ │ └── stylesheet2.css │ ├── js/ │ │ └── test.js │ ├── single-file/ │ │ └── view-with-inline-styles.html │ └── views/ │ ├── view1.html │ └── view2.html ├── munch ├── muncher/ │ ├── __init__.py │ ├── config.py │ ├── muncher.py │ ├── sizetracker.py │ ├── util.py │ └── varfactory.py └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc *.opt.html *.opt.js *.opt.css demo/css_opt demo/js_opt demo/views_opt ================================================ FILE: README ================================================ -------------- ABOUT -------------- HTML Muncher is a Python utility that rewrites CSS, HTML, and JavaScript files in order to save precious bytes and obfuscate your code if your stylesheet starts out looking like this: .file2 #special { font-size: 1.5em; color: #F737FF; } .file2 #special2 { letter-spacing: 0; } .box { border: 2px solid #aaa; -webkit-border-radius: 5px; background: #eee; padding: 5px; } it will be rewritten as .a #a { font-size: 1.5em; color: #F737FF; } .a #b { letter-spacing: 0; } .b { border: 2px solid #aaa; -webkit-border-radius: 5px; background: #eee; padding: 5px; } -------------- INSTALLATION -------------- easy_install http://htmlmuncher.com/htmlmuncher.egg OR: download the source from http://github.com/ccampbell/html-muncher cd html-muncher python setup.py install -------------- USAGE -------------- http://htmlmuncher.com/#usage OR: munch --help -------------- EXAMPLES -------------- to update a bunch of stylesheets and views: munch --css demo/css --html demo/views to update a single file with inline styles/javascript: munch --html demo/single-file/view-with-inline-styles.html you can also select specific files: munch --css file1.css,file2.css --html view1.html,view2.html or you can mix and match files and directories munch --css /my/css/directory,global.css --html /view/directory1,/view/directory2,/view/directory3,template.html ================================================ FILE: demo/css/stylesheet1.css ================================================ body { font-family: helvetica; } h1 { text-align: center; font-size: 2em; } .red { color: red; } .blue { color: blue; } .green { color: green; } .box.purple { color: purple; } .underline { text-decoration: underline; } #special { color: orange; font-style: italic; } .italic { font-style: italic; } #special2 { letter-spacing: .5em; color: grey; } #new_id, #special, #special2 { font-size: 1em; } ================================================ FILE: demo/css/stylesheet2.css ================================================ .file2 #special { font-size: 1.5em; color: #F737FF; } .file2 #special2 { letter-spacing: 0; } .box { border: 2px solid #aaa; -webkit-border-radius: 5px; background: #eee; padding: 5px; } ================================================ FILE: demo/js/test.js ================================================ $ = { qs: function(query) { return document.querySelector(query); } }; window.onload = function() { $.qs("#special").innerHTML = "new text for this paragraph"; document.getElementById("special").innerHTML = "change it again"; var italic = document.getElementsByClassName('italic'); // mootools var test = $('test'); if (test.hasClass("dont_know")) { test.removeClass("dont_know"); test.addClass('now_i_know'); } var class_thing = $('class_thing'); class_thing.addClass(test, "whatever"); class_thing.removeClass(test, "whatever"); $.qs(".dont_know", class_thing).value; var cool = $.qs("#one_id.class_thing", test); var another_weird_thing = $.qs(".class1.class2 #another_id"); $.qs(".selector1 > .selector2 .selector3"); var test = document.querySelector(".selector1"); } ================================================ FILE: demo/single-file/view-with-inline-styles.html ================================================
this is a green box
this is a blue box
this is paragraph
Assertively leverage existing scalable growth strategies with revolutionary value. Distinctively recaptiualize top-line models rather than leveraged e-commerce. Quickly engineer orthogonal e-markets via holistic human capital.
this is a test with two classes
Appropriately incubate collaborative imperatives after team building networks. Uniquely enhance sticky e-services for value-added solutions. Seamlessly parallel task value-added quality vectors before state of the art methods of empowerment.
Interactively strategize plug-and-play platforms whereas efficient infrastructures. Synergistically negotiate user-centric metrics rather than worldwide quality vectors. Holisticly deliver bleeding-edge leadership vis-a-vis world-class architectures.
Professionally iterate multifunctional systems before optimal materials. Efficiently reintermediate wireless total linkage with distributed portals. Globally exploit resource-leveling human capital without economically sound infomediaries.
Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.
================================================ FILE: demo/views/view2.html ================================================Assertively leverage existing scalable growth strategies with revolutionary value. Distinctively recaptiualize top-line models rather than leveraged e-commerce. Quickly engineer orthogonal e-markets via holistic human capital.
Appropriately incubate collaborative imperatives after team building networks. Uniquely enhance sticky e-services for value-added solutions. Seamlessly parallel task value-added quality vectors before state of the art methods of empowerment.
Professionally iterate multifunctional systems before optimal materials. Efficiently reintermediate wireless total linkage with distributed portals. Globally exploit resource-leveling human capital without economically sound infomediaries.
Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.
================================================ FILE: munch ================================================ #!/usr/bin/env python # Copyright 2011 Craig Campbell # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from muncher.config import Config from muncher.muncher import Muncher config = Config() config.processArgs() muncher = Muncher(config) muncher.run() ================================================ FILE: muncher/__init__.py ================================================ ================================================ FILE: muncher/config.py ================================================ #!/usr/bin/env python # Copyright 2011 Craig Campbell # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys, getopt from muncher import Muncher class Config(object): """configuration object for handling all config options for html-muncher""" def __init__(self): """config object constructor Returns: void """ self.css = [] self.views = [] self.js = [] self.ignore = [] self.class_selectors = ["getElementsByClassName", "hasClass", "addClass", "removeClass"] self.id_selectors = ["getElementById"] self.custom_selectors = ["document.querySelector"] self.framework = None self.view_extension = "html" self.js_manifest = None self.show_savings = False self.compress_html = False self.rewrite_constants = False self.verbose = False def getArgCount(self): """gets the count of how many arguments are present Returns: int """ return len(sys.argv) def setIgnore(self, value): """sets what classes and ids we should ignore and not shorten Arguments: value -- comma separated list of classes or ids Returns: void """ for name in value.split(","): self.ignore.append(name) def setCustomSelectors(self, value): for value in value.split(","): self.custom_selectors.append(value.lstrip(".")) def addClassSelectors(self, value): for value in value.split(","): self.class_selectors.append(value) def addIdSelectors(self, value): for value in value.split(","): self.id_selectors.append(value) def setCssFiles(self, value): for value in value.split(","): self.css.append(value.rstrip("/")) def setViewFiles(self, value): for value in value.split(","): self.views.append(value.rstrip("/")) def setJsFiles(self, value): for value in value.split(","): self.js.append(value.rstrip("/")) def setFramework(self, name): self.framework = name.lower() if self.framework == "jquery": self.custom_selectors.append("$") self.custom_selectors.append("jQuery") elif self.framework == "mootools": self.id_selectors.append("$") self.custom_selectors.append("getElement") def processArgs(self): """processes arguments passed in via command line and sets config settings accordingly Returns: void """ try: opts, args = getopt.getopt(sys.argv[1:], "", ["css=", "views=", "html=", "js=", "help", "view-ext=", "ignore=", "framework=", "selectors=", "class-selectors=", "id-selectors=", "compress-html", "show-savings", "verbose", "js-manifest=", "rewrite-constants"]) except: Muncher.showUsage() views_set = False for key, value in opts: if key == "--help": Muncher.showUsage() elif key == "--css": self.setCssFiles(value) elif key == "--views" or key == "--html": views_set = True self.setViewFiles(value) elif key == "--js": self.setJsFiles(value) elif key == "--ignore": self.setIgnore(value) elif key == "--view-ext": self.view_extension = value elif key == "--framework": self.setFramework(value) elif key == "--selectors": self.setCustomSelectors(value) elif key == "--class-selectors": self.addClassSelectors(value) elif key == "--id-selectors": self.addIdSelectors(value) elif key == "--compress-html": self.compress_html = True elif key == "--show-savings": self.show_savings = True elif key == "--verbose": self.verbose = True elif key == "--js-manifest": self.js_manifest = value elif key == "--rewrite-constants": self.rewrite_constants = True # you have to at least have a view if views_set is False: Muncher.showUsage() ================================================ FILE: muncher/muncher.py ================================================ #!/usr/bin/env python # Copyright 2011 Craig Campbell # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys, re, glob, os from operator import itemgetter from util import Util from varfactory import VarFactory from sizetracker import SizeTracker class Muncher(object): def __init__(self, config): """constructor Returns: void """ self.id_counter = {} self.class_counter = {} self.id_map = {} self.class_map = {} self.config = config @staticmethod def showUsage(): """shows usage information for this script""" print "\n---------------------------------" print " html-muncher" print "---------------------------------" print "\n" + '\033[91m' + "USAGE:" + '\033[0m' print "munch --css file1.css,/path/to/css1,file2.css,file3.css --html /path/to/views1,file1.html,/path/to/views2/,file3.html --js main.js,/path/to/js" print "\n" + '\033[91m' + "REQUIRED ARGUMENTS:" + '\033[0m' print "--html {path/to/views} html files to rewrite (comma separated list of directories and files)" print "\n" + '\033[91m' + "OPTIONAL ARGUMENTS:" + '\033[0m' print "--css {path/to/css} css files to rewrite (comma separated list of directories and files)" print "" print "--js {path/to/js} js files to rewrite (comma separated list of directories and files)" print "" print "--view-ext {extension} sets the extension to look for in the view directory (defaults to html)" print "" print "--ignore {classes,ids} comma separated list of classes or ids to ignore when rewriting css (ie .sick_class,#sweet_id)" print "" print "--compress-html strips new line characters to compress html files specified with --html" print " be careful when using this becuase it has not been thoroughly tested" print "" print "--framework name of js framework to use for selectors (currently only jquery or mootools)" print "" print "--selectors comma separated custom selectors using css selectors" print " for example if you have $.qs(\"#test .div\") this param would be qs" print "" print "--id-selectors comma separated id selectors with strings" print " for example if you are using .addId(\"test\") this param would be addId" print "" print "--class-selectors comma separated class selectors with strings" print " for example if you have selectClass(\"my_class\") this param would be selectClass" print "" print "--js-manifest path to a js file containing class name/id constants" print "" print "--rewrite-constants when using a manifest file this will take any constants with values as strings" print " and rewrite the values to be numbers" print "" print "--show-savings will output how many bytes were saved by munching" print "" print "--verbose output more information while the script runs" print "" print "--help shows this menu\n" sys.exit(2) def run(self): """runs the optimizer and does all the magic Returns: void """ self.output("searching for classes and ids...", False) if self.config.js_manifest is not None: self.outputJsWarnings() self.processCss() self.processViews() if self.config.js_manifest is None: self.processJs() else: self.processJsManifest() self.output("mapping classes and ids to new names...", False) # maps all classes and ids found to shorter names self.processMaps() # optimize everything self.output("munching css files...", False) self.optimizeFiles(self.config.css, self.optimizeCss) self.output("munching html files...", False) self.optimizeFiles(self.config.views, self.optimizeHtml, self.config.view_extension, self.config.compress_html) self.output("munching js files...", False) if self.config.js_manifest is None: self.optimizeFiles(self.config.js, self.optimizeJavascript) else: self.optimizeJsManifest() self.output("done", False) if self.config.show_savings: self.output(SizeTracker.savings(), False) def outputJsWarnings(self): pass def output(self, text, verbose_only = True): """outputs text during the script run Arguments: text -- string of text to output verbose_only -- should we only show this in verbose mode? Returns: void """ if verbose_only and not self.config.verbose: return print text def processCssDirectory(self, file): """processes a directory of css files Arguments: file -- path to directory Returns: void """ if ".svn" in file: return for dir_file in Util.getFilesFromDir(file): if Util.isDir(dir_file): self.processCssDirectory(dir_file) continue self.processCssFile(dir_file) def processCss(self): """gets all css files from config and processes them to see what to replace Returns: void """ files = self.config.css for file in files: if not Util.isDir(file): self.processCssFile(file) continue self.processCssDirectory(file) def processViewDirectory(self, file): """processes a directory of view files Arguments: file -- path to directory Returns: void """ if ".svn" in file: return for dir_file in Util.getFilesFromDir(file): if Util.isDir(dir_file): self.processViewDirectory(dir_file) continue self.processView(dir_file) def processViews(self): """processes all view files Returns: void """ files = self.config.views for file in files: if not Util.isDir(file): self.processView(file) continue self.processViewDirectory(file) def processJsDirectory(self, file): """processes a directory of js files Arguments: file -- path to directory Returns: void """ if ".svn" in file: return for dir_file in Util.getFilesFromDir(file): if Util.isDir(dir_file): self.processJsDirectory(dir_file) continue self.processJsFile(dir_file) def processJs(self): """gets all js files from config and processes them to see what to replace Returns: void """ files = self.config.js for file in files: if not Util.isDir(file): self.processJsFile(file) continue self.processJsDirectory(file) def processView(self, file): """processes a single view file Arguments: file -- path to directory """ self.processCssFile(file, True) self.processJsFile(file, True) def processCssFile(self, path, inline = False): """processes a single css file to find all classes and ids to replace Arguments: path -- path to css file to process Returns: void """ contents = Util.fileGetContents(path) if inline is True: blocks = self.getCssBlocks(contents) contents = "" for block in blocks: contents = contents + block ids_found = re.findall(r'((?)', '', content, re.MULTILINE) return content def optimizeCss(self, path): """replaces classes and ids with new values in a css file Arguments: path -- string path to css file to optimize Returns: string """ css = Util.fileGetContents(path) return self.replaceCss(css) def optimizeHtml(self, path): """replaces classes and ids with new values in an html file Uses: Muncher.replaceHtml Arguments: path -- string path to file to optimize Returns: string """ html = Util.fileGetContents(path) html = self.replaceHtml(html) html = self.optimizeCssBlocks(html) html = self.optimizeJavascriptBlocks(html) return html def replaceHtml(self, html): """replaces classes and ids with new values in an html file Arguments: html -- contents to replace Returns: string """ html = self.replaceHtmlIds(html) html = self.replaceHtmlClasses(html) return html def replaceHtmlIds(self, html): """replaces any instances of ids in html markup Arguments: html -- contents of file to replaces ids in Returns: string """ for key, value in self.id_map.items(): key = key[1:] value = value[1:] html = html.replace("id=\"" + key + "\"", "id=\"" + value + "\"") return html def replaceClassBlock(self, class_block, key, value): """replaces a class string with the new class name Arguments: class_block -- string from what would be found within class="{class_block}" key -- current class value -- new class Returns: string """ key_length = len(key) classes = class_block.split(" ") i = 0 for class_name in classes: if class_name == key: classes[i] = value # allows support for things like a.class_name as one of the js selectors elif key[0] in (".", "#") and class_name[-key_length:] == key: classes[i] = class_name.replace(key, value) i = i + 1 return " ".join(classes) def replaceHtmlClasses(self, html): """replaces any instances of classes in html markup Arguments: html -- contents of file to replace classes in Returns: string """ for key, value in self.class_map.items(): key = key[1:] value = value[1:] class_blocks = re.findall(r'class\=((\'|\")(.*?)(\'|\"))', html) for class_block in class_blocks: new_block = self.replaceClassBlock(class_block[2], key, value) html = html.replace("class=" + class_block[0], "class=" + class_block[1] + new_block + class_block[3]) return html def optimizeCssBlocks(self, html): """rewrites css blocks that are part of an html file Arguments: html -- contents of file we are replacing Returns: string """ result_css = "" matches = self.getCssBlocks(html) for match in matches: match = self.replaceCss(match) result_css = result_css + match if len(matches): return html.replace(matches[0], result_css) return html @staticmethod def getCssBlocks(html): """searches a file and returns all css blocks Arguments: html -- contents of file we are replacing Returns: list """ return re.compile(r'\