~zubairassad89/sahana-eden/vms_gsoc

« back to all changes in this revision

Viewing changes to static/proj4js/tools/mergejs.py

  • Committer: Fran Boon
  • Date: 2008-12-09 22:35:23 UTC
  • Revision ID: flavour@partyvibe.com-20081209223523-fcs5k95jjk0uqo0z
Initial import of work done so far

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Merge multiple JavaScript source code files into one.
 
4
#
 
5
# Usage:
 
6
# This script requires source files to have dependencies specified in them.
 
7
#
 
8
# Dependencies are specified with a comment of the form:
 
9
#
 
10
#     // @requires <file path>
 
11
#
 
12
#  e.g.
 
13
#
 
14
#    // @requires Geo/DataSource.js
 
15
#
 
16
#  or (ideally) within a class comment definition
 
17
#
 
18
#     /**
 
19
#      * @class
 
20
#      *
 
21
#      * @requires OpenLayers/Layer.js
 
22
#      */
 
23
#
 
24
# This script should be executed like so:
 
25
#
 
26
#     mergejs.py <output.js> <directory> [...]
 
27
#
 
28
# e.g.
 
29
#
 
30
#     mergejs.py openlayers.js Geo/ CrossBrowser/
 
31
#
 
32
#  This example will cause the script to walk the `Geo` and
 
33
#  `CrossBrowser` directories--and subdirectories thereof--and import
 
34
#  all `*.js` files encountered. The dependency declarations will be extracted
 
35
#  and then the source code from imported files will be output to 
 
36
#  a file named `openlayers.js` in an order which fulfils the dependencies
 
37
#  specified.
 
38
#
 
39
#
 
40
# Note: This is a very rough initial version of this code.
 
41
#
 
42
# -- Copyright 2005-2007 MetaCarta, Inc. / OpenLayers project --
 
43
#
 
44
 
 
45
# TODO: Allow files to be excluded. e.g. `Crossbrowser/DebugMode.js`?
 
46
# TODO: Report error when dependency can not be found rather than KeyError.
 
47
 
 
48
import re
 
49
import os
 
50
import sys
 
51
import glob
 
52
 
 
53
SUFFIX_JAVASCRIPT = ".js"
 
54
 
 
55
RE_REQUIRE = "@requires (.*)\n" # TODO: Ensure in comment?
 
56
class SourceFile:
 
57
    """
 
58
    Represents a Javascript source code file.
 
59
    """
 
60
 
 
61
    def __init__(self, filepath, source):
 
62
        """
 
63
        """
 
64
        self.filepath = filepath
 
65
        self.source = source
 
66
 
 
67
        self.requiredBy = []
 
68
 
 
69
 
 
70
    def _getRequirements(self):
 
71
        """
 
72
        Extracts the dependencies specified in the source code and returns
 
73
        a list of them.
 
74
        """
 
75
        # TODO: Cache?
 
76
        return re.findall(RE_REQUIRE, self.source)
 
77
 
 
78
    requires = property(fget=_getRequirements, doc="")
 
79
 
 
80
 
 
81
 
 
82
def usage(filename):
 
83
    """
 
84
    Displays a usage message.
 
85
    """
 
86
    print "%s [-c <config file>] <output.js> <directory> [...]" % filename
 
87
 
 
88
 
 
89
class Config:
 
90
    """
 
91
    Represents a parsed configuration file.
 
92
 
 
93
    A configuration file should be of the following form:
 
94
 
 
95
        [first]
 
96
        3rd/prototype.js
 
97
        core/application.js
 
98
        core/params.js
 
99
 
 
100
        [last]
 
101
        core/api.js
 
102
 
 
103
        [exclude]
 
104
        3rd/logger.js
 
105
 
 
106
    All headings are required.
 
107
 
 
108
    The files listed in the `first` section will be forced to load
 
109
    *before* all other files (in the order listed). The files in `last`
 
110
    section will be forced to load *after* all the other files (in the
 
111
    order listed).
 
112
 
 
113
    The files list in the `exclude` section will not be imported.
 
114
    
 
115
    """
 
116
 
 
117
    def __init__(self, filename):
 
118
        """
 
119
        Parses the content of the named file and stores the values.
 
120
        """
 
121
        lines = [line.strip() # Assumes end-of-line character is present
 
122
                 for line in open(filename)
 
123
                 if line.strip()] # Skip blank lines
 
124
 
 
125
        self.forceFirst = lines[lines.index("[first]") + 1:lines.index("[last]")]
 
126
 
 
127
        self.forceLast = lines[lines.index("[last]") + 1:lines.index("[include]")]
 
128
        self.include =  lines[lines.index("[include]") + 1:lines.index("[exclude]")]
 
129
        self.exclude =  lines[lines.index("[exclude]") + 1:]
 
130
 
 
131
def run (sourceDirectory, outputFilename = None, configFile = None):
 
132
    cfg = None
 
133
    if configFile:
 
134
        cfg = Config(configFile)
 
135
 
 
136
    allFiles = []
 
137
 
 
138
    ## Find all the Javascript source files
 
139
    for root, dirs, files in os.walk(sourceDirectory):
 
140
        for filename in files:
 
141
            if filename.endswith(SUFFIX_JAVASCRIPT) and not filename.startswith("."):
 
142
                filepath = os.path.join(root, filename)[len(sourceDirectory)+1:]
 
143
                filepath = filepath.replace("\\", "/")
 
144
                if cfg and cfg.include:
 
145
                    include = False
 
146
                    for included in cfg.include:
 
147
                        if glob.fnmatch.fnmatch(filepath, included):
 
148
                            include = True
 
149
                    if include or filepath in cfg.forceFirst:
 
150
                        allFiles.append(filepath)
 
151
                elif (not cfg) or (filepath not in cfg.exclude):
 
152
                    exclude = False
 
153
                    for excluded in cfg.exclude:
 
154
                        if glob.fnmatch.fnmatch(filepath, excluded):
 
155
                            exclude = True
 
156
                    if not exclude:
 
157
                        allFiles.append(filepath)
 
158
 
 
159
    ## Header inserted at the start of each file in the output
 
160
    HEADER = "/* " + "=" * 70 + "\n    %s\n" + "   " + "=" * 70 + " */\n\n"
 
161
 
 
162
    files = {}
 
163
 
 
164
    order = [] # List of filepaths to output, in a dependency satisfying order 
 
165
 
 
166
    ## Import file source code
 
167
    ## TODO: Do import when we walk the directories above?
 
168
    for filepath in allFiles:
 
169
        print "Importing: %s" % filepath
 
170
        fullpath = os.path.join(sourceDirectory, filepath)
 
171
        content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
 
172
        files[filepath] = SourceFile(filepath, content) # TODO: Chop path?
 
173
 
 
174
    print
 
175
 
 
176
    from toposort import toposort
 
177
 
 
178
    complete = False
 
179
    resolution_pass = 1
 
180
 
 
181
    while not complete:
 
182
        order = [] # List of filepaths to output, in a dependency satisfying order 
 
183
        nodes = []
 
184
        routes = []
 
185
        ## Resolve the dependencies
 
186
        print "Resolution pass %s... " % resolution_pass
 
187
        resolution_pass += 1 
 
188
 
 
189
        for filepath, info in files.items():
 
190
            nodes.append(filepath)
 
191
            for neededFilePath in info.requires:
 
192
                routes.append((neededFilePath, filepath))
 
193
 
 
194
        for dependencyLevel in toposort(nodes, routes):
 
195
            for filepath in dependencyLevel:
 
196
                order.append(filepath)
 
197
                if not files.has_key(filepath):
 
198
                    print "Importing: %s" % filepath
 
199
                    fullpath = os.path.join(sourceDirectory, filepath)
 
200
                    content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
 
201
                    files[filepath] = SourceFile(filepath, content) # TODO: Chop path?
 
202
        
 
203
 
 
204
 
 
205
        # Double check all dependencies have been met
 
206
        complete = True
 
207
        try:
 
208
            for fp in order:
 
209
                if max([order.index(rfp) for rfp in files[fp].requires] +
 
210
                       [order.index(fp)]) != order.index(fp):
 
211
                    complete = False
 
212
        except:
 
213
            complete = False
 
214
        
 
215
        print    
 
216
 
 
217
 
 
218
    ## Move forced first and last files to the required position
 
219
    if cfg:
 
220
        print "Re-ordering files..."
 
221
        order = cfg.forceFirst + [item
 
222
                     for item in order
 
223
                     if ((item not in cfg.forceFirst) and
 
224
                         (item not in cfg.forceLast))] + cfg.forceLast
 
225
    
 
226
    print
 
227
    ## Output the files in the determined order
 
228
    result = []
 
229
 
 
230
    for fp in order:
 
231
        f = files[fp]
 
232
        print "Exporting: ", f.filepath
 
233
        result.append(HEADER % f.filepath)
 
234
        source = f.source
 
235
        result.append(source)
 
236
        if not source.endswith("\n"):
 
237
            result.append("\n")
 
238
 
 
239
    print "\nTotal files merged: %d " % len(files)
 
240
 
 
241
    if outputFilename:
 
242
        print "\nGenerating: %s" % (outputFilename)
 
243
        open(outputFilename, "w").write("".join(result))
 
244
    return "".join(result)
 
245
 
 
246
if __name__ == "__main__":
 
247
    import getopt
 
248
 
 
249
    options, args = getopt.getopt(sys.argv[1:], "-c:")
 
250
    
 
251
    try:
 
252
        outputFilename = args[0]
 
253
    except IndexError:
 
254
        usage(sys.argv[0])
 
255
        raise SystemExit
 
256
    else:
 
257
        sourceDirectory = args[1]
 
258
        if not sourceDirectory:
 
259
            usage(sys.argv[0])
 
260
            raise SystemExit
 
261
 
 
262
    configFile = None
 
263
    if options and options[0][0] == "-c":
 
264
        configFile = options[0][1]
 
265
        print "Parsing configuration file: %s" % filename
 
266
 
 
267
    run( sourceDirectory, outputFilename, configFile )