| Index: build.py | 
| diff --git a/build.py b/build.py | 
| index aa3c2035eea5688fcbbd9468ccafa250757f161c..99e6641570c26290250105ac53c06c14a2ae7f14 100644 | 
| --- a/build.py | 
| +++ b/build.py | 
| @@ -2,211 +2,123 @@ | 
| # License, v. 2.0. If a copy of the MPL was not distributed with this | 
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
| +import argparse | 
| import os | 
| import sys | 
| import re | 
| import subprocess | 
| import shutil | 
| -from getopt import getopt, GetoptError | 
| +from functools import partial | 
| from StringIO import StringIO | 
| from zipfile import ZipFile | 
| -knownTypes = {'gecko', 'chrome', 'generic', 'edge'} | 
| +KNOWN_PLATFORMS = {'gecko', 'chrome', 'generic', 'edge'} | 
| +parser = argparse.ArgumentParser( | 
| + description=__doc__, | 
| + formatter_class=argparse.RawDescriptionHelpFormatter) | 
| -class Command(object): | 
| - name = property(lambda self: self._name) | 
| - shortDescription = property( | 
| - lambda self: self._shortDescription, | 
| - lambda self, value: self.__dict__.update({'_shortDescription': value}) | 
| - ) | 
| - description = property( | 
| - lambda self: self._description, | 
| - lambda self, value: self.__dict__.update({'_description': value}) | 
| - ) | 
| - params = property( | 
| - lambda self: self._params, | 
| - lambda self, value: self.__dict__.update({'_params': value}) | 
| - ) | 
| - supportedTypes = property( | 
| - lambda self: self._supportedTypes, | 
| - lambda self, value: self.__dict__.update({'_supportedTypes': value}) | 
| - ) | 
| - options = property(lambda self: self._options) | 
| - | 
| - def __init__(self, handler, name): | 
| - self._handler = handler | 
| - self._name = name | 
| - self._shortDescription = '' | 
| - self._description = '' | 
| - self._params = '' | 
| - self._supportedTypes = None | 
| - self._options = [] | 
| - self.addOption('Show this message and exit', short='h', long='help') | 
| - | 
| - def __enter__(self): | 
| - return self | 
| - | 
| - def __exit__(self, exc_type, exc_value, traceback): | 
| - pass | 
| - | 
| - def __call__(self, baseDir, scriptName, opts, args, type): | 
| - return self._handler(baseDir, scriptName, opts, args, type) | 
| - | 
| - def isSupported(self, type): | 
| - return self._supportedTypes == None or type in self._supportedTypes | 
| - | 
| - def addOption(self, description, short=None, long=None, value=None, types=None): | 
| - self._options.append((description, short, long, value, types)) | 
| - | 
| - def parseArgs(self, type, args): | 
| - shortOptions = map( | 
| - lambda o: o[1] + ':' if o[3] != None else o[1], | 
| - filter( | 
| - lambda o: o[1] != None and (o[4] == None or type in o[4]), | 
| - self._options | 
| - ) | 
| - ) | 
| - longOptions = map( | 
| - lambda o: o[2] + '=' if o[3] != None else o[2], | 
| - filter( | 
| - lambda o: o[2] != None and (o[4] == None or type in o[4]), | 
| - self._options | 
| - ) | 
| - ) | 
| - return getopt(args, ''.join(shortOptions), longOptions) | 
| +subs = parser.add_subparsers(title='Commands', dest='action', | 
| + metavar='[command]') | 
| -commandsList = [] | 
| -commands = {} | 
| +def make_argument(*args, **kwargs): | 
| + def _make_argument(*args, **kwargs): | 
| + parser = kwargs.pop('parser') | 
| + parser.add_argument(*args, **kwargs) | 
| + return partial(_make_argument, *args, **kwargs) | 
| -def addCommand(handler, name): | 
| - if isinstance(name, basestring): | 
| - aliases = () | 
| - else: | 
| - name, aliases = (name[0], name[1:]) | 
| - | 
| - global commandsList, commands | 
| - command = Command(handler, name) | 
| - commandsList.append(command) | 
| - commands[name] = command | 
| - for alias in aliases: | 
| - commands[alias] = command | 
| - return command | 
| - | 
| - | 
| -def splitByLength(string, maxLen): | 
| - parts = [] | 
| - currentPart = '' | 
| - for match in re.finditer(r'\s*(\S+)', string): | 
| - if len(match.group(0)) + len(currentPart) < maxLen: | 
| - currentPart += match.group(0) | 
| - else: | 
| - parts.append(currentPart) | 
| - currentPart = match.group(1) | 
| - if len(currentPart): | 
| - parts.append(currentPart) | 
| - return parts | 
| - | 
| - | 
| -def usage(scriptName, type, commandName=None): | 
| - if commandName == None: | 
| - global commandsList | 
| - descriptions = [] | 
| - for command in commandsList: | 
| - if not command.isSupported(type): | 
| - continue | 
| - commandText = ('%s %s' % (command.name, command.params)).ljust(39) | 
| - descriptionParts = splitByLength(command.shortDescription, 29) | 
| - descriptions.append(' %s [-t %s] %s %s' % (scriptName, type, commandText, descriptionParts[0])) | 
| - for part in descriptionParts[1:]: | 
| - descriptions.append(' %s %s %s %s' % (' ' * len(scriptName), ' ' * len(type), ' ' * len(commandText), part)) | 
| - print '''Usage: | 
| - | 
| -%(descriptions)s | 
| - | 
| -For details on a command run: | 
| - | 
| - %(scriptName)s [-t %(type)s] <command> --help | 
| -''' % { | 
| - 'scriptName': scriptName, | 
| - 'type': type, | 
| - 'descriptions': '\n'.join(descriptions) | 
| - } | 
| - else: | 
| - global commands | 
| - command = commands[commandName] | 
| - description = '\n'.join(map(lambda s: '\n'.join(splitByLength(s, 80)), command.description.split('\n'))) | 
| - options = [] | 
| - for descr, short, long, value, types in command.options: | 
| - if types != None and type not in types: | 
| - continue | 
| - if short == None: | 
| - shortText = '' | 
| - elif value == None: | 
| - shortText = '-%s' % short | 
| - else: | 
| - shortText = '-%s %s' % (short, value) | 
| - if long == None: | 
| - longText = '' | 
| - elif value == None: | 
| - longText = '--%s' % long | 
| - else: | 
| - longText = '--%s=%s' % (long, value) | 
| - descrParts = splitByLength(descr, 46) | 
| - options.append(' %s %s %s' % (shortText.ljust(11), longText.ljust(19), descrParts[0])) | 
| - for part in descrParts[1:]: | 
| - options.append(' %s %s %s' % (' ' * 11, ' ' * 19, part)) | 
| - print '''%(scriptName)s [-t %(type)s] %(name)s %(params)s | 
| - | 
| -%(description)s | 
| - | 
| -Options: | 
| -%(options)s | 
| -''' % { | 
| - 'scriptName': scriptName, | 
| - 'type': type, | 
| - 'name': command.name, | 
| - 'params': command.params, | 
| - 'description': description, | 
| - 'options': '\n'.join(options) | 
| - } | 
| +def argparse_command(name=None, platforms=None, arguments=()): | 
| + def wrapper(func): | 
| + def func_wrapper(*args, **kwargs): | 
| + return func(*args, **kwargs) | 
| + | 
| + short_desc, long_desc = func.__doc__.split(os.linesep + os.linesep, 1) | 
| -def runBuild(baseDir, scriptName, opts, args, type): | 
| + new_parser = subs.add_parser( | 
| + name or func.__name__, | 
| + description=long_desc, | 
| + formatter_class=argparse.RawDescriptionHelpFormatter, | 
| + help=short_desc, | 
| + ) | 
| + | 
| + if len(platforms) > 1: | 
| + new_parser.add_argument('-t', '--type', dest='platform', | 
| + choices=platforms, required=True) | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
This is suboptimal for repositories where only one
 
tlucas
2017/11/17 11:36:45
Done. (See collect_platforms)
 
 | 
| + elif len(platforms) == 1: | 
| + new_parser.set_defaults(platform=platforms.pop()) | 
| + | 
| + for argument in arguments: | 
| + argument(parser=new_parser) | 
| + | 
| + new_parser.set_defaults(function=func) | 
| + return func_wrapper | 
| + | 
| + return wrapper | 
| + | 
| + | 
| +@argparse_command( | 
| + name='build', platforms={'gecko', 'chrome', 'edge'}, | 
| + arguments=( | 
| + make_argument( | 
| + '-b', '--build', | 
| + help=('Use given build number (if omitted the build number will ' | 
| + 'be retrieved from Mercurial)')), | 
| + make_argument( | 
| + '-k', '--key', dest='key_file', | 
| + help=('File containing private key and certificates required to ' | 
| + 'sign the package')), | 
| + make_argument( | 
| + '-r', '--release', action='store_true', | 
| + help='Create a release build'), | 
| + make_argument('output_file', nargs='?') | 
| + ) | 
| +) | 
| +def run_build(base_dir, build, key_file, release, output_file, platform, | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Nit: Maybe rename this function into build and dro
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| + **kwargs): | 
| + """ | 
| + Create a build. | 
| + | 
| + Creates an extension build with given file name. If output_file is missing | 
| + a default name will be chosen. | 
| + """ | 
| kwargs = {} | 
| - for option, value in opts: | 
| - if option in {'-b', '--build'}: | 
| - kwargs['buildNum'] = value | 
| - if type != 'gecko' and not kwargs['buildNum'].isdigit(): | 
| - raise TypeError('Build number must be numerical') | 
| - elif option in {'-k', '--key'}: | 
| - kwargs['keyFile'] = value | 
| - elif option in {'-r', '--release'}: | 
| - kwargs['releaseBuild'] = True | 
| - if len(args) > 0: | 
| - kwargs['outFile'] = args[0] | 
| - | 
| - if type in {'chrome', 'gecko'}: | 
| + if platform in {'chrome', 'gecko'}: | 
| import buildtools.packagerChrome as packager | 
| - elif type == 'edge': | 
| + elif platform == 'edge': | 
| import buildtools.packagerEdge as packager | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Nit: This is code you merely moved around but cons
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| - packager.createBuild(baseDir, type=type, **kwargs) | 
| + kwargs['keyFile'] = key_file | 
| + kwargs['outFile'] = output_file | 
| + kwargs['releaseBuild'] = release | 
| + kwargs['buildNum'] = build | 
| + packager.createBuild(base_dir, type=platform, **kwargs) | 
| -def createDevEnv(baseDir, scriptName, opts, args, type): | 
| - if type == 'edge': | 
| + | 
| +@argparse_command( | 
| + name='devenv', platforms={'chrome', 'gecko', 'edge'} | 
| +) | 
| +def create_devenv(base_dir, platform, **kwargs): | 
| + """ | 
| + Set up a development environment. | 
| + | 
| + Will set up or update the devenv folder as an unpacked extension folder ' | 
| + for development. | 
| + """ | 
| + if platform == 'edge': | 
| import buildtools.packagerEdge as packager | 
| else: | 
| import buildtools.packagerChrome as packager | 
| file = StringIO() | 
| - packager.createBuild(baseDir, type=type, outFile=file, devenv=True, releaseBuild=True) | 
| + packager.createBuild(base_dir, type=platform, outFile=file, devenv=True, | 
| + releaseBuild=True) | 
| from buildtools.packager import getDevEnvPath | 
| - devenv_dir = getDevEnvPath(baseDir, type) | 
| + devenv_dir = getDevEnvPath(base_dir, platform) | 
| shutil.rmtree(devenv_dir, ignore_errors=True) | 
| @@ -215,129 +127,152 @@ def createDevEnv(baseDir, scriptName, opts, args, type): | 
| zip_file.extractall(devenv_dir) | 
| -def readLocaleConfig(baseDir, type, metadata): | 
| - if type != 'generic': | 
| +def read_locale_config(base_dir, platform, metadata): | 
| + if platform != 'generic': | 
| import buildtools.packagerChrome as packager | 
| - localeDir = os.path.join(baseDir, '_locales') | 
| - localeConfig = { | 
| + locale_dir = os.path.join(base_dir, '_locales') | 
| + locale_config = { | 
| 'default_locale': packager.defaultLocale, | 
| } | 
| else: | 
| - localeDir = os.path.join( | 
| - baseDir, *metadata.get('locales', 'base_path').split('/') | 
| + locale_dir = os.path.join( | 
| + base_dir, *metadata.get('locales', 'base_path').split('/') | 
| ) | 
| - localeConfig = { | 
| + locale_config = { | 
| 'default_locale': metadata.get('locales', 'default_locale') | 
| } | 
| - localeConfig['base_path'] = localeDir | 
| + locale_config['base_path'] = locale_dir | 
| - locales = [(locale.replace('_', '-'), os.path.join(localeDir, locale)) | 
| - for locale in os.listdir(localeDir)] | 
| - localeConfig['locales'] = dict(locales) | 
| + locales = [(locale.replace('_', '-'), os.path.join(locale_dir, locale)) | 
| + for locale in os.listdir(locale_dir)] | 
| + locale_config['locales'] = dict(locales) | 
| - return localeConfig | 
| + return locale_config | 
| -def setupTranslations(baseDir, scriptName, opts, args, type): | 
| - if len(args) < 1: | 
| - print 'Project key is required to update translation master files.' | 
| - usage(scriptName, type, 'setuptrans') | 
| - return | 
| +project_key_argument = make_argument( | 
| + 'project_key', help='The crowdin project key (required).', nargs='?' | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
This is a required parameter, why not nargs=1? Thi
 
tlucas
2017/11/17 11:36:44
nargs=1 would produce a list of one value, leaving
 
 | 
| +) | 
| - key = args[0] | 
| +@argparse_command( | 
| + name='setuptrans', platforms=KNOWN_PLATFORMS, | 
| + arguments=(project_key_argument, ) | 
| +) | 
| +def setup_translations(base_dir, project_key, platform, **kwargs): | 
| + """ | 
| + Set up translation languages. | 
| + | 
| + Set up translation languages for the project on crowdin.net. | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Here and for the commands below: it's crowdin.com.
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| + """ | 
| from buildtools.packager import readMetadata | 
| - metadata = readMetadata(baseDir, type) | 
| + metadata = readMetadata(base_dir, platform) | 
| basename = metadata.get('general', 'basename') | 
| - localeConfig = readLocaleConfig(baseDir, type, metadata) | 
| + locale_config = read_locale_config(base_dir, platform, metadata) | 
| import buildtools.localeTools as localeTools | 
| - localeTools.setupTranslations(localeConfig, basename, key) | 
| - | 
| + localeTools.setupTranslations(locale_config, basename, project_key) | 
| -def updateTranslationMaster(baseDir, scriptName, opts, args, type): | 
| - if len(args) < 1: | 
| - print 'Project key is required to update translation master files.' | 
| - usage(scriptName, type, 'translate') | 
| - return | 
| - key = args[0] | 
| +@argparse_command( | 
| + name='translate', platforms=KNOWN_PLATFORMS, | 
| + arguments=(project_key_argument, ) | 
| +) | 
| +def update_translation_master(base_dir, project_key, platform, **kwargs): | 
| + """ | 
| + Update translation master files. | 
| + Update the translation master files in the project on crowdin.net. | 
| + """ | 
| from buildtools.packager import readMetadata | 
| - metadata = readMetadata(baseDir, type) | 
| + metadata = readMetadata(base_dir, platform) | 
| basename = metadata.get('general', 'basename') | 
| - localeConfig = readLocaleConfig(baseDir, type, metadata) | 
| + locale_config = read_locale_config(base_dir, platform, metadata) | 
| - defaultLocaleDir = os.path.join(localeConfig['base_path'], | 
| - localeConfig['default_locale']) | 
| + default_locale_dir = os.path.join(locale_config['base_path'], | 
| + locale_config['default_locale']) | 
| import buildtools.localeTools as localeTools | 
| - localeTools.updateTranslationMaster(localeConfig, metadata, defaultLocaleDir, | 
| - basename, key) | 
| + localeTools.updateTranslationMaster(locale_config, metadata, | 
| + default_locale_dir, basename, | 
| + project_key) | 
| -def uploadTranslations(baseDir, scriptName, opts, args, type): | 
| - if len(args) < 1: | 
| - print 'Project key is required to upload existing translations.' | 
| - usage(scriptName, type, 'uploadtrans') | 
| - return | 
| - | 
| - key = args[0] | 
| +@argparse_command( | 
| + name='uploadtrans', platforms=KNOWN_PLATFORMS, | 
| + arguments=(project_key_argument, ) | 
| +) | 
| +def upload_translations(base_dir, project_key, platform, **kwargs): | 
| + """ | 
| + Upload existing translations. | 
| + Upload already existing translations to the project on crowdin.net. | 
| + """ | 
| from buildtools.packager import readMetadata | 
| - metadata = readMetadata(baseDir, type) | 
| + metadata = readMetadata(base_dir, platform) | 
| basename = metadata.get('general', 'basename') | 
| - localeConfig = readLocaleConfig(baseDir, type, metadata) | 
| + locale_config = read_locale_config(base_dir, platform, metadata) | 
| import buildtools.localeTools as localeTools | 
| - for locale, localeDir in localeConfig['locales'].iteritems(): | 
| - if locale != localeConfig['default_locale'].replace('_', '-'): | 
| - localeTools.uploadTranslations(localeConfig, metadata, localeDir, locale, | 
| - basename, key) | 
| - | 
| - | 
| -def getTranslations(baseDir, scriptName, opts, args, type): | 
| - if len(args) < 1: | 
| - print 'Project key is required to update translation master files.' | 
| - usage(scriptName, type, 'translate') | 
| - return | 
| - | 
| - key = args[0] | 
| - | 
| + for locale, locale_dir in locale_config['locales'].iteritems(): | 
| + if locale != locale_config['default_locale'].replace('_', '-'): | 
| + localeTools.uploadTranslations(locale_config, metadata, locale_dir, | 
| + locale, basename, project_key) | 
| + | 
| + | 
| +@argparse_command( | 
| + name='gettranslations', platforms=KNOWN_PLATFORMS, | 
| + arguments=(project_key_argument, ) | 
| +) | 
| +def get_translations(base_dir, project_key, platform, **kwargs): | 
| + """ | 
| + Download translation updates. | 
| + | 
| + Download updated translations from crowdin.net. | 
| + """ | 
| from buildtools.packager import readMetadata | 
| - metadata = readMetadata(baseDir, type) | 
| + metadata = readMetadata(base_dir, platform) | 
| basename = metadata.get('general', 'basename') | 
| - localeConfig = readLocaleConfig(baseDir, type, metadata) | 
| + locale_config = read_locale_config(base_dir, platform, metadata) | 
| import buildtools.localeTools as localeTools | 
| - localeTools.getTranslations(localeConfig, basename, key) | 
| + localeTools.getTranslations(locale_config, basename, project_key) | 
| -def generateDocs(baseDir, scriptName, opts, args, type): | 
| - if len(args) == 0: | 
| - print 'No target directory specified for the documentation' | 
| - usage(scriptName, type, 'docs') | 
| - return | 
| - targetDir = args[0] | 
| - | 
| - source_dir = os.path.join(baseDir, 'lib') | 
| +@argparse_command( | 
| + name='docs', platforms={'chrome'}, | 
| + arguments=( | 
| + make_argument('target_dir'), | 
| + make_argument('-q', '--quiet', help='Suppress JsDoc output'), | 
| + ) | 
| +) | 
| +def generate_docs(base_dir, target_dir, quiet, platform, **kwargs): | 
| + """ | 
| + Generate documentation (requires node.js). | 
| + | 
| + Generate documentation files and write them into the specified directory. | 
| + """ | 
| + source_dir = os.path.join(base_dir, 'lib') | 
| sources = [source_dir] | 
| - # JSDoc struggles wih huge objects: https://github.com/jsdoc3/jsdoc/issues/976 | 
| - if type == 'chrome': | 
| - sources = [os.path.join(source_dir, filename) for filename in os.listdir(source_dir) if filename != 'publicSuffixList.js'] | 
| + # JSDoc struggles wih huge objects: | 
| + # https://github.com/jsdoc3/jsdoc/issues/976 | 
| + if platform == 'chrome': | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Nit: This check can be removed, we have no other p
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| + sources = [os.path.join(source_dir, filename) | 
| + for filename in os.listdir(source_dir) | 
| + if filename != 'publicSuffixList.js'] | 
| buildtools_path = os.path.dirname(__file__) | 
| config = os.path.join(buildtools_path, 'jsdoc.conf') | 
| - command = ['npm', 'run-script', 'jsdoc', '--', '--destination', targetDir, | 
| + command = ['npm', 'run-script', 'jsdoc', '--', '--destination', target_dir, | 
| '--configure', config] + sources | 
| - if any(opt in {'-q', '--quiet'} for opt, _ in opts): | 
| + if quiet: | 
| process = subprocess.Popen(command, stdout=subprocess.PIPE, | 
| stderr=subprocess.PIPE, cwd=buildtools_path) | 
| stderr = process.communicate()[1] | 
| @@ -349,167 +284,67 @@ def generateDocs(baseDir, scriptName, opts, args, type): | 
| subprocess.check_call(command, cwd=buildtools_path) | 
| -def runReleaseAutomation(baseDir, scriptName, opts, args, type): | 
| - keyFile = None | 
| - downloadsRepo = os.path.join(baseDir, '..', 'downloads') | 
| - for option, value in opts: | 
| - if option in {'-k', '--key'}: | 
| - keyFile = value | 
| - elif option in {'-d', '--downloads'}: | 
| - downloadsRepo = value | 
| - | 
| - if len(args) == 0: | 
| - print 'No version number specified for the release' | 
| - usage(scriptName, type, 'release') | 
| - return | 
| - version = args[0] | 
| - if re.search(r'[^\d\.]', version): | 
| - print 'Wrong version number format' | 
| - usage(scriptName, type, 'release') | 
| - return | 
| - | 
| - if type == 'chrome' and keyFile is None: | 
| - print >>sys.stderr, 'Error: you must specify a key file for this release' | 
| - usage(scriptName, type, 'release') | 
| - return | 
| +def valid_version_format(value): | 
| + if re.search(r'[^\d\.]', value): | 
| + raise argparse.ArgumentTypeError('Wrong version number format') | 
| + | 
| + return value | 
| + | 
| + | 
| +@argparse_command( | 
| + name='release', platforms={'chrome', 'edge'}, | 
| + arguments=( | 
| + make_argument( | 
| + '-k', '--key', dest='key_file', | 
| + help=('File containing private key and certificates required to ' | 
| + 'sign the release.')), | 
| + make_argument( | 
| + '-d', '--downloads-repository', dest='download_repository', | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Is the naming download_repository rather than down
 
tlucas
2017/11/17 11:36:44
No, this was not intentional. Done.
 
 | 
| + default='../downloads', | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
Please use os.path.join() here. Also, the default
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| + help=('Directory containing downloads repository (if omitted ' | 
| + '../downloads is assumed)')), | 
| + make_argument( | 
| + 'version', nargs='?', help='Version number of the release', | 
| 
 
Wladimir Palant
2017/11/17 09:26:10
This parameter isn't optional.
 
tlucas
2017/11/17 11:36:44
Done.
 
 | 
| + type=valid_version_format) | 
| + ) | 
| +) | 
| +def run_release_automation(base_dir, download_repository, key_file, platform, | 
| + version, **kwargs): | 
| + """ | 
| + Run release automation. | 
| + | 
| + Note: If you are not the project owner then you probably don't want to run | 
| + this! | 
| + | 
| + Run release automation: create downloads for the new version, tag source | 
| + code repository as well as downloads and buildtools repository. | 
| + """ | 
| + if platform == 'chrome' and key_file is None: | 
| + raise ValueError('You must specify a key file for this release') | 
| import buildtools.releaseAutomation as releaseAutomation | 
| - releaseAutomation.run(baseDir, type, version, keyFile, downloadsRepo) | 
| + releaseAutomation.run(base_dir, platform, version, key_file, | 
| + download_repository) | 
| + | 
| +@argparse_command(name='updatepsl', platforms={'chrome'}) | 
| +def update_psl(base_dir, **kwargs): | 
| + """Update Public Suffix List. | 
| -def updatePSL(baseDir, scriptName, opts, args, type): | 
| + Downloads Public Suffix List (see http://publicsuffix.org/) and generates | 
| + lib/publicSuffixList.js from it. | 
| + """ | 
| import buildtools.publicSuffixListUpdater as publicSuffixListUpdater | 
| - publicSuffixListUpdater.updatePSL(baseDir) | 
| - | 
| - | 
| -with addCommand(lambda baseDir, scriptName, opts, args, type: usage(scriptName, type), ('help', '-h', '--help')) as command: | 
| - command.shortDescription = 'Show this message' | 
| - | 
| -with addCommand(runBuild, 'build') as command: | 
| - command.shortDescription = 'Create a build' | 
| - command.description = 'Creates an extension build with given file name. If output_file is missing a default name will be chosen.' | 
| - command.params = '[options] [output_file]' | 
| - command.addOption('Use given build number (if omitted the build number will be retrieved from Mercurial)', short='b', long='build', value='num') | 
| - command.addOption('File containing private key and certificates required to sign the package', short='k', long='key', value='file', types={'chrome'}) | 
| - command.addOption('Create a release build', short='r', long='release') | 
| - command.supportedTypes = {'gecko', 'chrome', 'edge'} | 
| - | 
| -with addCommand(createDevEnv, 'devenv') as command: | 
| - command.shortDescription = 'Set up a development environment' | 
| - command.description = 'Will set up or update the devenv folder as an unpacked extension folder for development.' | 
| - command.supportedTypes = {'gecko', 'chrome', 'edge'} | 
| - | 
| -with addCommand(setupTranslations, 'setuptrans') as command: | 
| - command.shortDescription = 'Sets up translation languages' | 
| - command.description = 'Sets up translation languages for the project on crowdin.net.' | 
| - command.params = '[options] project-key' | 
| - | 
| -with addCommand(updateTranslationMaster, 'translate') as command: | 
| - command.shortDescription = 'Updates translation master files' | 
| - command.description = 'Updates the translation master files in the project on crowdin.net.' | 
| - command.params = '[options] project-key' | 
| - | 
| -with addCommand(uploadTranslations, 'uploadtrans') as command: | 
| - command.shortDescription = 'Uploads existing translations' | 
| - command.description = 'Uploads already existing translations to the project on crowdin.net.' | 
| - command.params = '[options] project-key' | 
| - | 
| -with addCommand(getTranslations, 'gettranslations') as command: | 
| - command.shortDescription = 'Downloads translation updates' | 
| - command.description = 'Downloads updated translations from crowdin.net.' | 
| - command.params = '[options] project-key' | 
| - | 
| -with addCommand(generateDocs, 'docs') as command: | 
| - command.shortDescription = 'Generate documentation (requires node.js)' | 
| - command.description = ('Generate documentation files and write them into ' | 
| - 'the specified directory.') | 
| - command.addOption('Suppress JsDoc output', short='q', long='quiet') | 
| - command.params = '[options] <directory>' | 
| - command.supportedTypes = {'chrome'} | 
| - | 
| -with addCommand(runReleaseAutomation, 'release') as command: | 
| - command.shortDescription = 'Run release automation' | 
| - command.description = 'Note: If you are not the project owner then you ' "probably don't want to run this!\n\n" 'Runs release automation: creates downloads for the new version, tags ' 'source code repository as well as downloads and buildtools repository.' | 
| - command.addOption('File containing private key and certificates required to sign the release.', short='k', long='key', value='file', types={'chrome', 'edge'}) | 
| - command.addOption('Directory containing downloads repository (if omitted ../downloads is assumed)', short='d', long='downloads', value='dir') | 
| - command.params = '[options] <version>' | 
| - command.supportedTypes = {'chrome', 'edge'} | 
| - | 
| -with addCommand(updatePSL, 'updatepsl') as command: | 
| - command.shortDescription = 'Updates Public Suffix List' | 
| - command.description = 'Downloads Public Suffix List (see http://publicsuffix.org/) and generates lib/publicSuffixList.js from it.' | 
| - command.supportedTypes = {'chrome'} | 
| - | 
| - | 
| -def getType(baseDir, scriptName, args): | 
| - # Look for an explicit type parameter (has to be the first parameter) | 
| - if len(args) >= 2 and args[0] == '-t': | 
| - type = args[1] | 
| - del args[1] | 
| - del args[0] | 
| - if type not in knownTypes: | 
| - print ''' | 
| -Unknown type %s specified, supported types are: %s | 
| -''' % (type, ', '.join(knownTypes)) | 
| - return None | 
| - return type | 
| - | 
| - # Try to guess repository type | 
| - types = [] | 
| - for t in knownTypes: | 
| - if os.path.exists(os.path.join(baseDir, 'metadata.%s' % t)): | 
| - types.append(t) | 
| - | 
| - if len(types) == 1: | 
| - return types[0] | 
| - elif len(types) > 1: | 
| - print ''' | 
| -Ambiguous repository type, please specify -t parameter explicitly, e.g. | 
| -%s -t %s build | 
| -''' % (scriptName, types[0]) | 
| - return None | 
| - else: | 
| - print ''' | 
| -No metadata file found in this repository, a metadata file like | 
| -metadata.* is required. | 
| -''' | 
| - return None | 
| - | 
| - | 
| -def processArgs(baseDir, args): | 
| - global commands | 
| - | 
| - scriptName = os.path.basename(args[0]) | 
| - args = args[1:] | 
| - type = getType(baseDir, scriptName, args) | 
| - if type == None: | 
| - return | 
| - | 
| - if len(args) == 0: | 
| - args = ['build'] | 
| - print ''' | 
| -No command given, assuming "build". For a list of commands run: | 
| - | 
| - %s help | 
| -''' % scriptName | 
| - | 
| - command = args[0] | 
| - if command in commands: | 
| - if commands[command].isSupported(type): | 
| - try: | 
| - opts, args = commands[command].parseArgs(type, args[1:]) | 
| - except GetoptError as e: | 
| - print str(e) | 
| - usage(scriptName, type, command) | 
| - sys.exit(2) | 
| - for option, value in opts: | 
| - if option in {'-h', '--help'}: | 
| - usage(scriptName, type, command) | 
| - sys.exit() | 
| - commands[command](baseDir, scriptName, opts, args, type) | 
| - else: | 
| - print 'Command %s is not supported for this application type' % command | 
| - usage(scriptName, type) | 
| - else: | 
| - print 'Command %s is unrecognized' % command | 
| - usage(scriptName, type) | 
| + publicSuffixListUpdater.updatePSL(base_dir) | 
| + | 
| + | 
| +def process_args(base_dir, *args): | 
| + parser.set_defaults(base_dir=base_dir) | 
| + | 
| + # If no args are provided, this module is run directly from the command | 
| + # line. argparse will take care of consuming sys.argv. | 
| + arguments = parser.parse_args(args if len(args) > 0 else None) | 
| + | 
| + function = arguments.function | 
| + del arguments.function | 
| + function(**vars(arguments)) |