Last active
August 29, 2015 14:09
-
-
Save shadybones/f8692fd8b49e57a04bae to your computer and use it in GitHub Desktop.
Combine ( merge ) multiple JS files into a single one. Build JS files using imports. Use build-time bindings from property files. Meant to be used in conjunction with a maven or ant build system.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# | |
# Combine ( merge ) multiple JS files into a single one. Build JS files using imports. | |
# Use bindings from multiple property files. | |
# | |
# Use @import {file path} to import js files (paste into the current JS in the output). | |
# Pasting occurs at the site of the import statement. | |
# Use ${binding_name} to inject values from a property file when you build | |
# | |
# I chose simplicity and ease of understanding over complexity and featurefull-ness. | |
# I tried to limit memory usage by writing and closing immediately vs. storing as a variable, | |
# but I'm not familiar enough with python to know if I did any good. | |
# | |
# All JS which use @import are referred to as "templates" | |
# All files which get processed can use dynamic bindings ${property} | |
# If a file is @imported already, it won't be imported a second time; which means imports should | |
# be structured like modules and designed to be accessable from anywhere in the code. | |
# | |
# You can force a file to be imported more than once by using @!import instead of @import. | |
# | |
# Yes, @import-ing is recursive; so you can @import templates. | |
# | |
# A template may use up to two property files - a project level file, and a subclass property file. | |
# The project-level property file may include a ${property} found in subclass property files. | |
# Or of no subclass level property file is used, may use ${properties} in itself (recursive, 1 level). | |
# Unlike some frameworks, it doesn't keep recursing till all the ${properties} are bound; it only runs twice. | |
# | |
# Example: | |
# template.js: | |
# var value = "${some.binding}; oranges are ${desc}" | |
# projectA.properties: | |
# desc=delicious | |
# some.binding="apples are ${desc}" | |
# projectA.qa.properties: | |
# desc=terrible | |
# some.binding="apples are ${desc}" | |
# When building with no -l argument, then the output will be: | |
# var value = "apples are delicious; oranges are delicious" | |
# When building with "-l qa" argument, the output is: | |
# var value = "apples are delicious; oranges are terrible" | |
# | |
# | |
# You may need to install ConfigObj module: | |
# sudo pip install configobj | |
# OR | |
# sudo easy_install configobj | |
import re, os, sys, getopt | |
from string import Template | |
try: | |
from configobj import ConfigObj | |
except ImportError as e: | |
print "\nError: Missing Import\nYou must install ConfigObject module (hint: pip install configobj or easy_install configobj)" | |
sys.exit(2); | |
RE_IMPORTS = "/*\**\s*@(!?)import[\s/\*]+(\S*)" | |
desc = '' | |
project = '' | |
filename = '' | |
sourceDir = None | |
level = '' | |
propFilenameTop = '' | |
propFilenameProj = '' | |
outputfile = None | |
files = [] | |
beenProcessed = {} | |
def getFileContent(filePath): | |
try: | |
f = open(filePath, 'r') | |
if propFilenameTop: | |
try: | |
content = Template(Template(f.read()).safe_substitute(ConfigObj(propFilenameProj, file_error=True))).safe_substitute(ConfigObj(propFilenameTop, file_error=True),version=desc) | |
except IOError as e: | |
outputfile.close() | |
print "\nERROR: File not found or cannot be opened: %s or %s,\n %s" % (propFilenameTop, propFilenameProj, e) | |
sys.exit(2) | |
else: | |
content = f.read() | |
f.close() | |
return content | |
except IOError as e: | |
outputfile.close() | |
print "\nERROR: File not found or cannot be opened: %s,\n %s" % (filePath, e) | |
sys.exit(2) | |
def writeToFile(content): | |
outputfile.write(content) | |
outputfile.write("\n") | |
def process(filePath, modifier=None): | |
filePath = os.path.join(sourceDir, filePath).strip() | |
if filePath in beenProcessed and modifier != '!': | |
if filePath not in files: | |
print "\nERROR: Import loop. Imported script imports importing script." | |
raise SystemExit | |
return | |
print "INFO: Importing file %s" % (filePath) | |
beenProcessed[filePath] = 'true' | |
content = getFileContent(filePath) | |
last_i=0 | |
#example: // @import subfolder/file.js | |
for mob in re.finditer(RE_IMPORTS,content): | |
if mob: | |
writeToFile(content[last_i:mob.start()]) | |
last_i = mob.end() | |
process(mob.group(2), mob.group(1)) | |
writeToFile(content[last_i:]) | |
files.append(filePath) | |
if __name__ == "__main__": | |
outputfn = None | |
errorstring = '\nERROR: Incorrect arguments: build.py -f <template_file_name> -s <source_dir> -p <property_filename> -d <versioning> -l <property_file_subclass> -o <output_file_name>' | |
try: | |
opts, args = getopt.getopt(sys.argv[1:],"f:p:d:l:s:o:") | |
except getopt.GetoptError: | |
print errorstring | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt == '-f': | |
filename = arg | |
elif opt == '-s': | |
sourceDir = arg | |
elif opt == '-o': | |
outputfn = arg | |
elif opt == '-p': | |
project = arg | |
elif opt == '-d': | |
desc = arg | |
elif opt == '-l': | |
level = arg | |
else: | |
print errorstring | |
sys.exit(2) | |
if level: | |
if level == 'prod': | |
level = '' | |
else: | |
level = '.' + level | |
if not project: | |
print '\nERROR: requires -p argument (property filename minus extension) if level -l specified ' | |
sys.exit(2) | |
if not filename: | |
print '\nERROR: requires -f argument (file name of template), ' + filename | |
sys.exit(2) | |
if not sourceDir: | |
sourceDir = './src' | |
print 'INFO: No source directory provided, using default: ' + sourceDir | |
if not outputfn: | |
outputfn = './output.txt' | |
print 'WARNING: No output file specified, using default: ' + outputfn | |
if project: | |
propertyDir = os.path.normpath(os.path.join(sourceDir, os.path.join("..", "build"))).strip() | |
propFilenameTop = os.path.join(propertyDir, project+".properties").strip() | |
propFilenameProj = os.path.join(propertyDir, project+level+".properties").strip() | |
print "INFO: Using property files: %s and %s" % (propFilenameTop, propFilenameProj) | |
try: | |
outputfile = open(outputfn,'w') | |
except IOError as e: | |
print "\nERROR: Cannot open output file: %s , \n %s" % (outputfn, e) | |
sys.exit(2) | |
process(filename) | |
outputfile.close() | |
print "SUCCESS, output to %s" % (outputfn) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment