Full Code of ccampbell/html-muncher for AI

master 86c425b0a3db cached
16 files
51.6 KB
12.1k tokens
82 symbols
1 requests
Download .txt
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
================================================
<html>
<head>
    <title>view with inline styles</title>
    <style type="text/css">
        .content {
            padding: 25px 0px 50px 0px;
            overflow: hidden;
        }

        a {
            color: #FF5F1E;
        }

        .content, #footer {
            width: 800px;
        }

        ul.menu {
            width: 200px;
            float: left;
        }

        #main {
            width: 500px;
            float: left;
        }

        ul {
            padding: 0px;
            margin: 0px;
            list-style: none;
        }

        ul li {
            margin-top: 20px;
        }

        ul li.first_child {
            margin-top: 0px;
        }

        .box {
            width: 500px;
            border: 1px solid #aaa;
            padding: 5px;
            -moz-border-radius: 5px;
            -webkit-border-radius: 5px;
            margin-bottom: 15px;
            background: #fff url("../test.jpg");
        }

        .green {
            background: #ACFFB9;
        }

        .blue {
            background: #C5D7FF;
        }

        #footer ul {
            text-align: center;
        }

        #footer ul li {
            display: inline;
            margin-left: 20px;
        }

        #footer ul li.first_child {
            margin-left: 0px;
        }

        p.special span {
            font-weight: bold;
        }
    </style>
</head>
<body>
    <h1>test page with inline styles</h1>

    <div class="content">
        <ul class="menu">
            <li class="first_child"><a href="#">link 1</a></li>
            <li class="unused_class"><a href="#">link 2</a></li>
            <li><a href="#">link 3</a></li>
            <li><a href="#">link 4</a></li>
            <li><a href="#">link 5</a></li>
        </ul>

        <div id="main">
            <div class="box green">
                <p>this is a green box</p>
            </div>

            <div class="box blue">
                <p>this is a blue box</p>
            </div>

            <p class="special">this is <span>paragraph</span></p>
        </div>
    </div>

    <div id="footer">
        <ul>
            <li class="first_child"><a href="#">footer link 1</a></li>
            <li><a href="#">footer link 2</a></li>
            <li><a href="#">footer link 3</a></li>
            <li><a href="#">footer link 4</a></li>
        </ul>
    </div>
    <script type="text/javascript">
        $('.special').hide();
        $("#main .box").hide();
    </script>
</body>
</html>

================================================
FILE: demo/views/view1.html
================================================
<html>
<head>
    <title>test page 1 changed</title>
    <link href="../css/stylesheet1.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <h1>this is the first test page</h1>
    <p class="red">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.</p>
    <p class="box purple">this is a test with two classes</p>
    <p class="blue">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.</p>
    <p class="green underline">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.</p>
    <p id="special">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.</p>
    <p id="special2" class="italic">Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.</p>
    <script type="text/javascript" src="../js/test.js"></script>
</body>
</html>

================================================
FILE: demo/views/view2.html
================================================
<html>
<head>
    <title>test page 2</title>
    <link href="../css/stylesheet1.css" rel="stylesheet" type="text/css" />
    <link href="../css/stylesheet2.css" rel="stylesheet" type="text/css" />
</head>
<body class="file2">
    <h1>this is the second test page</h1>
    <p class="red">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.</p>
    <p class="box blue">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.</p>
    <p id="special">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.</p>
    <p id="special2" class="italic">Intrinsicly streamline user-centric users before visionary scenarios. Dynamically repurpose seamless channels via multifunctional process improvements. Professionally synthesize exceptional infrastructures without premier vortals.</p>
</body>
</html>


================================================
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'((?<!\:\s)(?<!\:)#\w+)(\.|\{|,|\s|#)', contents, re.DOTALL)
        classes_found = re.findall(r'(?!\.[0-9])\.\w+', contents)
        self.addIds(ids_found)
        self.addClasses(classes_found)

    def processJsFile(self, path, inline = False):
        """processes a single js 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.getJsBlocks(contents)
            contents = ""
            for block in blocks:
                contents = contents + block

        selectors = self.getJsSelectors(contents, self.config)
        for selector in selectors:
            if selector[0] in self.config.id_selectors:
                if ',' in selector[2]:
                    id_to_add = re.search(r'(\'|\")(.*?)(\'|\")', selector[2])
                    if id_to_add is None:
                        continue

                    if not id_to_add.group(2):
                        continue

                    self.addId("#" + id_to_add.group(2))

                # if this is something like document.getElementById(variable) don't add it
                if not '\'' in selector[2] and not '"' in selector[2]:
                    continue

                self.addId("#" + selector[2].strip("\"").strip("'"))
                continue

            if selector[0] in self.config.class_selectors:
                class_to_add = re.search(r'(\'|\")(.*?)(\'|\")', selector[2])
                if class_to_add is None:
                    continue

                if not class_to_add.group(2):
                    continue

                self.addClass("." + class_to_add.group(2))
                continue

            if selector[0] in self.config.custom_selectors:
                matches = re.findall(r'((#|\.)[a-zA-Z0-9_]*)', selector[2])
                for match in matches:
                    if match[1] == "#":
                        self.addId(match[0])
                        continue

                    self.addClass(match[0])

    def processJsManifest(self):
        contents = Util.fileGetContents(self.config.js_manifest)
        ids = re.findall(r'\s+?(var\s)?\${1}([A-Z0-9_]+)\s?=\s?[\'|\"](.*?)[\'|\"][,|;]', contents)
        classes = re.findall(r'\s+?(var\s)?\${2}([A-Z0-9_]+)\s?=\s?[\'|\"](.*?)[\'|\"][,|;]', contents)

        self.manifest_ids = {}
        self.manifest_classes = {}

        for id in ids:
            self.addId("#" + id[2])
            self.manifest_ids[id[1]] = id[2]

        for manifest_class in classes:
            self.addClass("." + manifest_class[2])
            self.manifest_classes[manifest_class[1]] = manifest_class[2]

    def optimizeJsManifest(self):
        contents = Util.fileGetContents(self.config.js_manifest)

        for key, value in self.manifest_ids.items():
            if "#" + value in self.id_map:
                contents = re.sub(r'((?<!\$)\${1}[A-Z0-9_]+\s?=\s?[\'|\"])(' + value + ')([\'|\"][,|;])', r'\1' + self.id_map["#" + value].replace("#", "") + r'\3', contents)

        for key, value in self.manifest_classes.items():
            if "." + value in self.class_map:
                contents = re.sub(r'(\${2}[A-Z0-9_]+\s?=\s?[\'|\"])(' + value + ')([\'|\"][,|;])', r'\1' + self.class_map["." + value].replace(".", "") + r'\3', contents)

        if self.config.rewrite_constants:
            constants = re.findall(r'(\s+?(var\s)?([A-Z0-9_]+)\s?=\s?[\'|\"](.*?)[\'|\"][,|;])', contents)
            new_constants = {}
            i = 0
            for constant in constants:
                # underscore variables are ignored
                if constant[2][0] == "_":
                    continue

                i += 1
                new_constant = re.sub(r'=(.*)([,|;])','= ' + str(i) + r'\2', constant[0])
                contents = contents.replace(constant[0], new_constant)

        new_manifest = Util.prependExtension("opt", self.config.js_manifest)
        Util.filePutContents(new_manifest, contents)

        if self.config.show_savings:
            SizeTracker.trackFile(self.config.js_manifest, new_manifest)

    def processMaps(self):
        """loops through classes and ids to process to determine shorter names to use for them
        and creates a dictionary with these mappings

        Returns:
        void

        """
        # reverse sort so we can figure out the biggest savings
        classes = self.class_counter.items()
        classes.sort(key = itemgetter(1), reverse=True)

        for class_name, savings in classes:
            small_class = "." + VarFactory.getNext("class")

            # adblock extensions may block class "ad" so we should never generate it
            # also if the generated class already exists as a class to be processed
            # we can't use it or bad things will happen
            while small_class == ".ad" or Util.keyInTupleList(small_class, classes):
                small_class = "." + VarFactory.getNext("class")

            self.class_map[class_name] = small_class

        ids = self.id_counter.items()
        ids.sort(key = itemgetter(1), reverse=True)

        for id, savings in ids:
            small_id = "#" + VarFactory.getNext("id")

            # same holds true for ids as classes
            while small_id == "#ad" or Util.keyInTupleList(small_id, ids):
                small_id = "#" + VarFactory.getNext("id")

            self.id_map[id] = small_id

    def incrementIdCounter(self, name):
        """called for every time an id is added to increment the bytes we will save

        Arguments:
        name -- string of id

        Returns:
        void

        """
        length = len(name)

        if not name in self.id_counter:
            self.id_counter[name] = length
            return

        self.id_counter[name] += length

    def incrementClassCounter(self, name):
        """called for every time a class is added to increment the bytes we will save

        Arguments:
        name -- string of class

        Returns:
        void

        """
        length = len(name)

        if not name in self.class_counter:
            self.class_counter[name] = length
            return

        self.class_counter[name] += length

    def incrementCounter(self, name):
        """called everytime a class or id is added

        Arguments:
        name -- string of class or id name

        Returns:
        void

        """
        if name[0] == "#":
            return self.incrementIdCounter(name)

        return self.incrementClassCounter(name)

    def addId(self, id):
        """adds a single id to the master list of ids

        Arguments:
        id -- single id to add

        Returns:
        void

        """
        if id in self.config.ignore or id is '#':
            return

        # skip $ ids from manifest
        if self.config.js_manifest is not None and id[1] == '$':
            return

        self.incrementCounter(id)

    def addIds(self, ids):
        """adds a list of ids to the master id list to replace

        Arguments:
        ids -- list of ids to add

        Returns:
        void

        """
        for id in ids:
            self.addId(id[0])

    def addClass(self, class_name):
        """adds a single class to the master list of classes

        Arguments:
        class_name -- single class to add

        Returns:
        void

        """
        if class_name in self.config.ignore or class_name is '.':
            return

        # skip $$ class names from manifest
        if self.config.js_manifest is not None and class_name[1:2] == '$$':
            return

        self.incrementCounter(class_name)

    def addClasses(self, classes):
        """adds a list of classes to the master class list to replace

        Arguments:
        classes -- list of classes to add

        Returns:
        void

        """
        for class_name in classes:
            self.addClass(class_name)

    def optimizeFiles(self, paths, callback, extension = "", minimize = False):
        """loops through a bunch of files and directories, runs them through a callback, then saves them to disk

        Arguments:
        paths -- array of files and directories
        callback -- function to process each file with

        Returns:
        void

        """
        for file in paths:
            if not Util.isDir(file):
                self.optimizeFile(file, callback, minimize)
                continue

            self.optimizeDirectory(file, callback, extension, minimize)

    def optimizeFile(self, file, callback, minimize = False, new_path = None, prepend = "opt"):
        """optimizes a single file

        Arguments:
        file -- path to file
        callback -- function to run the file through
        minimize -- whether or not we should minimize the file contents (html)
        prepend -- what extension to prepend

        Returns:
        void

        """
        content = callback(file)
        if new_path is None:
            new_path = Util.prependExtension(prepend, file)
        if minimize is True:
            self.output("minimizing " + file)
            content = self.minimize(content)
        self.output("optimizing " + file + " to " + new_path)
        Util.filePutContents(new_path, content)

        if self.config.show_savings:
            SizeTracker.trackFile(file, new_path)

    def prepareDirectory(self, path):
        if ".svn" in path:
            return True

        if Util.isDir(path):
            return False

        Util.unlinkDir(path)
        self.output("creating directory " + path)
        os.mkdir(path)
        return False

    def optimizeDirectory(self, path, callback, extension = "", minimize = False):
        """optimizes a directory

        Arguments:
        path -- path to directory
        callback -- function to run the file through
        extension -- extension to search for in the directory
        minimize -- whether or not we should minimize the file contents (html)

        Returns:
        void

        """
        directory = path + "_opt"
        skip = self.prepareDirectory(directory)
        if skip is True:
            return

        for dir_file in Util.getFilesFromDir(path, extension):
            if Util.isDir(dir_file):
                self.optimizeSubdirectory(dir_file, callback, directory, extension, minimize)
                continue

            new_path = directory + "/" + Util.getFileName(dir_file)
            self.optimizeFile(dir_file, callback, minimize, new_path)

    def optimizeSubdirectory(self, path, callback, new_path, extension = "", minimize = False):
        """optimizes a subdirectory within a directory being optimized

        Arguments:
        path -- path to directory
        callback -- function to run the file through
        new_path -- path to optimized parent directory
        extension -- extension to search for in the directory
        minimize -- whether or not we should minimize the file contents (html)

        Returns:
        void

        """
        subdir_path = new_path + "/" + path.split("/").pop()
        skip = self.prepareDirectory(subdir_path)
        if skip is True:
            return

        for dir_file in Util.getFilesFromDir(path, extension):
            if Util.isDir(dir_file):
                self.optimizeSubdirectory(dir_file, callback, subdir_path, extension, minimize)
                continue

            new_file_path = subdir_path + "/" + Util.getFileName(dir_file)
            self.optimizeFile(dir_file, callback, minimize, new_file_path)

    def minimize(self, content):
        content = re.sub(r'\n', '', content)
        content = re.sub(r'\s\s+', '', content)
        content = re.sub(r'(<!--(?!\[if)(.*?)-->)', '', 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 <style type="text/css"></style>

        Arguments:
        html -- contents of file we are replacing

        Returns:
        list

        """
        return re.compile(r'\<style.*?\>(.*)\<\/style\>', re.DOTALL).findall(html)

    def replaceCss(self, css):
        """single call to handle replacing ids and classes

        Arguments:
        css -- contents of file to replace

        Returns:
        string

        """
        css = self.replaceCssFromDictionary(self.class_map, css)
        css = self.replaceCssFromDictionary(self.id_map, css)
        return css

    def replaceCssFromDictionary(self, dictionary, css):
        """replaces any instances of classes and ids based on a dictionary

        Arguments:
        dictionary -- map of classes or ids to replace
        css -- contents of css to replace

        Returns:
        string

        """
        # this really should be done better
        for key, value in dictionary.items():
            css = css.replace(key + "{", value + "{")
            css = css.replace(key + " {", value + " {")
            css = css.replace(key + "#", value + "#")
            css = css.replace(key + " #", value + " #")
            css = css.replace(key + ".", value + ".")
            css = css.replace(key + " .", value + " .")
            css = css.replace(key + ",", value + ",")
            css = css.replace(key + " ", value + " ")
            css = css.replace(key + ":", value + ":")
            # if key == ".svg":
                # print "replacing " + key + " with " + value

        return css

    def optimizeJavascriptBlocks(self, html):
        """rewrites javascript blocks that are part of an html file

        Arguments:
        html -- contents of file we are replacing

        Returns:
        string

        """
        matches = self.getJsBlocks(html)

        for match in matches:
            new_js = match
            if self.config.compress_html:
                matches = re.findall(r'((:?)\/\/.*?\n|\/\*.*?\*\/)', new_js, re.DOTALL)
                for single_match in matches:
                    if single_match[1] == ':':
                        continue
                    new_js = new_js.replace(single_match[0], '');
            new_js = self.replaceJavascript(new_js)
            html = html.replace(match, new_js)

        return html

    @staticmethod
    def getJsBlocks(html):
        """searches a file and returns all javascript blocks: <script type="text/javascript"></script>

        Arguments:
        html -- contents of file we are replacing

        Returns:
        list

        """
        return re.compile(r'\<script(?! src).*?\>(.*?)\<\/script\>', re.DOTALL).findall(html)

    def optimizeJavascript(self, path):
        """optimizes javascript for a specific file

        Arguments:
        path -- path to js file on disk that we are optimizing

        Returns:
        string -- contents to replace file with

        """
        js = Util.fileGetContents(path)
        return self.replaceJavascript(js)

    def replaceJavascript(self, js):
        """single call to handle replacing ids and classes

        Arguments:
        js -- contents of file to replace

        Returns:
        string

        """
        js = self.replaceJsFromDictionary(self.id_map, js)
        js = self.replaceJsFromDictionary(self.class_map, js)
        return js

    @staticmethod
    def getJsSelectors(js, config):
        """finds all js selectors within a js block

        Arguments:
        js -- contents of js file to search

        Returns:
        list

        """
        valid_selectors = "|".join(config.custom_selectors) + "|" + "|".join(config.id_selectors) + "|" + "|".join(config.class_selectors)
        valid_selectors = valid_selectors.replace('$', '\$')
        return re.findall(r'(' + valid_selectors + ')(\(([^<>]*?)\))', js, re.DOTALL)

    def replaceJsFromDictionary(self, dictionary, js):
        """replaces any instances of classes and ids based on a dictionary

        Arguments:
        dictionary -- map of classes or ids to replace
        js -- contents of javascript to replace

        Returns:
        string

        """
        for key, value in dictionary.items():
            blocks = self.getJsSelectors(js, self.config)
            for block in blocks:
                if key[0] == "#" and block[0] in self.config.class_selectors:
                    continue

                if key[0] == "." and block[0] in self.config.id_selectors:
                    continue

                old_selector = block[0] + block[1]

                # custom selectors
                if block[0] in self.config.custom_selectors:
                    new_selector = old_selector.replace(key + ".", value + ".")
                    new_selector = new_selector.replace(key + " ", value + " ")
                    new_selector = new_selector.replace(key + "\"", value + "\"")
                    new_selector = new_selector.replace(key + "\'", value + "\'")
                else:
                    new_selector = old_selector.replace("'" + key[1:] + "'", "'" + value[1:] + "'")
                    new_selector = new_selector.replace("\"" + key[1:] + "\"", "\"" + value[1:] + "\"")

                js = js.replace(old_selector, new_selector)

        return js


================================================
FILE: muncher/sizetracker.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, os, gzip
from util import Util

class SizeTracker(object):
    original_size = 0
    original_size_gzip = 0
    new_size = 0
    new_size_gzip = 0

    @staticmethod
    def addSize(path, new = False):

        # gzip the file to get that size
        gzip_path = path + '.gz'
        f_in = open(path, 'rb')
        f_out = gzip.open(gzip_path, 'wb')
        f_out.writelines(f_in)
        f_out.close()
        f_in.close()

        size = os.path.getsize(path)
        gzip_size = os.path.getsize(gzip_path)

        if new is False:
            SizeTracker.original_size += size
            SizeTracker.original_size_gzip += gzip_size
        else:
            SizeTracker.new_size += size
            SizeTracker.new_size_gzip += gzip_size

        Util.unlink(gzip_path)

    @staticmethod
    def trackFile(path, new_path):
        SizeTracker.addSize(path)
        SizeTracker.addSize(new_path, True)

    @staticmethod
    def getSize(bytes):
        if bytes < 1024:
            return str(bytes) + " bytes"

        kb = float(bytes) / 1024
        kb = round(kb, 2)
        return str(kb) + " KB"

    @staticmethod
    def savings():
        percent = 100 - (float(SizeTracker.new_size) / float(SizeTracker.original_size)) * 100
        gzip_percent = 100 - (float(SizeTracker.new_size_gzip) / float(SizeTracker.original_size_gzip)) * 100

        string = "\noriginal size:   " + SizeTracker.getSize(SizeTracker.original_size) + " (" + SizeTracker.getSize(SizeTracker.original_size_gzip) + " gzipped)"
        string += "\nmunched size:    " + SizeTracker.getSize(SizeTracker.new_size) + " (" + SizeTracker.getSize(SizeTracker.new_size_gzip) + " gzipped)"
        string += "\n                 saved " + str(round(percent, 2)) + "% off the original size (" + str(round(gzip_percent, 2)) + "% off the gzipped size)\n"
        return string


================================================
FILE: muncher/util.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 os, shutil, glob

class Util:
    """collection of various utility functions"""
    @staticmethod
    def fileExists(path):
        """determines if a file exists

        Arguments:
        path -- path to file on disk

        Returns:
        bool

        """
        return os.path.isfile(path)

    @staticmethod
    def isDir(path):
        """determines if a path is a directory

        Arguments:
        path -- path on disk

        Returns:
        bool

        """
        return os.path.isdir(path)

    @staticmethod
    def getFilesFromDir(path, extension = ""):
        path = path + "/*"

        if not extension == "":
            path = path + "." + extension.lstrip(".")

        return glob.glob(path)

    @staticmethod
    def dump(obj):
        """displays an object as a string for debugging

        Arguments:
        obj -- object

        Returns:
        string

        """
        for attr in dir(obj):
            print "obj.%s = %s" % (attr, getattr(obj, attr))

    @staticmethod
    def getExtension(path):
        """gets the extension from a file

        Arguments:
        path -- string of the file name

        Returns:
        string

        """
        return path.split(".").pop()

    @staticmethod
    def prependExtension(ext, path):
        current_ext = Util.getExtension(path)
        return path.replace("." + current_ext, "." + ext + "." + current_ext)

    @staticmethod
    def getBasePath(path):
        """gets the base directory one level up from the current path

        Arguments:
        path -- path to file or directory

        Returns:
        string

        """
        bits = path.split("/")
        last_bit = bits.pop()
        return "/".join(bits)
        # return "/".join(bits).rstrip(last_bit)

    @staticmethod
    def getFileName(path):
        return path.replace(Util.getBasePath(path), "").lstrip("/")

    @staticmethod
    def unlink(path):
        """deletes a file on disk

        Arguments:
        path -- path to file on disk

        Returns:
        void

        """
        if Util.fileExists(path):
            os.unlink(path)

    @staticmethod
    def unlinkDir(path):
        """removes an entire directory on disk

        Arguments:
        path -- path to directory to remove

        Returns:
        void

        """
        try:
            shutil.rmtree(path)
        except:
            pass

    @staticmethod
    def fileGetContents(path):
        """gets the contents of a file

        Arguments:
        path -- path to file on disk

        Returns:
        string

        """
        if not Util.fileExists(path):
            print "file does not exist at path " + path
            print "skipping"
        file = open(path, "r")
        contents = file.read()
        file.close()
        return contents

    @staticmethod
    def filePutContents(path, contents):
        """puts contents into a file

        Arguments:
        path -- path to file to write to
        contents -- contents to put into file

        Returns:
        void

        """
        file = open(path, "w")
        file.write(contents)
        file.close()

    @staticmethod
    def keyInTupleList(key, tuple_list):
        """checks a list of tuples for the given key"""
        for tuple in tuple_list:
            if tuple[0] == key:
                return True
        return False


================================================
FILE: muncher/varfactory.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 math

class VarFactory:
    """class to keep multiple counters and turn numeric counters into alphabetical ones"""
    types = {}
    letters = map(chr, range(97, 123))

    @staticmethod
    def getNext(type):
        """gets the next letter name based on counter name

        Arguments:
        type -- name of counter we want the next value for

        Returns:
        string

        """
        i = VarFactory.getVersion(type)
        return VarFactory.getSmallName(i)

    @staticmethod
    def getVersion(type):
        """gets the next number in the counter for this type

        Arguments:
        type -- name of counter we are incrementing

        Resturns:
        int

        """
        if not type in VarFactory.types:
            VarFactory.types[type] = 0
            return 0

        VarFactory.types[type] += 1

        return VarFactory.types[type]

    @staticmethod
    def getSmallName(index):
        """gets a letter index based on the numeric index

        Arguments:
        index -- the number you are looking for

        Returns:
        string

        """
        # total number of combinations for this index size
        combinations = 0
        letters = 0
        while (combinations + (((letters - 1) * 26) - 1) < index):
            letters += 1
            combinations = int(math.pow(len(VarFactory.letters), letters))

        if (index > 701):
            raise Exception("until my math skillz get better we can only support 702 possibilities!")

        a = int(index) + 1

        if a < 27:
            return chr(a + 96)

        b = 0
        while a > 26:
            b += 1
            a = a - 26

        b = chr(b + 96)
        a = chr(a + 96)
        return b + a


================================================
FILE: setup.py
================================================
#!/usr/bin/env python

from setuptools import setup

setup(name='htmlmuncher',
    version='1.0',
    description='Utility that rewrites CSS, HTML, and JavaScript files in order to save bytes and obfuscate your code.',
    author='Craig Campbell',
    author_email='iamcraigcampbell@gmail.com',
    url='http://htmlmuncher.com',
    packages=['muncher'],
    scripts=['munch']
)
Download .txt
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
Download .txt
SYMBOL INDEX (82 symbols across 5 files)

FILE: muncher/config.py
  class Config (line 19) | class Config(object):
    method __init__ (line 21) | def __init__(self):
    method getArgCount (line 43) | def getArgCount(self):
    method setIgnore (line 52) | def setIgnore(self, value):
    method setCustomSelectors (line 65) | def setCustomSelectors(self, value):
    method addClassSelectors (line 69) | def addClassSelectors(self, value):
    method addIdSelectors (line 73) | def addIdSelectors(self, value):
    method setCssFiles (line 77) | def setCssFiles(self, value):
    method setViewFiles (line 81) | def setViewFiles(self, value):
    method setJsFiles (line 85) | def setJsFiles(self, value):
    method setFramework (line 89) | def setFramework(self, name):
    method processArgs (line 98) | def processArgs(self):

FILE: muncher/muncher.py
  class Muncher (line 22) | class Muncher(object):
    method __init__ (line 23) | def __init__(self, config):
    method showUsage (line 37) | def showUsage():
    method run (line 82) | def run(self):
    method outputJsWarnings (line 125) | def outputJsWarnings(self):
    method output (line 128) | def output(self, text, verbose_only = True):
    method processCssDirectory (line 144) | def processCssDirectory(self, file):
    method processCss (line 164) | def processCss(self):
    method processViewDirectory (line 178) | def processViewDirectory(self, file):
    method processViews (line 198) | def processViews(self):
    method processJsDirectory (line 212) | def processJsDirectory(self, file):
    method processJs (line 231) | def processJs(self):
    method processView (line 245) | def processView(self, file):
    method processCssFile (line 255) | def processCssFile(self, path, inline = False):
    method processJsFile (line 277) | def processJsFile(self, path, inline = False):
    method processJsManifest (line 334) | def processJsManifest(self):
    method optimizeJsManifest (line 350) | def optimizeJsManifest(self):
    method processMaps (line 380) | def processMaps(self):
    method incrementIdCounter (line 415) | def incrementIdCounter(self, name):
    method incrementClassCounter (line 433) | def incrementClassCounter(self, name):
    method incrementCounter (line 451) | def incrementCounter(self, name):
    method addId (line 466) | def addId(self, id):
    method addIds (line 485) | def addIds(self, ids):
    method addClass (line 498) | def addClass(self, class_name):
    method addClasses (line 517) | def addClasses(self, classes):
    method optimizeFiles (line 530) | def optimizeFiles(self, paths, callback, extension = "", minimize = Fa...
    method optimizeFile (line 548) | def optimizeFile(self, file, callback, minimize = False, new_path = No...
    method prepareDirectory (line 573) | def prepareDirectory(self, path):
    method optimizeDirectory (line 585) | def optimizeDirectory(self, path, callback, extension = "", minimize =...
    method optimizeSubdirectory (line 611) | def optimizeSubdirectory(self, path, callback, new_path, extension = "...
    method minimize (line 638) | def minimize(self, content):
    method optimizeCss (line 644) | def optimizeCss(self, path):
    method optimizeHtml (line 657) | def optimizeHtml(self, path):
    method replaceHtml (line 677) | def replaceHtml(self, html):
    method replaceHtmlIds (line 691) | def replaceHtmlIds(self, html):
    method replaceClassBlock (line 708) | def replaceClassBlock(self, class_block, key, value):
    method replaceHtmlClasses (line 734) | def replaceHtmlClasses(self, html):
    method optimizeCssBlocks (line 754) | def optimizeCssBlocks(self, html):
    method getCssBlocks (line 776) | def getCssBlocks(html):
    method replaceCss (line 788) | def replaceCss(self, css):
    method replaceCssFromDictionary (line 802) | def replaceCssFromDictionary(self, dictionary, css):
    method optimizeJavascriptBlocks (line 829) | def optimizeJavascriptBlocks(self, html):
    method getJsBlocks (line 855) | def getJsBlocks(html):
    method optimizeJavascript (line 867) | def optimizeJavascript(self, path):
    method replaceJavascript (line 880) | def replaceJavascript(self, js):
    method getJsSelectors (line 895) | def getJsSelectors(js, config):
    method replaceJsFromDictionary (line 909) | def replaceJsFromDictionary(self, dictionary, js):

FILE: muncher/sizetracker.py
  class SizeTracker (line 19) | class SizeTracker(object):
    method addSize (line 26) | def addSize(path, new = False):
    method trackFile (line 49) | def trackFile(path, new_path):
    method getSize (line 54) | def getSize(bytes):
    method savings (line 63) | def savings():

FILE: muncher/util.py
  class Util (line 18) | class Util:
    method fileExists (line 21) | def fileExists(path):
    method isDir (line 34) | def isDir(path):
    method getFilesFromDir (line 47) | def getFilesFromDir(path, extension = ""):
    method dump (line 56) | def dump(obj):
    method getExtension (line 70) | def getExtension(path):
    method prependExtension (line 83) | def prependExtension(ext, path):
    method getBasePath (line 88) | def getBasePath(path):
    method getFileName (line 104) | def getFileName(path):
    method unlink (line 108) | def unlink(path):
    method unlinkDir (line 122) | def unlinkDir(path):
    method fileGetContents (line 138) | def fileGetContents(path):
    method filePutContents (line 157) | def filePutContents(path, contents):
    method keyInTupleList (line 173) | def keyInTupleList(key, tuple_list):

FILE: muncher/varfactory.py
  class VarFactory (line 18) | class VarFactory:
    method getNext (line 24) | def getNext(type):
    method getVersion (line 38) | def getVersion(type):
    method getSmallName (line 57) | def getSmallName(index):
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (57K chars).
[
  {
    "path": ".gitignore",
    "chars": 76,
    "preview": "*.pyc\n*.opt.html\n*.opt.js\n*.opt.css\ndemo/css_opt\ndemo/js_opt\ndemo/views_opt\n"
  },
  {
    "path": "README",
    "chars": 1461,
    "preview": "--------------\n  ABOUT\n--------------\n\nHTML Muncher is a Python utility that rewrites CSS, HTML, and JavaScript files in"
  },
  {
    "path": "demo/css/stylesheet1.css",
    "chars": 462,
    "preview": "body {\n    font-family: helvetica;\n}\n\nh1 {\n    text-align: center;\n    font-size: 2em;\n}\n\n.red {\n    color: red;\n}\n\n.blu"
  },
  {
    "path": "demo/css/stylesheet2.css",
    "chars": 216,
    "preview": ".file2 #special {\n    font-size: 1.5em;\n    color: #F737FF;\n}\n\n.file2 #special2 {\n    letter-spacing: 0;\n}\n\n.box {\n    b"
  },
  {
    "path": "demo/js/test.js",
    "chars": 868,
    "preview": "$ = {\n    qs: function(query) {\n        return document.querySelector(query);\n    }\n};\n\nwindow.onload = function()\n{\n   "
  },
  {
    "path": "demo/single-file/view-with-inline-styles.html",
    "chars": 2508,
    "preview": "<html>\n<head>\n    <title>view with inline styles</title>\n    <style type=\"text/css\">\n        .content {\n            padd"
  },
  {
    "path": "demo/views/view1.html",
    "chars": 1664,
    "preview": "<html>\n<head>\n    <title>test page 1 changed</title>\n    <link href=\"../css/stylesheet1.css\" rel=\"stylesheet\" type=\"text"
  },
  {
    "path": "demo/views/view2.html",
    "chars": 1339,
    "preview": "<html>\n<head>\n    <title>test page 2</title>\n    <link href=\"../css/stylesheet1.css\" rel=\"stylesheet\" type=\"text/css\" />"
  },
  {
    "path": "munch",
    "chars": 759,
    "preview": "#!/usr/bin/env python\n\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "muncher/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "muncher/config.py",
    "chars": 4863,
    "preview": "#!/usr/bin/env python\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
  },
  {
    "path": "muncher/muncher.py",
    "chars": 29458,
    "preview": "#!/usr/bin/env python\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
  },
  {
    "path": "muncher/sizetracker.py",
    "chars": 2462,
    "preview": "#!/usr/bin/env python\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
  },
  {
    "path": "muncher/util.py",
    "chars": 3990,
    "preview": "#!/usr/bin/env python\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
  },
  {
    "path": "muncher/varfactory.py",
    "chars": 2328,
    "preview": "#!/usr/bin/env python\n# Copyright 2011 Craig Campbell\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
  },
  {
    "path": "setup.py",
    "chars": 379,
    "preview": "#!/usr/bin/env python\n\nfrom setuptools import setup\n\nsetup(name='htmlmuncher',\n    version='1.0',\n    description='Utili"
  }
]

About this extraction

This page contains the full source code of the ccampbell/html-muncher GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (51.6 KB), approximately 12.1k tokens, and a symbol index with 82 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!