laurent@371: #!/usr/bin/env python
laurent@371: 
laurent@371: import sys
laurent@371: import os
laurent@371: import shutil
laurent@371: from copy import copy
laurent@371: from os.path import join, dirname, basename, abspath, split, isfile, isdir
laurent@371: from optparse import OptionParser
laurent@371: import pyjs
laurent@371: from cStringIO import StringIO
laurent@371: try:
laurent@371:     # Python 2.5 and above
laurent@371:     from hashlib import md5
laurent@371: except:
laurent@371:     import md5
laurent@371: import re
laurent@371: 
laurent@371: usage = """
laurent@371:   usage: %prog [options] <application module name or path>
laurent@371: 
laurent@371: This is the command line builder for the pyjamas project, which can
laurent@371: be used to build Ajax applications from Python.
laurent@371: For more information, see the website at http://pyjs.org/
laurent@371: """
laurent@371: 
laurent@371: # GWT1.2 Impl  | GWT1.2 Output         | Pyjamas 0.2 Platform | Pyjamas 0.2 Output
laurent@371: # -------------+-----------------------+----------------------+----------------------
laurent@371: # IE6          | ie6                   | IE6                  | ie6
laurent@371: # Opera        | opera                 | Opera                | opera
laurent@371: # Safari       | safari                | Safari               | safari
laurent@371: # --           | gecko1_8              | Mozilla              | mozilla
laurent@371: # --           | gecko                 | OldMoz               | oldmoz
laurent@371: # Standard     | all                   | (default code)       | all
laurent@371: # Mozilla      | gecko1_8, gecko       | --                   | --
laurent@371: # Old          | safari, gecko, opera  | --                   | --
laurent@371: 
laurent@371: version = "%prog pyjamas version 2006-08-19"
laurent@371: 
laurent@371: # these names in lowercase need match the strings
laurent@371: # returned by "provider$user.agent" in order to be selected corretly
laurent@371: app_platforms = ['IE6', 'Opera', 'OldMoz', 'Safari', 'Mozilla']
laurent@371: 
laurent@371: # usually defaults to e.g. /usr/share/pyjamas
laurent@371: _data_dir = os.path.join(pyjs.prefix, "share/pyjamas")
laurent@371: 
laurent@371: 
laurent@371: # .cache.html files produces look like this
laurent@371: CACHE_HTML_PAT=re.compile('^[a-z]*.[0-9a-f]{32}\.cache\.html$')
laurent@371: 
laurent@371: # ok these are the three "default" library directories, containing
laurent@371: # the builtins (str, List, Dict, ord, round, len, range etc.)
laurent@371: # the main pyjamas libraries (pyjamas.ui, pyjamas.Window etc.)
laurent@371: # and the contributed addons
laurent@371: 
laurent@371: for p in ["library/builtins",
laurent@371:           "library",
laurent@371:           "addons"]:
laurent@371:     p = os.path.join(_data_dir, p)
laurent@371:     if os.path.isdir(p):
laurent@371:         pyjs.path.append(p)
laurent@371: 
laurent@371: 
laurent@371: def read_boilerplate(data_dir, filename):
laurent@371:     return open(join(data_dir, "builder/boilerplate", filename)).read()
laurent@371: 
laurent@371: def copy_boilerplate(data_dir, filename, output_dir):
laurent@371:     filename = join(data_dir, "builder/boilerplate", filename)
laurent@371:     shutil.copy(filename, output_dir)
laurent@371: 
laurent@371: 
laurent@371: # taken and modified from python2.4
laurent@371: def copytree_exists(src, dst, symlinks=False):
laurent@371:     if not os.path.exists(src):
laurent@371:         return
laurent@371: 
laurent@371:     names = os.listdir(src)
laurent@371:     try:
laurent@371:         os.mkdir(dst)
laurent@371:     except:
laurent@371:         pass
laurent@371: 
laurent@371:     errors = []
laurent@371:     for name in names:
laurent@371:         if name.startswith('CVS'):
laurent@371:             continue
laurent@371:         if name.startswith('.git'):
laurent@371:             continue
laurent@371:         if name.startswith('.svn'):
laurent@371:             continue
laurent@371: 
laurent@371:         srcname = os.path.join(src, name)
laurent@371:         dstname = os.path.join(dst, name)
laurent@371:         try:
laurent@371:             if symlinks and os.path.islink(srcname):
laurent@371:                 linkto = os.readlink(srcname)
laurent@371:                 os.symlink(linkto, dstname)
laurent@371:             elif isdir(srcname):
laurent@371:                 copytree_exists(srcname, dstname, symlinks)
laurent@371:             else:
laurent@371:                 shutil.copy2(srcname, dstname)
laurent@371:         except (IOError, os.error), why:
laurent@371:             errors.append((srcname, dstname, why))
laurent@371:     if errors:
laurent@371:         print errors
laurent@371: 
laurent@371: def check_html_file(source_file, dest_path):
laurent@371:     """ Checks if a base HTML-file is available in the PyJamas
laurent@371:         output directory.
laurent@371:         If the HTML-file isn't available, it will be created.
laurent@371: 
laurent@371:         If a CSS-file with the same name is available
laurent@371:         in the output directory, a reference to this CSS-file
laurent@371:         is included.
laurent@371: 
laurent@371:         If no CSS-file is found, this function will look for a special
laurent@371:         CSS-file in the output directory, with the name
laurent@371:         "pyjamas_default.css", and if found it will be referenced
laurent@371:         in the generated HTML-file.
laurent@371: 
laurent@371:         [thank you to stef mientki for contributing this function]
laurent@371:     """
laurent@371: 
laurent@371:     base_html = """\
laurent@371: <html>
laurent@371:     <!-- auto-generated html - you should consider editing and
laurent@371:          adapting this to suit your requirements
laurent@371:      -->
laurent@371:     <head>
laurent@371:       <meta name="pygwt:module" content="%(modulename)s">
laurent@371:       %(css)s
laurent@371:       <title>%(title)s</title>
laurent@371:     </head>
laurent@371:     <body bgcolor="white">
laurent@371:       <script language="javascript" src="pygwt.js"></script>
laurent@371:     </body>
laurent@371: </html>
laurent@371: """
laurent@371: 
laurent@371:     filename = os.path.split    ( source_file )[1]
laurent@371:     mod_name = os.path.splitext ( filename    )[0]
laurent@371:     file_name = os.path.join     ( dest_path, mod_name + '.html' )
laurent@371: 
laurent@371:     # if html file in output directory exists, leave it alone.
laurent@371:     if os.path.exists ( file_name ):
laurent@371:         return 0
laurent@371: 
laurent@371:     if os.path.exists (
laurent@371:         os.path.join ( dest_path, mod_name + '.css' ) ) :
laurent@371:         css = "<link rel='stylesheet' href='" + mod_name + ".css'>"
laurent@371:     elif os.path.exists (
laurent@371:         os.path.join ( dest_path, 'pyjamas_default.css' ) ) :
laurent@371:         css = "<link rel='stylesheet' href='pyjamas_default.css'>"
laurent@371: 
laurent@371:     else:
laurent@371:         css = ''
laurent@371: 
laurent@371:     title = 'PyJamas Auto-Generated HTML file ' + mod_name
laurent@371: 
laurent@371:     base_html = base_html % {'modulename': mod_name, 'title': title, 'css': css}
laurent@371: 
laurent@371:     fh = open (file_name, 'w')
laurent@371:     fh.write  (base_html)
laurent@371:     fh.close  ()
laurent@371: 
laurent@371:     return 1
laurent@371: 
laurent@371: 
laurent@371: def build(app_name, output, js_includes=(), debug=False, dynamic=0,
laurent@371:           data_dir=None, cache_buster=False, optimize=False):
laurent@371: 
laurent@371:     # make sure the output directory is always created in the current working
laurent@371:     # directory or at the place given if it is an absolute path.
laurent@371:     output = os.path.abspath(output)
laurent@371:     msg = "Building '%(app_name)s' to output directory '%(output)s'" % locals()
laurent@371:     if debug:
laurent@371:         msg += " with debugging statements"
laurent@371:     print msg
laurent@371: 
laurent@371:     # check the output directory
laurent@371:     if os.path.exists(output) and not os.path.isdir(output):
laurent@371:         print >>sys.stderr, "Output destination %s exists and is not a directory" % output
laurent@371:         return
laurent@371:     if not os.path.isdir(output):
laurent@371:         try:
laurent@371:             print "Creating output directory"
laurent@371:             os.mkdir(output)
laurent@371:         except StandardError, e:
laurent@371:             print >>sys.stderr, "Exception creating output directory %s: %s" % (output, e)
laurent@371: 
laurent@371:     ## public dir
laurent@371:     for p in pyjs.path:
laurent@371:         pub_dir = join(p, 'public')
laurent@371:         if isdir(pub_dir):
laurent@371:             print "Copying: public directory of library %r" % p
laurent@371:             copytree_exists(pub_dir, output)
laurent@371: 
laurent@371:     ## AppName.html - can be in current or public directory
laurent@371:     html_input_filename = app_name + ".html"
laurent@371:     html_output_filename = join(output, basename(html_input_filename))
laurent@371:     if os.path.isfile(html_input_filename):
laurent@371:         if not os.path.isfile(html_output_filename) or \
laurent@371:                os.path.getmtime(html_input_filename) > \
laurent@371:                os.path.getmtime(html_output_filename):
laurent@371:             try:
laurent@371:                 shutil.copy(html_input_filename, html_output_filename)
laurent@371:             except:
laurent@371:                 print >>sys.stderr, "Warning: Missing module HTML file %s" % html_input_filename
laurent@371: 
laurent@371:             print "Copying: %(html_input_filename)s" % locals()
laurent@371: 
laurent@371:     if check_html_file(html_input_filename, output):
laurent@371:         print >>sys.stderr, "Warning: Module HTML file %s has been auto-generated" % html_input_filename
laurent@371: 
laurent@371:     ## pygwt.js
laurent@371: 
laurent@371:     print "Copying: pygwt.js"
laurent@371: 
laurent@371:     pygwt_js_template = read_boilerplate(data_dir, "pygwt.js")
laurent@371:     pygwt_js_output = open(join(output, "pygwt.js"), "w")
laurent@371: 
laurent@371:     print >>pygwt_js_output, pygwt_js_template
laurent@371: 
laurent@371:     pygwt_js_output.close()
laurent@371: 
laurent@371:     ## Images
laurent@371: 
laurent@371:     print "Copying: Images and History"
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_topleft_black.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_topright_black.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_bottomright_black.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_bottomleft_black.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_edge_black.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_topleft.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_topright.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_bottomright.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_bottomleft.png", output)
laurent@371:     copy_boilerplate(data_dir, "corner_dialog_edge.png", output)
laurent@371:     copy_boilerplate(data_dir, "tree_closed.gif", output)
laurent@371:     copy_boilerplate(data_dir, "tree_open.gif", output)
laurent@371:     copy_boilerplate(data_dir, "tree_white.gif", output)
laurent@371:     copy_boilerplate(data_dir, "history.html", output)
laurent@371: 
laurent@371: 
laurent@371:     ## all.cache.html
laurent@371:     app_files = generateAppFiles(data_dir, js_includes, app_name, debug,
laurent@371:                                  output, dynamic, cache_buster, optimize)
laurent@371: 
laurent@371:     ## AppName.nocache.html
laurent@371: 
laurent@371:     print "Creating: %(app_name)s.nocache.html" % locals()
laurent@371: 
laurent@371:     home_nocache_html_template = read_boilerplate(data_dir, "home.nocache.html")
laurent@371:     home_nocache_html_output = open(join(output, app_name + ".nocache.html"),
laurent@371:                                     "w")
laurent@371: 
laurent@371:     # the selector templ is added to the selectScript function
laurent@371:     select_tmpl = """O(["true","%s"],"%s");"""
laurent@371:     script_selectors = StringIO()
laurent@371: 
laurent@371:     for platform, file_prefix in app_files:
laurent@371:         print >> script_selectors, select_tmpl % (platform, file_prefix)
laurent@371: 
laurent@371:     print >>home_nocache_html_output, home_nocache_html_template % dict(
laurent@371:         app_name = app_name,
laurent@371:         script_selectors = script_selectors.getvalue(),
laurent@371:     )
laurent@371: 
laurent@371:     home_nocache_html_output.close()
laurent@371: 
laurent@371:     print "Done. You can run your app by opening '%(html_output_filename)s' in a browser" % locals()
laurent@371: 
laurent@371: 
laurent@371: def generateAppFiles(data_dir, js_includes, app_name, debug, output, dynamic,
laurent@371:                      cache_buster, optimize):
laurent@371: 
laurent@371:     all_cache_html_template = read_boilerplate(data_dir, "all.cache.html")
laurent@371:     mod_cache_html_template = read_boilerplate(data_dir, "mod.cache.html")
laurent@371: 
laurent@371:     # clean out the old ones first
laurent@371:     for name in os.listdir(output):
laurent@371:         if CACHE_HTML_PAT.match(name):
laurent@371:             p = join(output, name)
laurent@371:             print "Deleting existing app file %s" % p
laurent@371:             os.unlink(p)
laurent@371: 
laurent@371:     app_files = []
laurent@371:     tmpl = read_boilerplate(data_dir, "all.cache.html")
laurent@371:     parser = pyjs.PlatformParser("platform")
laurent@371:     app_headers = ''
laurent@371:     scripts = ['<script type="text/javascript" src="%s"></script>'%script \
laurent@371:                                                   for script in js_includes]
laurent@371:     app_body = '\n'.join(scripts)
laurent@371: 
laurent@371:     mod_code = {}
laurent@371:     mod_libs = {}
laurent@371:     modules = {}
laurent@371:     app_libs = {}
laurent@371:     early_app_libs = {}
laurent@371:     app_code = {}
laurent@371:     overrides = {}
laurent@371:     pover = {}
laurent@371:     app_modnames = {}
laurent@371:     mod_levels = {}
laurent@371: 
laurent@371:     # First, generate all the code.
laurent@371:     # Second, (dynamic only), post-analyse the places where modules
laurent@371:     # haven't changed
laurent@371:     # Third, write everything out.
laurent@371:     
laurent@371:     for platform in app_platforms:
laurent@371: 
laurent@371:         mod_code[platform] = {}
laurent@371:         mod_libs[platform] = {}
laurent@371:         modules[platform] = []
laurent@371:         pover[platform] = {}
laurent@371:         app_libs[platform] = ''
laurent@371:         early_app_libs[platform] = ''
laurent@371:         app_code[platform] = {}
laurent@371:         app_modnames[platform] = {}
laurent@371: 
laurent@371:         # Application.Platform.cache.html
laurent@371: 
laurent@371:         parser.setPlatform(platform)
laurent@371:         app_translator = pyjs.AppTranslator(
laurent@371:             parser=parser, dynamic=dynamic, optimize=optimize)
laurent@371:         early_app_libs[platform], appcode = \
laurent@371:                      app_translator.translate(None, is_app=False,
laurent@371:                                               debug=debug,
laurent@371:                                       library_modules=['dynamicajax.js',
laurent@371:                                                     '_pyjs.js', 'sys',
laurent@371:                                                      'pyjslib'])
laurent@371:         pover[platform].update(app_translator.overrides.items())
laurent@371:         for mname, name in app_translator.overrides.items():
laurent@371:             pd = overrides.setdefault(mname, {})
laurent@371:             pd[platform] = name
laurent@371: 
laurent@371:         print appcode
laurent@371:         #mod_code[platform][app_name] = appcode
laurent@371: 
laurent@371:         # platform.Module.cache.js 
laurent@371: 
laurent@371:         modules_done = ['pyjslib', 'sys', '_pyjs.js']
laurent@371:         #modules_to_do = [app_name] + app_translator.library_modules
laurent@371:         modules_to_do = [app_name] + app_translator.library_modules 
laurent@371: 
laurent@371:         dependencies = {}
laurent@371: 
laurent@371:         deps = map(pyjs.strip_py, modules_to_do)
laurent@371:         for d in deps:
laurent@371:             sublist = add_subdeps(dependencies, d)
laurent@371:             modules_to_do += sublist
laurent@371:         deps = uniquify(deps)
laurent@371:         #dependencies[app_name] = deps
laurent@371: 
laurent@371:         modules[platform] = modules_done + modules_to_do
laurent@371: 
laurent@371:         while modules_to_do:
laurent@371: 
laurent@371:             #print "modules to do", modules_to_do
laurent@371: 
laurent@371:             mn = modules_to_do.pop()
laurent@371:             mod_name = pyjs.strip_py(mn)
laurent@371: 
laurent@371:             if mod_name in modules_done:
laurent@371:                 continue
laurent@371: 
laurent@371:             modules_done.append(mod_name)
laurent@371: 
laurent@371:             mod_cache_name = "%s.%s.cache.js" % (platform.lower(), mod_name)
laurent@371: 
laurent@371:             parser.setPlatform(platform)
laurent@371:             mod_translator = pyjs.AppTranslator(parser=parser, optimize=optimize)
laurent@371:             mod_libs[platform][mod_name], mod_code[platform][mod_name] = \
laurent@371:                               mod_translator.translate(mod_name,
laurent@371:                                                   is_app=False,
laurent@371:                                                   debug=debug)
laurent@371:             pover[platform].update(mod_translator.overrides.items())
laurent@371:             for mname, name in mod_translator.overrides.items():
laurent@371:                 pd = overrides.setdefault(mname, {})
laurent@371:                 pd[platform] = name
laurent@371: 
laurent@371:             mods = mod_translator.library_modules
laurent@371:             modules_to_do += mods
laurent@371:             modules[platform] += mods
laurent@371: 
laurent@371:             deps = map(pyjs.strip_py, mods)
laurent@371:             sd = subdeps(mod_name)
laurent@371:             if len(sd) > 1:
laurent@371:                 deps += sd[:-1]
laurent@371:             while mod_name in deps:
laurent@371:                 deps.remove(mod_name)
laurent@371: 
laurent@371:             #print
laurent@371:             #print
laurent@371:             #print "modname preadd:", mod_name, deps
laurent@371:             #print
laurent@371:             #print
laurent@371:             for d in deps:
laurent@371:                 sublist = add_subdeps(dependencies, d)
laurent@371:                 modules_to_do += sublist
laurent@371:             modules_to_do += add_subdeps(dependencies, mod_name)
laurent@371:             #print "modname:", mod_name, deps
laurent@371:             deps = uniquify(deps)
laurent@371:             #print "modname:", mod_name, deps
laurent@371:             dependencies[mod_name] = deps
laurent@371:             
laurent@371:         # work out the dependency ordering of the modules
laurent@371:     
laurent@371:         mod_levels[platform] = make_deps(None, dependencies, modules_done)
laurent@371: 
laurent@371:     # now write everything out
laurent@371: 
laurent@371:     for platform in app_platforms:
laurent@371: 
laurent@371:         early_app_libs_ = early_app_libs[platform]
laurent@371:         app_libs_ = app_libs[platform]
laurent@371:         app_code_ = app_code[platform]
laurent@371:         #modules_ = filter_mods(app_name, modules[platform])
laurent@371:         mods = flattenlist(mod_levels[platform])
laurent@371:         mods.reverse()
laurent@371:         modules_ = filter_mods(None, mods)
laurent@371: 
laurent@371:         for mod_name in modules_:
laurent@371: 
laurent@371:             mod_code_ = mod_code[platform][mod_name]
laurent@371: 
laurent@371:             mod_name = pyjs.strip_py(mod_name)
laurent@371: 
laurent@371:             override_name = "%s.%s" % (platform.lower(), mod_name)
laurent@371:             if pover[platform].has_key(override_name):
laurent@371:                 mod_cache_name = "%s.cache.js" % (override_name)
laurent@371:             else:
laurent@371:                 mod_cache_name = "%s.cache.js" % (mod_name)
laurent@371: 
laurent@371:             print "Creating: " + mod_cache_name
laurent@371: 
laurent@371:             modlevels = make_deps(None, dependencies, dependencies[mod_name])
laurent@371: 
laurent@371:             modnames = []
laurent@371: 
laurent@371:             for md in modlevels:
laurent@371:                 mnames = map(lambda x: "'%s'" % x, md)
laurent@371:                 mnames = "new pyjslib.List([\n\t\t\t%s])" % ',\n\t\t\t'.join(mnames)
laurent@371:                 modnames.append(mnames)
laurent@371: 
laurent@371:             modnames.reverse()
laurent@371:             modnames = "new pyjslib.List([\n\t\t%s\n\t])" % ',\n\t\t'.join(modnames)
laurent@371: 
laurent@371:             # convert the overrides
laurent@371: 
laurent@371:             overnames = map(lambda x: "'%s': '%s'" % x, pover[platform].items())
laurent@371:             overnames = "new pyjslib.Dict({\n\t\t%s\n\t})" % ',\n\t\t'.join(overnames)
laurent@371: 
laurent@371:             if dynamic:
laurent@371:                 mod_cache_html_output = open(join(output, mod_cache_name), "w")
laurent@371:             else:
laurent@371:                 mod_cache_html_output = StringIO()
laurent@371: 
laurent@371:             print >>mod_cache_html_output, mod_cache_html_template % dict(
laurent@371:                 mod_name = mod_name,
laurent@371:                 app_name = app_name,
laurent@371:                 modnames = modnames,
laurent@371:                 overrides = overnames,
laurent@371:                 mod_libs = mod_libs[platform][mod_name],
laurent@371:                 dynamic = dynamic,
laurent@371:                 mod_code = mod_code_,
laurent@371:             )
laurent@371: 
laurent@371:             if dynamic:
laurent@371:                 mod_cache_html_output.close()
laurent@371:             else:
laurent@371:                 mod_cache_html_output.seek(0)
laurent@371:                 app_libs_ += mod_cache_html_output.read()
laurent@371: 
laurent@371:         # write out the dependency ordering of the modules
laurent@371:     
laurent@371:         app_modnames = []
laurent@371: 
laurent@371:         for md in mod_levels[platform]:
laurent@371:             mnames = map(lambda x: "'%s'" % x, md)
laurent@371:             mnames = "new pyjslib.List([\n\t\t\t%s])" % ',\n\t\t\t'.join(mnames)
laurent@371:             app_modnames.append(mnames)
laurent@371: 
laurent@371:         app_modnames.reverse()
laurent@371:         app_modnames = "new pyjslib.List([\n\t\t%s\n\t])" % ',\n\t\t'.join(app_modnames)
laurent@371: 
laurent@371:         # convert the overrides
laurent@371: 
laurent@371:         overnames = map(lambda x: "'%s': '%s'" % x, pover[platform].items())
laurent@371:         overnames = "new pyjslib.Dict({\n\t\t%s\n\t})" % ',\n\t\t'.join(overnames)
laurent@371: 
laurent@371:         #print "platform names", platform, overnames
laurent@371:         #print pover
laurent@371: 
laurent@371:         # now write app.allcache including dependency-ordered list of
laurent@371:         # library modules
laurent@371: 
laurent@371:         file_contents = all_cache_html_template % dict(
laurent@371:             app_name = app_name,
laurent@371:             early_app_libs = early_app_libs_,
laurent@371:             app_libs = app_libs_,
laurent@371:             app_code = app_code_,
laurent@371:             app_body = app_body,
laurent@371:             overrides = overnames,
laurent@371:             platform = platform.lower(),
laurent@371:             dynamic = dynamic,
laurent@371:             app_modnames = app_modnames,
laurent@371:             app_headers = app_headers
laurent@371:         )
laurent@371:         if cache_buster:
laurent@371:             digest = md5.new(file_contents).hexdigest()
laurent@371:             file_name = "%s.%s.%s" % (platform.lower(), app_name, digest)
laurent@371:         else:
laurent@371:             file_name = "%s.%s" % (platform.lower(), app_name)
laurent@371:         file_name += ".cache.html" 
laurent@371:         out_path = join(output, file_name)
laurent@371:         out_file = open(out_path, 'w')
laurent@371:         out_file.write(file_contents)
laurent@371:         out_file.close()
laurent@371:         app_files.append((platform.lower(), file_name))
laurent@371:         print "Created app file %s:%s: %s" % (
laurent@371:             app_name, platform, out_path)
laurent@371: 
laurent@371:     return app_files
laurent@371: 
laurent@371: def flattenlist(ll):
laurent@371:     res = []
laurent@371:     for l in ll:
laurent@371:         res += l
laurent@371:     return res
laurent@371: 
laurent@371: # creates sub-dependencies e.g. pyjamas.ui.Widget
laurent@371: # creates pyjamas.ui.Widget, pyjamas.ui and pyjamas.
laurent@371: def subdeps(m):
laurent@371:     d = []
laurent@371:     m = m.split(".")
laurent@371:     for i in range(0, len(m)):
laurent@371:         d.append('.'.join(m[:i+1]))
laurent@371:     return d
laurent@371: 
laurent@371: import time
laurent@371: 
laurent@371: def add_subdeps(deps, mod_name):
laurent@371:     sd = subdeps(mod_name)
laurent@371:     if len(sd) == 1:
laurent@371:         return []
laurent@371:     #print "subdeps", mod_name, sd
laurent@371:     #print "deps", deps
laurent@371:     res = []
laurent@371:     for i in range(0, len(sd)-1):
laurent@371:         parent = sd[i]
laurent@371:         child = sd[i+1]
laurent@371:         l = deps.get(child, [])
laurent@371:         l.append(parent)
laurent@371:         deps[child] = l
laurent@371:         if parent not in res:
laurent@371:             res.append(parent)
laurent@371:     #print deps
laurent@371:     return res
laurent@371: 
laurent@371: # makes unique and preserves list order
laurent@371: def uniquify(md):
laurent@371:     res = []
laurent@371:     for m in md:
laurent@371:         if m not in res:
laurent@371:             res.append(m)
laurent@371:     return res
laurent@371: 
laurent@371: def filter_mods(app_name, md):
laurent@371:     while 'sys' in md:
laurent@371:         md.remove('sys')
laurent@371:     while 'pyjslib' in md:
laurent@371:         md.remove('pyjslib')
laurent@371:     while app_name in md:
laurent@371:         md.remove(app_name)
laurent@371:     md = filter(lambda x: not x.endswith('.js'), md)
laurent@371:     md = map(pyjs.strip_py, md)
laurent@371: 
laurent@371:     return uniquify(md)
laurent@371: 
laurent@371: def filter_deps(app_name, deps):
laurent@371: 
laurent@371:     res = {}
laurent@371:     for (k, l) in deps.items():
laurent@371:         mods = filter_mods(k, l)
laurent@371:         while k in mods:
laurent@371:             mods.remove(k)
laurent@371:         res[k] = mods
laurent@371:     return res
laurent@371: 
laurent@371: def has_nodeps(mod, deps):
laurent@371:     if not deps.has_key(mod) or not deps[mod]:
laurent@371:         return True
laurent@371:     return False
laurent@371: 
laurent@371: def nodeps_list(mod_list, deps):
laurent@371:     res = []
laurent@371:     for mod in mod_list:
laurent@371:         if has_nodeps(mod, deps):
laurent@371:             res.append(mod)
laurent@371:     return res
laurent@371:         
laurent@371: # this function takes a dictionary of dependent modules and
laurent@371: # creates a list of lists.  the first list will be modules
laurent@371: # that have no dependencies; the second list will be those
laurent@371: # modules that have the first list as dependencies; the
laurent@371: # third will be those modules that have the first and second...
laurent@371: # etc.
laurent@371: 
laurent@371: 
laurent@371: def make_deps(app_name, deps, mod_list):
laurent@371:     print "Calculating Dependencies ..."
laurent@371:     mod_list = filter_mods(app_name, mod_list)
laurent@371:     deps = filter_deps(app_name, deps)
laurent@371: 
laurent@371:     if not mod_list:
laurent@371:         return []
laurent@371: 
laurent@371:     #print mod_list
laurent@371:     #print deps
laurent@371: 
laurent@371:     ordered_deps = []
laurent@371:     last_len = -1
laurent@371:     while deps:
laurent@371:         l_deps = len(deps)
laurent@371:         #print l_deps
laurent@371:         if l_deps==last_len:
laurent@371:             for m, dl in deps.items():
laurent@371:                 for d in dl:
laurent@371:                     if m in deps.get(d, []):
laurent@371:                         raise Exception('Circular Imports found: \n%s %s -> %s %s'
laurent@371:                                         % (m, dl, d, deps[d]))
laurent@371:             #raise Exception('Could not calculate dependencies: \n%s' % deps)
laurent@371:             break
laurent@371:         last_len = l_deps
laurent@371:         #print "modlist", mod_list
laurent@371:         nodeps = nodeps_list(mod_list, deps)
laurent@371:         #print "nodeps", nodeps
laurent@371:         mod_list = filter(lambda x: x not in nodeps, mod_list)
laurent@371:         newdeps = {}
laurent@371:         for k in deps.keys():
laurent@371:             depslist = deps[k]
laurent@371:             depslist = filter(lambda x: x not in nodeps, depslist)
laurent@371:             if depslist:
laurent@371:                 newdeps[k] = depslist
laurent@371:         #print "newdeps", newdeps
laurent@371:         deps = newdeps
laurent@371:         ordered_deps.append(nodeps)
laurent@371:         #time.sleep(0)
laurent@371: 
laurent@371:     if mod_list:
laurent@371:         ordered_deps.append(mod_list) # last dependencies - usually the app(s)
laurent@371: 
laurent@371:     ordered_deps.reverse()
laurent@371: 
laurent@371:     return ordered_deps
laurent@371: 
laurent@371: def main():
laurent@371:     global app_platforms
laurent@371: 
laurent@371:     parser = OptionParser(usage = usage, version = version)
laurent@371:     parser.add_option("-o", "--output", dest="output",
laurent@371:         help="directory to which the webapp should be written")
laurent@371:     parser.add_option("-j", "--include-js", dest="js_includes", action="append",
laurent@371:         help="javascripts to load into the same frame as the rest of the script")
laurent@371:     parser.add_option("-I", "--library_dir", dest="library_dirs",
laurent@371:         action="append", help="additional paths appended to PYJSPATH")
laurent@371:     parser.add_option("-D", "--data_dir", dest="data_dir",
laurent@371:         help="path for data directory")
laurent@371:     parser.add_option("-m", "--dynamic-modules", action="store_true",
laurent@371:         dest="dynamic", default=False,
laurent@371:         help="Split output into separate dynamically-loaded modules (experimental)")
laurent@371:     parser.add_option("-P", "--platforms", dest="platforms",
laurent@371:         help="platforms to build for, comma-separated")
laurent@371:     parser.add_option("-d", "--debug", action="store_true", dest="debug")
laurent@371:     parser.add_option("-O", "--optimize", action="store_true",
laurent@371:                       dest="optimize", default=False,
laurent@371:                       help="Optimize generated code (removes all print statements)",
laurent@371:                       )
laurent@371:     parser.add_option("-c", "--cache_buster", action="store_true",
laurent@371:                   dest="cache_buster",
laurent@371:         help="Enable browser cache-busting (MD5 hash added to output filenames)")
laurent@371: 
laurent@371:     parser.set_defaults(output = "output", js_includes=[], library_dirs=[],
laurent@371:                         platforms=(','.join(app_platforms)),
laurent@371:                         data_dir=os.path.join(sys.prefix, "share/pyjamas"),
laurent@371:                         dynamic=False,
laurent@371:                         cache_buster=False,
laurent@371:                         debug=False)
laurent@371:     (options, args) = parser.parse_args()
laurent@371:     if len(args) != 1:
laurent@371:         parser.error("incorrect number of arguments")
laurent@371: 
laurent@371:     data_dir = abspath(options.data_dir)
laurent@371: 
laurent@371:     app_path = args[0]
laurent@371:     if app_path.endswith('.py'):
laurent@371:         app_path = abspath(app_path)
laurent@371:         if not isfile(app_path):
laurent@371:             parser.error("Application file not found %r" % app_path)
laurent@371:         app_path, app_name = split(app_path)
laurent@371:         app_name = app_name[:-3]
laurent@371:         pyjs.path.append(app_path)
laurent@371:     elif os.path.sep in app_path:
laurent@371:         parser.error("Not a valid module declaration %r" % app_path)
laurent@371:     else:
laurent@371:         app_name = app_path
laurent@371: 
laurent@371:     for d in options.library_dirs:
laurent@371:         pyjs.path.append(abspath(d))
laurent@371: 
laurent@371:     if options.platforms:
laurent@371:        app_platforms = options.platforms.split(',')
laurent@371: 
laurent@371:     # this is mostly for getting boilerplate stuff
laurent@371:     data_dir = os.path.abspath(options.data_dir)
laurent@371: 
laurent@371:     build(app_name, options.output, options.js_includes,
laurent@371:           options.debug, options.dynamic and 1 or 0, data_dir,
laurent@371:           options.cache_buster, options.optimize)
laurent@371: 
laurent@371: if __name__ == "__main__":
laurent@371:     main()
laurent@371: