#
# Mozilla Extension Generator
# Copyright 2007 Einar Egilsson
#
# See http://tech.einaregilsson.com/2007/08/01/mozilla-extension-generator/ for more info.
#

import os, sys, re, base64        

#Constants
#Fill these out so you won't get prompted for them everytime:
YOUR_NAME       = ''
YOUR_DOMAIN     = '' # if filled out the extension guid will be of the form extensionname@yourdomain.com
FIREFOX_MIN     = '2.0'
FIREFOX_MAX     = '2.0.0.*'
THUNDERBIRD_MIN = '2.0'
THUNDERBIRD_MAX = '2.0.0.*'
INITIAL_VERSION = '0.9'


def generate():

    print 'Mozilla Extension Generator'
    print 'Copyright (c) 2007 Einar Egilsson (http://tech.einaregilsson.com)'
    print ''

    sections = {}
    exclude_sections = []

    replacements = {}
    for keyword, prompt_text in repl_vars:
        replacements[keyword] = prompt(prompt_text)

    answer = ''
    while not answer.lower() in ['f', 't']:
        answer = prompt('Firefox or Thunderbird (f/t) ')

    sections['firefox'] = answer.lower() == 'f'
    sections['thunderbird'] = answer.lower() == 't'

    for keyword, prompt_text in section_vars:
        answer = ''
        while not answer.lower() in ['y', 'n']:
            answer = prompt('Include %s ? (y/n)' % prompt_text)

        sections[keyword] = answer.lower() == 'y'

    
    replacements['author'] = YOUR_NAME or prompt('Author name')
    
    extname = replacements['name'].lower()
    replacements['lname'] = extname

    replacements['lib'] = replacements['name'] + 'Lib'
    replacements['llib'] = replacements['lib'].lower()

    if YOUR_DOMAIN:
        replacements['guid'] = replacements['lname'] + '@' + YOUR_DOMAIN
    else:
        replacements['guid'] = prompt('Guid') 


    replacements['fmin'] = FIREFOX_MIN
    replacements['fmax'] = FIREFOX_MAX
    replacements['tmin'] = THUNDERBIRD_MIN
    replacements['tmax'] = THUNDERBIRD_MAX

    replacements['version'] = INITIAL_VERSION

    if sections['menuitem']:
        replacements['menuName'] = prompt('Menu item label')
        replacements['menuAccessKey'] = prompt('Menu item access key')

    if sections['contextmenu']:
        replacements['contextName'] = prompt('Contextmenu item label')
        replacements['contextAccessKey'] = prompt('Contextmenu item access key')


    #Make all dirs needed
    os.makedirs(extname)
    for d in dirs:
        os.makedirs(os.path.join(extname, d))


    print '\nCreating files...\n'
    #Proccess each file
    for filename, filecontent in files:

        is_img = filename[-3:] == 'png'

        #Include or exclude sections of code
        exclude_pattern = r'\[%s].*?\[/%s]\s*\n'
        include_pattern = r'\[/?%s]\s*\n'

        for key in sections:
            if sections[key]:
                pattern = include_pattern % key
            else:
                pattern = exclude_pattern % (key, key)

            regex = re.compile(pattern, re.DOTALL)
            filecontent = re.sub(regex, '', filecontent)

        #Simple keyword replacements
        filename = filename % replacements
        if not is_img:
            filecontent = filecontent % replacements

        filecontent = filecontent.strip()

        #The whole file might be wrapped in a section, and if the section
        #was removed we don't want to create the file
        if filecontent:
            if is_img:
                filecontent = base64.b64decode(filecontent)

            path = extname + '/' + filename
            file = open(path, 'wb')
            print path
            file.write(filecontent)
            file.flush()
            file.close()

    print ''
    print 'Extension complete'


def prompt(prompt_text):
    sys.stdout.write(prompt_text + ': ')
    return raw_input()

#Directories to create
dirs = ['chrome/content', 'chrome/locale/en-US', 'chrome/skin', 'defaults/preferences']

repl_vars = (
    ('prettyname',  'Extension name'),
    ('name',        'Extension code name'),
    ('descr',       'Extension description'),
)

section_vars = (
    ('menuitem',    'Menu item'),
    ('contextmenu', 'Context menu'),
)


#Below here are only file templates
files = (

('install.rdf',
"""
<?xml version="1.0" encoding="UTF-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>%(guid)s</em:id>
    <em:name>%(prettyname)s</em:name>
    <em:version>%(version)s</em:version>
    <em:creator>%(author)s</em:creator>
    <em:description>%(descr)s</em:description>
    <!-- <em:homepageURL></em:homepageURL> -->
    <!-- <em:aboutURL>chrome://%(lname)s/content/about.xul</em:aboutURL> -->
    <em:iconURL>chrome://%(lname)s/content/%(lname)s.png</em:iconURL>
    <em:targetApplication>
      <Description>
[firefox]
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->
        <em:minVersion>%(fmin)s</em:minVersion>
        <em:maxVersion>%(fmax)s</em:maxVersion>
[/firefox]
[thunderbird]
        <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id> <!-- thunderbird -->
        <em:minVersion>%(tmin)s</em:minVersion>
        <em:maxVersion>%(tmax)s</em:maxVersion>
[/thunderbird]
      </Description>
    </em:targetApplication>
  </Description>
</RDF>
"""),

('chrome.manifest',
"""
content %(lname)s file:chrome/content/
locale  %(lname)s en-US   file:chrome/locale/en-US/
skin    %(lname)s classic/1.0 file:chrome/skin/
[thunderbird]
overlay chrome://messenger/content/messenger.xul    chrome://%(lname)s/content/overlay.xul
[/thunderbird]
[firefox]
overlay chrome://browser/content/browser.xul    chrome://%(lname)s/content/overlay.xul
[/firefox]
"""),

('defaults/preferences/%(lname)s.js',
"""
pref("extensions.%(lname)s.debug", false);

// See http://kb.mozillazine.org/Localize_extension_descriptions
pref("extensions.%(guid)s.description", "chrome://%(lname)s/locale/%(lname)s.properties");
"""),

('chrome/content/%(lname)s.js',
"""
var %(name)s = {

    id          : "%(guid)s",
    name        : "%(name)s",
    initialized : false,
    strings     : null,

    onLoad : function(event) {
        try {

            // initialization code
            %(lib)s.initialize(this);

[contextmenu]
[thunderbird]
            $('threadPaneContext')
[/thunderbird]
[firefox]
            $('contentAreaContextMenu')
[/firefox]
                .addEventListener("popupshowing", function(e) { %(name)s.showContextMenu(e); }, false);
[/contextmenu]

            %(lib)s.debug("Initializing...");
            this.strings = document.getElementById("%(lname)s-strings");

            %(lib)s.debug("Finished initialization");
            this.initialized = true;

        } catch(e) {
            //Don't use %(lib)s because it's initialization might have failed.
            if (this.strings) {
                alert(this.strings.getString("initError") .$ (this.name) + "\\n\\n" + e);
            } else {
                alert(e);
            }
        }
    },

    onUnload : function(event) {
        //Clean up here
        %(lib)s.debug("Finished cleanup");
    },

[contextmenu]
    showContextMenu : function(event) {
[thunderbird]
        $("%(lname)s-context").hidden = (GetNumSelectedMessages() > 0);
[/thunderbird]
[firefox]
        $("%(lname)s-context").hidden = gContextMenu.onImage;
[/firefox]
    },

    onContextMenuCommand: function(event) {
        //Do your thing
        alert("Context menu command");
    },

[/contextmenu]
[menuitem]
    onMenuItemCommand: function(event) {
        alert("Menu item command");
    },

[/menuitem]

};

window.addEventListener("load", function(event) { %(name)s.onLoad(event); }, false);
window.addEventListener("unload", function(event) { %(name)s.onUnload(event); }, false);
"""),

('chrome/content/overlay.xul',
"""
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="chrome://%(lname)s/skin/overlay.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://%(lname)s/locale/%(lname)s.dtd">
<overlay id="%(lname)s-overlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <script src="%(llib)s.js"/>
  <script src="%(lname)s.js"/>

  <stringbundleset id="stringbundleset">
    <stringbundle id="%(lname)s-strings" src="chrome://%(lname)s/locale/%(lname)s.properties"/>
  </stringbundleset>

[firefox]
[menuitem]
  <menupopup id="menu_ToolsPopup">
    <menuitem id="%(lname)s-menuitem" label="&%(name)sMenuItem.label;"
              accesskey="&%(name)sMenuItem.accesskey;"
              oncommand="%(name)s.onMenuItemCommand(event);"/>
  </menupopup>
[/menuitem]
[contextmenu]
  <popup id="contentAreaContextMenu">
    <menuitem id="%(lname)s-context" label="&%(name)sContext.label;"
              accesskey="&%(name)sContext.accesskey;"
              insertafter="context-stop"
              oncommand="%(name)s.onContextMenuCommand(event)"/>
  </popup>
[/contextmenu]
[/firefox]
[thunderbird]
[menuitem]
  <menupopup id="taskPopup">
    <menuitem id="%(lname)s-menuitem" label="&%(name)sMenuItem.label;"
              accesskey="&%(name)sMenuItem.accesskey;"
              oncommand="%(name)s.onMenuItemCommand(event);"/>
  </menupopup>
[/menuitem]
[contextmenu]
  <popup id="threadPaneContext">
    <menuitem id="%(lname)s-context" label="&%(name)sContext.label;"
              accesskey="&%(name)sContext.accesskey;"
              oncommand="%(name)s.onContextMenuCommand(event)"/>
  </popup>
[/contextmenu]
[/thunderbird]
</overlay>
"""),

('chrome/skin/overlay.css',
"""
/* Include your overlay stylings here */
"""),

('chrome/content/%(lname)s.png',
"""
iVBORw0KGgoAAAANSUhEUgAAACIAAAAiCAIAAAC1JZyVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdB
TUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAFLElE
QVR42oSGgQ0AIAzCRAb/nyzjAduEgvMDAO8FOSxT06BaJO14vDeRE2eWJ4BYCNsBtIQJaC4T1GiYFRCz
gTyQKWCjwBTQZLB1rGALwPYA2QABhM8aoB+YgA4H2gL2CtBYJpgNzCxgX8B8AzYRZBGIgNkAhWC7AAKI
BbcdTGAfgL3CAg0xsPtZmFmZoWEFCh0WqFdYoeGD5AWwPWAeQABht4YZbDwz1BvMKAHFArUBEkaoABFi
IDvZ2dlgogABhG4NI8QfIAgKL1hkg0iYD+DRDYkTFhawmWzQYALZghJeYB5AALGgRTg4rJiZmOGhBKJZ
YZHBDLMAbCfYUBZ4cmKDxzvQF2DbwL5hB0kDBBALasJlZmKBeoQFmnQxA4oFEtMw30CcCzGXDe4Ldnhy
BomxAQQQ1BpgmgIbDwMwG6ApCkQDtTCzwBIvOIJBQtCEBTEa4iGY2RB/QGQAAogFkqggoQQJMUjegJiN
lPFYwDbBoh2SfKGhBQokSPICmw5OY+zQGAN5kYUNIICg2QKSdIHmbtq0yd7eHk9mcnZ2Auru6OgwMDDA
o6yzq4sdnnXYWAECiAWUmFhAWQPiIWAM4S8XgBELdCXQbfiVsbPD0xvIWwABBMl60LACsoiwBhQ4jEwE
lLFDkhkbtMgBCCAWVkj2g8U5sjWZGZlnz59lA2cNSEnCBkmlLKzAJANXNnvOnIcPHkCSLlAR0B8skGQM
TQwgEiCAWEDZDhxgkLIJOTTcPdz19PUgSQ8cf0wPHz68eeMG0DZkZYYG+kqKisxI4P37D2/evIaXbUAA
EEDgQhaSbsFlLbJvAgIC0IJi5cqV9+/dB7oQ2RoTE1M0ZefPn//06SOsnAHZBBBALEhFFSh08Ac60KVs
4ODArwyU9iGBC7WFFSCAYIU5rEhHDvRt27e/fvUKHhTAcHv29Ck7qEBkQ1Z2+fLlL1++QGskMPj85TPY
FhZoOcTCChBA4OwGLhaZQRkaJdBOHDt+/cY1FhZ4KQJyFrjYRQm0Bw8evHr5Cp7/oYUyLO+ygMs9gABi
gfuIDVx3IOsHlyJIhRMkh4ONQFEG1M8OKYoRxQMbcl3EwgoQQLCgY4GqQPZNbW0tZrgvWbLkyZMnyNZ4
eHpiKrt+/fq3b9+g7mdhAQgg9KqJYPYGl2HsBHMxC7wcB1cfAAEEjiWkGpagNZAQIkIZC7iRA03EAAHE
aGdnh2wNpHaFMYDuhjPZ4HHDCinXYPHFBmtkQKKchQXOgNa1QP8ABBCk8oPW30gMVrCZ7KxwG9hQWRDj
wXxo+EDCCBwTEDFgrmcF53pgQgYIIBaEqWwsyKYh6nGoKMx4RLOFHVypscJSD8IvLNAMwgKvvAACiIUd
6n+wbnZoTcSO1P6B5hoW5JCCF71IloCzPisspJhhVS+kLgYIIEipgGQoNKdg8Q1SrmNDTpwssKob6gdW
aIMR3pwApheAAGKBVD+wVg8bG7zJgFyrQ6INLAsvQaB2QAhmaNIFWwluCzHDG92gcgoggFigjmdDqoYQ
NrOxIbUiYI0AaMqCewIaI6D8AbaIGQHBDUpmYDMcIIDAQY6IYjZYXMDCDBYLrPCECC2V4AHGBm/+QNp0
sFYqMwuscQzMywABBC0KkaMBViqxonoF0vhDatxAynSIL1jhDS5QCIGtgTZdIbkVIIDASQCSJcH5AErB
Cj+UCEeyAt46hCYrZmi6ZUbUB6AWE7xQAAgghG/gOQLRpEMth1ASFbTlxgJvN0LaLKDaHlyfM6KWRgAB
BgDTG34EXY2OPQAAAABJRU5ErkJggg==
"""),

('chrome/content/%(llib)s.js',
"""
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *    MozLib  -  Utility functions for Firefox extensions
 *
 *    Einar Egilsson
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var %(lib)s = {

    _debug      : false,
    _ext        : null,
    _cout       : null,
    _prefBranch : null,
    version     : 0.2,

    initialize : function(extension) {
        this._ext = extension;
        this._cout = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
        this._debug = true;
        this._initPrefs();
    },

    debug : function(str) {
        if (this._ext.prefs.debug) {
            this._cout.logStringMessage("$1: $2" .$ (this._ext.name, str));
        }
    },


    debugObject : function(name, obj) {
        s = name + ': ';
        for (x in obj)
            s += "\\n\\t$1 : $2" .$ (x, obj[x]);
        this.debug(s);
    },

    //Adds all prefs to a prefs object on the extension object
    _initPrefs : function() {

        this._prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService)
                                .getBranch("extensions.$1." .$ (this._ext.id.split("@")[0]));
        this._ext.prefs = {};

        var list = this._prefBranch.getChildList("", {}).toString().split(",");

        for (i in list) {

            var name = list[i];

            var type = this._prefBranch.getPrefType(name);

            if (type == this._prefBranch.PREF_STRING) {
                this._ext.prefs[name] = this._prefBranch.getCharPref(name);
            } else if (type == this._prefBranch.PREF_INT)  {
                this._ext.prefs[name] = this._prefBranch.getIntPref(name);
            } else if (type == this._prefBranch.PREF_BOOL) {
                this._ext.prefs[name] = this._prefBranch.getBoolPref(name);
            }
        }
    },


    getCharPref : function(branch) {
        return this._prefBranch.getCharPref(branch);
    },

    getBoolPref : function(branch) {
        return this._prefBranch.getBoolPref(branch);
    },

    getIntPref : function(branch) {
        return this._prefBranch.getIntPref(branch);
    },

    getExtensionFolder : function() {
        return Cc["@mozilla.org/extensions/manager;1"]
                .getService(Ci.nsIExtensionManager)
                    .getInstallLocation(this._ext.id)
                        .getItemLocation(this._ext.id);

    },

    getEnvVar : function(name) {
        return Cc["@mozilla.org/process/environment;1"]
                .getService(Ci.nsIEnvironment)
                    .get(name);

    },

    setEnvVar : function(name, value) {
        return Cc["@mozilla.org/process/environment;1"]
                .getService(Ci.nsIEnvironment)
                    .set(name, value);

    },

    msgBox : function(title, text) {
        Cc["@mozilla.org/embedcomp/prompt-service;1"]
            .getService(Ci.nsIPromptService)
                .alert(window, title, text);
    },

    //Converts a chrome path to a local file path. Note that the
    //file specified at the end of the chrome path does not have
    //to exist.
    chromeToPath : function(path) {
        var rv;
        var ios = Cc["@mozilla.org/network/io-service;1"].createInstance(Ci.nsIIOService);
        var uri = ios.newURI(path, 'UTF-8', null);
        var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].createInstance(Ci.nsIChromeRegistry);
        return cr.convertChromeURL(uri);
        return decodeURI(rv.spec.substr("file:///".length).replace(/\//g, "\\\\"));
    },

    //Saves 'content' to file 'filepath'. Note that filepath needs to
    //be a real file path, not a chrome path.
    saveToFile : function(filepath, content) {
        var file = this.getFile(filepath);

        if (!file.exists()) {
            file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 420);
        }
        var outputStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);

        outputStream.init( file, 0x04 | 0x08 | 0x20, 420, 0 );
        var result = outputStream.write( content, content.length );
        outputStream.close();
    },

    startProcess: function(filename, args) {

        var file = this.getFile(filename);

        args = args ? args : [];

        if (file.exists()) {
            var nsIProcess = Cc["@mozilla.org/process/util;1"].getService(Ci.nsIProcess);
            nsIProcess.init(file);
            nsIProcess.run(false, args, args.length);
        } else {
            throw Error("File '$1' does not exist!" .$ (filename));
        }

   },

    //Simulates a double click on the file in question
    launchFile : function(filepath) {
        var f = this.getFile(filepath);
        f.launch();
    },


    //Gets a local file reference, return the interface nsILocalFile.
    getFile : function(filepath) {
        var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
        f.initWithPath(filepath);
        return f;
    },

    //Returns all elements that match the query sent in. The root used
    //in the query is the window.content.document, so this will only
    //work for html content.
    xpath : function(doc, query) {
        return doc.evaluate(query, doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    }



};

//************** Some prototype enhancements ************** //

if (!window.Cc) window.Cc = Components.classes;
if (!window.Ci) window.Ci = Components.interfaces;

//Returns true if the string contains the substring str.
String.prototype.contains = function (str) {
    return this.indexOf(str) != -1;
};

String.prototype.trim = function() {
    return this.replace(/^\s*|\s*$/gi, '');
};

String.prototype.startsWith = function(s) {
    return (this.length >= s.length && this.substr(0, s.length) == s);
};

String.prototype.endsWith = function(s) {
    return (this.length >= s.length && this.substr(this.length - s.length) == s);
};

//Inserts the arguments into the string. E.g. if
//the string is "Hello $1" then "Hello $1" .$ ('johnny')
//would return "Hello johnny"
String.prototype.$ = function() {

  s = this;
  if (arguments.length == 1 && arguments[0].constructor == Object) {
    for (var key in arguments[0]) {
      s = s.replace(new RegExp("\\\\$" + key, "g"), arguments[0][key]);
    }
  } else {
    for (var i = 0; i < arguments.length; i++) {
      s = s.replace(new RegExp("\\\\$" + (i+1), "g"), arguments[i]);
    }
  }
  return s;
};

function $(id) {
    return document.getElementById(id);
}"""),

('chrome/locale/en-US/%(lname)s.dtd',
"""
[menuitem]
<!ENTITY %(name)sMenuItem.label "%(menuName)s">
<!ENTITY %(name)sMenuItem.accesskey "%(menuAccessKey)s">
[/menuitem]
[contextmenu]
<!ENTITY %(name)sContext.label "%(contextName)s">
<!ENTITY %(name)sContext.accesskey "%(contextAccessKey)s">
[/contextmenu]
"""),

('chrome/locale/en-US/%(lname)s.properties',
"""
initError=Failed to initialize $1.
extensions.%(guid)s.description=%(descr)s
""")

)

generate()