diff options
Diffstat (limited to 'tools/addon-sdk-1.7/python-lib')
209 files changed, 17391 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/__init__.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/__init__.py new file mode 100644 index 0000000..9792eb9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/__init__.py @@ -0,0 +1,797 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 sys +import os +import optparse +import webbrowser + +from copy import copy +import simplejson as json +from cuddlefish import packaging +from cuddlefish._version import get_versions + +MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary' +MOZRUNNER_BIN_NOT_FOUND_HELP = """ +I can't find the application binary in any of its default locations +on your system. Please specify one using the -b/--binary option. +""" + +UPDATE_RDF_FILENAME = "%s.update.rdf" +XPI_FILENAME = "%s.xpi" + +usage = """ +%prog [options] command [command-specific options] + +Supported Commands: + docs - view web-based documentation + init - create a sample addon in an empty directory + test - run tests + run - run program + xpi - generate an xpi + +Internal Commands: + sdocs - export static documentation + testcfx - test the cfx tool + testex - test all example code + testpkgs - test all installed packages + testall - test whole environment + +Experimental and internal commands and options are not supported and may be +changed or removed in the future. +""" + +global_options = [ + (("-v", "--verbose",), dict(dest="verbose", + help="enable lots of output", + action="store_true", + default=False)), + ] + +parser_groups = ( + ("Supported Command-Specific Options", [ + (("", "--update-url",), dict(dest="update_url", + help="update URL in install.rdf", + metavar=None, + default=None, + cmds=['xpi'])), + (("", "--update-link",), dict(dest="update_link", + help="generate update.rdf", + metavar=None, + default=None, + cmds=['xpi'])), + (("-p", "--profiledir",), dict(dest="profiledir", + help=("profile directory to pass to " + "app"), + metavar=None, + default=None, + cmds=['test', 'run', 'testex', + 'testpkgs', 'testall'])), + (("-b", "--binary",), dict(dest="binary", + help="path to app binary", + metavar=None, + default=None, + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("", "--binary-args",), dict(dest="cmdargs", + help=("additional arguments passed to the " + "binary"), + metavar=None, + default=None, + cmds=['run', 'test'])), + (("", "--dependencies",), dict(dest="dep_tests", + help="include tests for all deps", + action="store_true", + default=False, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + (("", "--times",), dict(dest="iterations", + type="int", + help="number of times to run tests", + default=1, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + (("-f", "--filter",), dict(dest="filter", + help=("only run tests whose filenames " + "match FILENAME and optionally " + "match TESTNAME, both regexps"), + metavar="FILENAME[:TESTNAME]", + default=None, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + (("-g", "--use-config",), dict(dest="config", + help="use named config from local.json", + metavar=None, + default="default", + cmds=['test', 'run', 'xpi', 'testex', + 'testpkgs', 'testall'])), + (("", "--templatedir",), dict(dest="templatedir", + help="XULRunner app/ext. template", + metavar=None, + default=None, + cmds=['run', 'xpi'])), + (("", "--package-path",), dict(dest="packagepath", action="append", + help="extra directories for package search", + metavar=None, + default=[], + cmds=['run', 'xpi', 'test'])), + (("", "--extra-packages",), dict(dest="extra_packages", + help=("extra packages to include, " + "comma-separated. Default is " + "'addon-kit'."), + metavar=None, + default="addon-kit", + cmds=['run', 'xpi', 'test', 'testex', + 'testpkgs', 'testall', + 'testcfx'])), + (("", "--pkgdir",), dict(dest="pkgdir", + help=("package dir containing " + "package.json; default is " + "current directory"), + metavar=None, + default=None, + cmds=['run', 'xpi', 'test'])), + (("", "--static-args",), dict(dest="static_args", + help="extra harness options as JSON", + type="json", + metavar=None, + default="{}", + cmds=['run', 'xpi'])), + ] + ), + + ("Experimental Command-Specific Options", [ + (("-a", "--app",), dict(dest="app", + help=("app to run: firefox (default), fennec, " + "fennec-on-device, xulrunner or " + "thunderbird"), + metavar=None, + type="choice", + choices=["firefox", "fennec", + "fennec-on-device", "thunderbird", + "xulrunner"], + default="firefox", + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("", "--no-run",), dict(dest="no_run", + help=("Instead of launching the " + "application, just show the command " + "for doing so. Use this to launch " + "the application in a debugger like " + "gdb."), + action="store_true", + default=False, + cmds=['run', 'test'])), + (("", "--no-strip-xpi",), dict(dest="no_strip_xpi", + help="retain unused modules in XPI", + action="store_true", + default=False, + cmds=['xpi'])), + (("", "--force-mobile",), dict(dest="enable_mobile", + help="Force compatibility with Firefox Mobile", + action="store_true", + default=False, + cmds=['run', 'test', 'xpi', 'testall'])), + (("", "--mobile-app",), dict(dest="mobile_app_name", + help=("Name of your Android application to " + "use. Possible values: 'firefox', " + "'firefox_beta', 'fennec_aurora', " + "'fennec' (for nightly)."), + metavar=None, + default=None, + cmds=['run', 'test', 'testall'])), + (("", "--harness-option",), dict(dest="extra_harness_option_args", + help=("Extra properties added to " + "harness-options.json"), + action="append", + metavar="KEY=VALUE", + default=[], + cmds=['xpi'])), + (("", "--stop-on-error",), dict(dest="stopOnError", + help="Stop running tests after the first failure", + action="store_true", + metavar=None, + default=False, + cmds=['test', 'testex', 'testpkgs'])), + ] + ), + + ("Internal Command-Specific Options", [ + (("", "--addons",), dict(dest="addons", + help=("paths of addons to install, " + "comma-separated"), + metavar=None, + default=None, + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("", "--baseurl",), dict(dest="baseurl", + help=("root of static docs tree: " + "for example: 'http://me.com/the_docs/'"), + metavar=None, + default='', + cmds=['sdocs'])), + (("", "--test-runner-pkg",), dict(dest="test_runner_pkg", + help=("name of package " + "containing test runner " + "program (default is " + "test-harness)"), + default="test-harness", + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + # --keydir was removed in 1.0b5, but we keep it around in the options + # parser to make life easier for frontends like FlightDeck which + # might still pass it. It can go away once the frontends are updated. + (("", "--keydir",), dict(dest="keydir", + help=("obsolete, ignored"), + metavar=None, + default=None, + cmds=['test', 'run', 'xpi', 'testex', + 'testpkgs', 'testall'])), + (("", "--e10s",), dict(dest="enable_e10s", + help="enable out-of-process Jetpacks", + action="store_true", + default=False, + cmds=['test', 'run', 'testex', 'testpkgs'])), + (("", "--logfile",), dict(dest="logfile", + help="log console output to file", + metavar=None, + default=None, + cmds=['run', 'test', 'testex', 'testpkgs'])), + # TODO: This should default to true once our memory debugging + # issues are resolved; see bug 592774. + (("", "--profile-memory",), dict(dest="profileMemory", + help=("profile memory usage " + "(default is false)"), + type="int", + action="store", + default=0, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + ] + ), + ) + +def find_parent_package(cur_dir): + tail = True + while tail: + if os.path.exists(os.path.join(cur_dir, 'package.json')): + return cur_dir + cur_dir, tail = os.path.split(cur_dir) + return None + +def check_json(option, opt, value): + # We return the parsed JSON here; see bug 610816 for background on why. + try: + return json.loads(value) + except ValueError: + raise optparse.OptionValueError("Option %s must be JSON." % opt) + +class CfxOption(optparse.Option): + TYPES = optparse.Option.TYPES + ('json',) + TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER['json'] = check_json + +def parse_args(arguments, global_options, usage, version, parser_groups, + defaults=None): + parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption, + version=version) + + def name_cmp(a, b): + # a[0] = name sequence + # a[0][0] = short name (possibly empty string) + # a[0][1] = long name + names = [] + for seq in (a, b): + names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:]) + return cmp(*names) + + global_options.sort(name_cmp) + for names, opts in global_options: + parser.add_option(*names, **opts) + + for group_name, options in parser_groups: + group = optparse.OptionGroup(parser, group_name) + options.sort(name_cmp) + for names, opts in options: + if 'cmds' in opts: + cmds = opts['cmds'] + del opts['cmds'] + cmds.sort() + if not 'help' in opts: + opts['help'] = "" + opts['help'] += " (%s)" % ", ".join(cmds) + group.add_option(*names, **opts) + parser.add_option_group(group) + + if defaults: + parser.set_defaults(**defaults) + + (options, args) = parser.parse_args(args=arguments) + + if not args: + parser.print_help() + parser.exit() + + return (options, args) + +# all tests emit progress messages to stderr, not stdout. (the mozrunner +# console output goes to stderr and is hard to change, and +# unittest.TextTestRunner prefers stderr, so we send everything else there +# too, to keep all the messages in order) + +def test_all(env_root, defaults): + fail = False + + print >>sys.stderr, "Testing cfx..." + sys.stderr.flush() + result = test_cfx(env_root, defaults['verbose']) + if result.failures or result.errors: + fail = True + + if not fail or not defaults.get("stopOnError"): + print >>sys.stderr, "Testing all examples..." + sys.stderr.flush() + + try: + test_all_examples(env_root, defaults) + except SystemExit, e: + fail = (e.code != 0) or fail + + if not fail or not defaults.get("stopOnError"): + print >>sys.stderr, "Testing all packages..." + sys.stderr.flush() + try: + test_all_packages(env_root, defaults) + except SystemExit, e: + fail = (e.code != 0) or fail + + if fail: + print >>sys.stderr, "Some tests were unsuccessful." + sys.exit(1) + print >>sys.stderr, "All tests were successful. Ship it!" + sys.exit(0) + +def test_cfx(env_root, verbose): + import cuddlefish.tests + + # tests write to stderr. flush everything before and after to avoid + # confusion later. + sys.stdout.flush(); sys.stderr.flush() + olddir = os.getcwd() + os.chdir(env_root) + retval = cuddlefish.tests.run(verbose) + os.chdir(olddir) + sys.stdout.flush(); sys.stderr.flush() + return retval + +def test_all_examples(env_root, defaults): + examples_dir = os.path.join(env_root, "examples") + examples = [dirname for dirname in os.listdir(examples_dir) + if os.path.isdir(os.path.join(examples_dir, dirname))] + examples.sort() + fail = False + for dirname in examples: + print >>sys.stderr, "Testing %s..." % dirname + sys.stderr.flush() + try: + run(arguments=["test", + "--pkgdir", + os.path.join(examples_dir, dirname)], + defaults=defaults, + env_root=env_root) + except SystemExit, e: + fail = (e.code != 0) or fail + if fail and defaults.get("stopOnError"): + break + + if fail: + print >>sys.stderr, "Some examples tests were unsuccessful." + sys.exit(-1) + +def test_all_packages(env_root, defaults): + packages_dir = os.path.join(env_root, "packages") + packages = [dirname for dirname in os.listdir(packages_dir) + if os.path.isdir(os.path.join(packages_dir, dirname))] + packages.sort() + print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages)) + sys.stderr.flush() + fail = False + for dirname in packages: + print >>sys.stderr, "Testing %s..." % dirname + sys.stderr.flush() + try: + run(arguments=["test", + "--pkgdir", + os.path.join(packages_dir, dirname)], + defaults=defaults, + env_root=env_root) + except SystemExit, e: + fail = (e.code != 0) or fail + if fail and defaults.get('stopOnError'): + break + if fail: + print >>sys.stderr, "Some package tests were unsuccessful." + sys.exit(-1) + +def get_config_args(name, env_root): + local_json = os.path.join(env_root, "local.json") + if not (os.path.exists(local_json) and + os.path.isfile(local_json)): + if name == "default": + return [] + else: + print >>sys.stderr, "File does not exist: %s" % local_json + sys.exit(1) + local_json = packaging.load_json_file(local_json) + if 'configs' not in local_json: + print >>sys.stderr, "'configs' key not found in local.json." + sys.exit(1) + if name not in local_json.configs: + if name == "default": + return [] + else: + print >>sys.stderr, "No config found for '%s'." % name + sys.exit(1) + config = local_json.configs[name] + if type(config) != list: + print >>sys.stderr, "Config for '%s' must be a list of strings." % name + sys.exit(1) + return config + +def initializer(env_root, args, out=sys.stdout, err=sys.stderr): + from templates import MAIN_JS, PACKAGE_JSON, README_DOC, MAIN_JS_DOC, TEST_MAIN_JS + path = os.getcwd() + addon = os.path.basename(path) + # if more than one argument + if len(args) > 1: + print >>err, 'Too many arguments.' + return 1 + # avoid clobbering existing files, but we tolerate things like .git + existing = [fn for fn in os.listdir(path) if not fn.startswith(".")] + if existing: + print >>err, 'This command must be run in an empty directory.' + return 1 + for d in ['lib','data','test','doc']: + os.mkdir(os.path.join(path,d)) + print >>out, '*', d, 'directory created' + open('README.md','w').write(README_DOC % {'name':addon}) + print >>out, '* README.md written' + open('package.json','w').write(PACKAGE_JSON % {'name':addon.lower(), + 'fullName':addon }) + print >>out, '* package.json written' + open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS) + print >>out, '* test/test-main.js written' + open(os.path.join(path,'lib','main.js'),'w').write(MAIN_JS) + print >>out, '* lib/main.js written' + open(os.path.join(path,'doc','main.md'),'w').write(MAIN_JS_DOC) + print >>out, '* doc/main.md written' + print >>out, '\nYour sample add-on is now ready.' + print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' + return 0 + +def buildJID(target_cfg): + if "id" in target_cfg: + jid = target_cfg["id"] + else: + import uuid + jid = str(uuid.uuid4()) + if not ("@" in jid or jid.startswith("{")): + jid = jid + "@jetpack" + return jid + +def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, + defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'), + stdout=sys.stdout): + versions = get_versions() + sdk_version = versions["version"] + display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"]) + parser_kwargs = dict(arguments=arguments, + global_options=global_options, + parser_groups=parser_groups, + usage=usage, + version=display_version, + defaults=defaults) + + (options, args) = parse_args(**parser_kwargs) + + config_args = get_config_args(options.config, env_root); + + # reparse configs with arguments from local.json + if config_args: + parser_kwargs['arguments'] += config_args + (options, args) = parse_args(**parser_kwargs) + + command = args[0] + + if command == "init": + initializer(env_root, args) + return + if command == "testpkgs": + test_all_packages(env_root, defaults=options.__dict__) + return + elif command == "testex": + test_all_examples(env_root, defaults=options.__dict__) + return + elif command == "testall": + test_all(env_root, defaults=options.__dict__) + return + elif command == "testcfx": + test_cfx(env_root, options.verbose) + return + elif command == "docs": + from cuddlefish.docs import generate + if len(args) > 1: + docs_home = generate.generate_named_file(env_root, filename=args[1]) + else: + docs_home = generate.generate_local_docs(env_root) + webbrowser.open(docs_home) + return + elif command == "sdocs": + from cuddlefish.docs import generate + filename = generate.generate_static_docs(env_root) + print >>stdout, "Wrote %s." % filename + return + elif command not in ["xpi", "test", "run"]: + print >>sys.stderr, "Unknown command: %s" % command + print >>sys.stderr, "Try using '--help' for assistance." + sys.exit(1) + + target_cfg_json = None + if not target_cfg: + if not options.pkgdir: + options.pkgdir = find_parent_package(os.getcwd()) + if not options.pkgdir: + print >>sys.stderr, ("cannot find 'package.json' in the" + " current directory or any parent.") + sys.exit(1) + else: + options.pkgdir = os.path.abspath(options.pkgdir) + if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): + print >>sys.stderr, ("cannot find 'package.json' in" + " %s." % options.pkgdir) + sys.exit(1) + + target_cfg_json = os.path.join(options.pkgdir, 'package.json') + target_cfg = packaging.get_config_in_dir(options.pkgdir) + + # At this point, we're either building an XPI or running Jetpack code in + # a Mozilla application (which includes running tests). + + use_main = False + inherited_options = ['verbose', 'enable_e10s'] + enforce_timeouts = False + + if command == "xpi": + use_main = True + elif command == "test": + if 'tests' not in target_cfg: + target_cfg['tests'] = [] + inherited_options.extend(['iterations', 'filter', 'profileMemory', + 'stopOnError']) + enforce_timeouts = True + elif command == "run": + use_main = True + else: + assert 0, "shouldn't get here" + + if use_main and 'main' not in target_cfg: + # If the user supplies a template dir, then the main + # program may be contained in the template. + if not options.templatedir: + print >>sys.stderr, "package.json does not have a 'main' entry." + sys.exit(1) + + if not pkg_cfg: + pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath) + + target = target_cfg.name + + # TODO: Consider keeping a cache of dynamic UUIDs, based + # on absolute filesystem pathname, in the root directory + # or something. + if command in ('xpi', 'run'): + from cuddlefish.preflight import preflight_config + if target_cfg_json: + config_was_ok, modified = preflight_config(target_cfg, + target_cfg_json) + if not config_was_ok: + if modified: + # we need to re-read package.json . The safest approach + # is to re-run the "cfx xpi"/"cfx run" command. + print >>sys.stderr, ("package.json modified: please re-run" + " 'cfx %s'" % command) + else: + print >>sys.stderr, ("package.json needs modification:" + " please update it and then re-run" + " 'cfx %s'" % command) + sys.exit(1) + # if we make it this far, we have a JID + else: + assert command == "test" + + jid = buildJID(target_cfg) + + targets = [target] + if command == "test": + targets.append(options.test_runner_pkg) + + extra_packages = [] + if options.extra_packages: + extra_packages = options.extra_packages.split(",") + if extra_packages: + targets.extend(extra_packages) + target_cfg.extra_dependencies = extra_packages + + deps = packaging.get_deps_for_targets(pkg_cfg, targets) + + from cuddlefish.manifest import build_manifest, ModuleNotFoundError + # Figure out what loader files should be scanned. This is normally + # computed inside packaging.generate_build_for_target(), by the first + # dependent package that defines a "loader" property in its package.json. + # This property is interpreted as a filename relative to the top of that + # file, and stored as a path in build.loader . generate_build_for_target() + # cannot be called yet (it needs the list of used_deps that + # build_manifest() computes, but build_manifest() needs the list of + # loader files that it computes). We could duplicate or factor out this + # build.loader logic, but that would be messy, so instead we hard-code + # the choice of loader for manifest-generation purposes. In practice, + # this means that alternative loaders probably won't work with + # --strip-xpi. + assert packaging.DEFAULT_LOADER == "api-utils" + assert pkg_cfg.packages["api-utils"].loader == "lib/cuddlefish.js" + cuddlefish_js_path = os.path.join(pkg_cfg.packages["api-utils"].root_dir, + "lib", "cuddlefish.js") + loader_modules = [("api-utils", "lib", "cuddlefish", cuddlefish_js_path)] + scan_tests = command == "test" + test_filter_re = None + if scan_tests and options.filter: + test_filter_re = options.filter + if ":" in options.filter: + test_filter_re = options.filter.split(":")[0] + try: + manifest = build_manifest(target_cfg, pkg_cfg, deps, + scan_tests, test_filter_re, + loader_modules) + except ModuleNotFoundError, e: + print str(e) + sys.exit(1) + used_deps = manifest.get_used_packages() + if command == "test": + # The test runner doesn't appear to link against any actual packages, + # because it loads everything at runtime (invisible to the linker). + # If we believe that, we won't set up URI mappings for anything, and + # tests won't be able to run. + used_deps = deps + for xp in extra_packages: + if xp not in used_deps: + used_deps.append(xp) + + build = packaging.generate_build_for_target( + pkg_cfg, target, used_deps, + include_dep_tests=options.dep_tests + ) + + harness_options = { + 'jetpackID': jid, + 'staticArgs': options.static_args, + 'name': target, + } + + harness_options.update(build) + + extra_environment = {} + if command == "test": + # This should be contained in the test runner package. + # maybe just do: target_cfg.main = 'test-harness/run-tests' + harness_options['main'] = 'test-harness/run-tests' + harness_options['mainPath'] = manifest.get_manifest_entry("test-harness", "lib", "run-tests").get_path() + else: + harness_options['main'] = target_cfg.get('main') + harness_options['mainPath'] = manifest.top_path + extra_environment["CFX_COMMAND"] = command + + for option in inherited_options: + harness_options[option] = getattr(options, option) + + harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps) + + harness_options['sdkVersion'] = sdk_version + + packaging.call_plugins(pkg_cfg, used_deps) + + retval = 0 + + if options.templatedir: + app_extension_dir = os.path.abspath(options.templatedir) + else: + mydir = os.path.dirname(os.path.abspath(__file__)) + app_extension_dir = os.path.join(mydir, "app-extension") + + + if target_cfg.get('preferences'): + harness_options['preferences'] = target_cfg.get('preferences') + + harness_options['manifest'] = manifest.get_harness_options_manifest() + harness_options['allTestModules'] = manifest.get_all_test_modules() + + from cuddlefish.rdf import gen_manifest, RDFUpdate + + manifest_rdf = gen_manifest(template_root_dir=app_extension_dir, + target_cfg=target_cfg, + jid=jid, + update_url=options.update_url, + bootstrap=True, + enable_mobile=options.enable_mobile) + + if command == "xpi" and options.update_link: + rdf_name = UPDATE_RDF_FILENAME % target_cfg.name + print >>stdout, "Exporting update description to %s." % rdf_name + update = RDFUpdate() + update.add(manifest_rdf, options.update_link) + open(rdf_name, "w").write(str(update)) + + # ask the manifest what files were used, so we can construct an XPI + # without the rest. This will include the loader (and everything it + # uses) because of the "loader_modules" starting points we passed to + # build_manifest earlier + used_files = None + if command == "xpi": + used_files = set(manifest.get_used_files()) + + if options.no_strip_xpi: + used_files = None # disables the filter, includes all files + + if command == 'xpi': + from cuddlefish.xpi import build_xpi + extra_harness_options = {} + for kv in options.extra_harness_option_args: + key,value = kv.split("=", 1) + extra_harness_options[key] = value + xpi_path = XPI_FILENAME % target_cfg.name + print >>stdout, "Exporting extension to %s." % xpi_path + build_xpi(template_root_dir=app_extension_dir, + manifest=manifest_rdf, + xpi_path=xpi_path, + harness_options=harness_options, + limit_to=used_files, + extra_harness_options=extra_harness_options) + else: + from cuddlefish.runner import run_app + + if options.profiledir: + options.profiledir = os.path.expanduser(options.profiledir) + options.profiledir = os.path.abspath(options.profiledir) + + if options.addons is not None: + options.addons = options.addons.split(",") + + try: + retval = run_app(harness_root_dir=app_extension_dir, + manifest_rdf=manifest_rdf, + harness_options=harness_options, + app_type=options.app, + binary=options.binary, + profiledir=options.profiledir, + verbose=options.verbose, + enforce_timeouts=enforce_timeouts, + logfile=options.logfile, + addons=options.addons, + args=options.cmdargs, + extra_environment=extra_environment, + norun=options.no_run, + used_files=used_files, + enable_mobile=options.enable_mobile, + mobile_app_name=options.mobile_app_name) + except ValueError, e: + print "" + print "A given cfx option has an inappropriate value:" + print >>sys.stderr, " " + " \n ".join(str(e).split("\n")) + retval = -1 + except Exception, e: + if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): + print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() + retval = -1 + else: + raise + sys.exit(retval) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/_version.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/_version.py new file mode 100644 index 0000000..61e7178 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/_version.py @@ -0,0 +1,171 @@ + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (build by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by versioneer-0.6 +# (https://github.com/warner/python-versioneer) + +# these strings will be replaced by git during git-archive +git_refnames = " (HEAD, 1.7rc2, 1.7, origin/stabilization, stabilization)" +git_full = "075becdd649500d845fa15aea09129e6aef02bef" + + +import subprocess + +def run_command(args, cwd=None, verbose=False): + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) + except EnvironmentError, e: + if verbose: + print "unable to run %s" % args[0] + print e + return None + stdout = p.communicate()[0].strip() + if p.returncode != 0: + if verbose: + print "unable to run %s (error)" % args[0] + return None + return stdout + + +import sys +import re +import os.path + +def get_expanded_variables(versionfile_source): + # the code embedded in _version.py can just fetch the value of these + # variables. When used from setup.py, we don't want to import + # _version.py, so we do it with a regexp instead. This function is not + # used from _version.py. + variables = {} + try: + for line in open(versionfile_source,"r").readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["full"] = mo.group(1) + except EnvironmentError: + pass + return variables + +def versions_from_expanded_variables(variables, tag_prefix): + refnames = variables["refnames"].strip() + if refnames.startswith("$Format"): + return {} # unexpanded, so not in an unpacked git-archive tarball + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + for ref in list(refs): + if not re.search(r'\d', ref): + refs.discard(ref) + # Assume all version tags have a digit. git's %d expansion + # behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us + # distinguish between branches and tags. By ignoring refnames + # without digits, we filter out many common branch names like + # "release" and "stabilization", as well as "HEAD" and "master". + for ref in sorted(refs): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + return { "version": r, + "full": variables["full"].strip() } + # no suitable tags, so we use the full revision id + return { "version": variables["full"].strip(), + "full": variables["full"].strip() } + +def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): + # this runs 'git' from the root of the source tree. That either means + # someone ran a setup.py command (and this code is in versioneer.py, thus + # the containing directory is the root of the source tree), or someone + # ran a project-specific entry point (and this code is in _version.py, + # thus the containing directory is somewhere deeper in the source tree). + # This only gets called if the git-archive 'subst' variables were *not* + # expanded, and _version.py hasn't already been rewritten with a short + # version string, meaning we're inside a checked out source tree. + + try: + here = os.path.abspath(__file__) + except NameError: + # some py2exe/bbfreeze/non-CPython implementations don't do __file__ + return {} # not always correct + + # versionfile_source is the relative path from the top of the source tree + # (where the .git directory might live) to this file. Invert this to find + # the root from __file__. + root = here + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + if not os.path.exists(os.path.join(root, ".git")): + return {} + + GIT = "git" + if sys.platform == "win32": + GIT = "git.cmd" + stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + cwd=root) + if stdout is None: + return {} + if not stdout.startswith(tag_prefix): + if verbose: + print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix) + return {} + tag = stdout[len(tag_prefix):] + stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + if stdout is None: + return {} + full = stdout.strip() + if tag.endswith("-dirty"): + full += "-dirty" + return {"version": tag, "full": full} + + +def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): + try: + here = os.path.abspath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to _version.py, when + # this is used by the runtime. Invert this to find the root from + # __file__. + root = here + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + except NameError: + # try a couple different things to handle py2exe, bbfreeze, and + # non-CPython implementations which don't do __file__. This code + # either lives in versioneer.py (used by setup.py) or _version.py + # (used by the runtime). In the versioneer.py case, sys.argv[0] will + # be setup.py, in the root of the source tree. In the _version.py + # case, we have no idea what sys.argv[0] is (some + # application-specific runner). + root = os.path.dirname(os.path.abspath(sys.argv[0])) + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print "dirname '%s' doesn't start with prefix '%s'" % (dirname, parentdir_prefix) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} + +tag_prefix = "" +parentdir_prefix = "addon-sdk-" +versionfile_source = "python-lib/cuddlefish/_version.py" + +def get_versions(): + variables = { "refnames": git_refnames, "full": git_full } + ver = versions_from_expanded_variables(variables, tag_prefix) + if not ver: + ver = versions_from_vcs(tag_prefix, versionfile_source) + if not ver: + ver = versions_from_parentdir(parentdir_prefix, versionfile_source) + if not ver: + ver = {"version": "unknown", "full": ""} + return ver + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/application.ini b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/application.ini new file mode 100644 index 0000000..6cec69a --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/application.ini @@ -0,0 +1,11 @@ +[App] +Vendor=Varma +Name=Test App +Version=1.0 +BuildID=20060101 +Copyright=Copyright (c) 2009 Atul Varma +ID=xulapp@toolness.com + +[Gecko] +MinVersion=1.9.2.0 +MaxVersion=2.0.* diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/bootstrap.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/bootstrap.js new file mode 100644 index 0000000..661f2bb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/bootstrap.js @@ -0,0 +1,216 @@ +/* vim:set ts=2 sw=2 sts=2 expandtab */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp + +"use strict"; + +const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu, + results: Cr, manager: Cm } = Components; +const ioService = Cc['@mozilla.org/network/io-service;1']. + getService(Ci.nsIIOService); +const resourceHandler = ioService.getProtocolHandler('resource') + .QueryInterface(Ci.nsIResProtocolHandler); +const XMLHttpRequest = CC('@mozilla.org/xmlextras/xmlhttprequest;1', + 'nsIXMLHttpRequest'); +const prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService). + QueryInterface(Ci.nsIPrefBranch2); +const mozIJSSubScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + +const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable', + 'install', 'uninstall', 'upgrade', 'downgrade' ]; + +let loader = null; +let loaderUri = null; + +const URI = __SCRIPT_URI_SPEC__.replace(/bootstrap\.js$/, ""); + +// Initializes default preferences +function setDefaultPrefs() { + let branch = prefs.getDefaultBranch(""); + let prefLoaderScope = { + pref: function(key, val) { + switch (typeof val) { + case "boolean": + branch.setBoolPref(key, val); + break; + case "number": + if (val % 1 == 0) // number must be a integer, otherwise ignore it + branch.setIntPref(key, val); + break; + case "string": + branch.setCharPref(key, val); + break; + } + } + }; + + let uri = ioService.newURI( + "defaults/preferences/prefs.js", + null, + ioService.newURI(URI, null, null)); + + // if there is a prefs.js file, then import the default prefs + try { + // setup default prefs + mozIJSSubScriptLoader.loadSubScript(uri.spec, prefLoaderScope); + } + // errors here should not kill addon + catch (e) { + Cu.reportError(e); + } +} + +// Gets the topic that fit best as application startup event, in according with +// the current application (e.g. Firefox, Fennec, Thunderbird...) +function getAppStartupTopic() { + // The following mapping of application names to GUIDs was taken + // from `xul-app` module. They should keep in sync, so if you change one, + // change the other too! + let ids = { + Firefox: '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}', + Mozilla: '{86c18b42-e466-45a9-ae7a-9b95ba6f5640}', + Sunbird: '{718e30fb-e89b-41dd-9da7-e25a45638b28}', + SeaMonkey: '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}', + Fennec: '{aa3c5121-dab2-40e2-81ca-7ea25febc110}', + Thunderbird: '{3550f703-e582-4d05-9a08-453d09bdfdc6}' + }; + + let id = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULAppInfo).ID; + + switch (id) { + case ids.Firefox: + case ids.SeaMonkey: + return 'sessionstore-windows-restored'; + case ids.Thunderbird: + return 'mail-startup-done'; + // Temporary, until Fennec Birch will support sessionstore event + case ids.Fennec: + default: + return 'final-ui-startup'; + } +} + +// Utility function that synchronously reads local resource from the given +// `uri` and returns content string. +function readURI(uri) { + let ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + let channel = ioservice.newChannel(uri, "UTF-8", null); + let stream = channel.open(); + + let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream); + cstream.init(stream, "UTF-8", 0, 0); + + let str = {}; + let data = ""; + let read = 0; + do { + read = cstream.readString(0xffffffff, str); + data += str.value; + } while (read != 0); + + cstream.close(); + + return data; +} + +// Function takes `topic` to be observer via `nsIObserverService` and returns +// promise that will be delivered once notification is published. +function on(topic) { + return function promise(deliver) { + const observerService = Cc['@mozilla.org/observer-service;1']. + getService(Ci.nsIObserverService); + + observerService.addObserver({ + observe: function observer(subject, topic, data) { + observerService.removeObserver(this, topic); + deliver(subject, topic, data); + } + }, topic, false); + } +} + +// We don't do anything on install & uninstall yet, but in a future +// we should allow add-ons to cleanup after uninstall. +function install(data, reason) {} +function uninstall(data, reason) {} + +function startup(data, reason) { + // TODO: When bug 564675 is implemented this will no longer be needed + // Always set the default prefs, because they disappear on restart + setDefaultPrefs(); + + // TODO: Maybe we should perform read harness-options.json asynchronously, + // since we can't do anything until 'sessionstore-windows-restored' anyway. + let options = JSON.parse(readURI(URI + './harness-options.json')); + options.loadReason = REASON[reason]; + + // URI for the root of the XPI file. + // 'jar:' URI if the addon is packed, 'file:' URI otherwise. + // (Used by l10n module in order to fetch `locale` folder) + options.rootURI = data.resourceURI.spec; + + // Register a new resource "domain" for this addon which is mapping to + // XPI's `resources` folder. + // Generate the domain name by using jetpack ID, which is the extension ID + // by stripping common characters that doesn't work as a domain name: + let uuidRe = + /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/; + let domain = options.jetpackID.toLowerCase() + .replace(/@/g, "-at-") + .replace(/\./g, "-dot-") + .replace(uuidRe, "$1"); + + let resourcesUri = ioService.newURI(URI + '/resources/', null, null); + resourceHandler.setSubstitution(domain, resourcesUri); + options.uriPrefix = "resource://" + domain + "/"; + + // Import loader module using `Cu.imports` and bootstrap module loader. + loaderUri = options.uriPrefix + options.loader; + loader = Cu.import(loaderUri).Loader.new(options); + + // Creating a promise, that will be delivered once application is ready. + // If application is at startup then promise is delivered on + // the application startup topic, otherwise it's delivered immediately. + let promise = reason === APP_STARTUP ? on(getAppStartupTopic()) : + function promise(deliver) deliver() + + // Once application is ready we spawn a new process with main module of + // on add-on. + promise(function() { + try { + loader.spawn(options.main, options.mainPath); + } catch (error) { + // If at this stage we have an error only thing we can do is report about + // it via error console. Keep in mind that error won't automatically show + // up there when called via observerService. + Cu.reportError(error); + throw error; + } + }); + +}; + +function shutdown(data, reason) { + // If loader is already present unload it, since add-on is disabled. + if (loader) { + reason = REASON[reason]; + let system = loader.require('api-utils/system'); + loader.unload(reason); + + // Bug 724433: We need to unload JSM otherwise it will stay alive + // and keep a reference to this compartment. + Cu.unload(loaderUri); + loader = null; + + // If add-on is lunched via `cfx run` we need to use `system.exit` to let + // cfx know we're done (`cfx test` will take care of exit so we don't do + // anything here). + if (system.env.CFX_COMMAND === 'run' && reason === 'shutdown') + system.exit(0); + } +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/install.rdf b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/install.rdf new file mode 100644 index 0000000..8bbcacb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/app-extension/install.rdf @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + + +<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>xulapp@toolness.com</em:id> + <em:version>1.0</em:version> + <em:type>2</em:type> + <em:bootstrap>true</em:bootstrap> + <em:unpack>false</em:unpack> + + <!-- Firefox --> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:minVersion>12.0</em:minVersion> + <em:maxVersion>13.*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Test App</em:name> + <em:description>Harness for tests.</em:description> + <em:creator>Mozilla Corporation</em:creator> + <em:homepageURL></em:homepageURL> + <em:optionsType></em:optionsType> + <em:updateURL></em:updateURL> + </Description> +</RDF> diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/bunch.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/bunch.py new file mode 100644 index 0000000..5efa79f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/bunch.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +# Taken from Paver's paver.options module. + +class Bunch(dict): + """A dictionary that provides attribute-style access.""" + + def __repr__(self): + keys = self.keys() + keys.sort() + args = ', '.join(['%s=%r' % (key, self[key]) for key in keys]) + return '%s(%s)' % (self.__class__.__name__, args) + + def __getitem__(self, key): + item = dict.__getitem__(self, key) + if callable(item): + return item() + return item + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + raise AttributeError(name) + + __setattr__ = dict.__setitem__ + + def __delattr__(self, name): + try: + del self[name] + except KeyError: + raise AttributeError(name) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/__init__.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/__init__.py new file mode 100644 index 0000000..5501cd4 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/__init__.py @@ -0,0 +1,4 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apiparser.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apiparser.py new file mode 100644 index 0000000..b6ccf22 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apiparser.py @@ -0,0 +1,392 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 sys, re, textwrap + +VERSION = 4 + +class ParseError(Exception): + # args[1] is the line number that caused the problem + def __init__(self, why, lineno): + self.why = why + self.lineno = lineno + def __str__(self): + return ("ParseError: the JS API docs were unparseable on line %d: %s" % + (self.lineno, self.why)) + +class Accumulator: + def __init__(self, holder, firstline): + self.holder = holder + self.firstline = firstline + self.otherlines = [] + def addline(self, line): + self.otherlines.append(line) + def finish(self): + # take a list of strings like: + # "initial stuff" (this is in firstline) + # " more stuff" (this is in lines[0]) + # " yet more stuff" + # " indented block" + # " indented block" + # " nonindented stuff" (lines[-1]) + # + # calculate the indentation level by looking at all but the first + # line, and removing the whitespace they all have in common. Then + # join the results with newlines and return a single string. + pieces = [] + if self.firstline: + pieces.append(self.firstline) + if self.otherlines: + pieces.append(textwrap.dedent("\n".join(self.otherlines))) + self.holder["description"] = "\n".join(pieces) + + +class APIParser: + def parse(self, lines, lineno): + api = {"line_number": lineno + 1} +# assign the name from the first line, of the form "<api name="API_NAME">" + title_line = lines[lineno].rstrip("\n") + api["name"] = self._parse_title_line(title_line, lineno + 1) + lineno += 1 +# finished with the first line, assigned the name + working_set = self._initialize_working_set() + props = [] + currentPropHolder = api +# fetch the next line, of the form "@tag [name] {datatype} description" +# and parse it into tag, info, description + tag, info, firstline = self._parseTypeLine(lines[lineno], lineno + 1) + api["type"] = tag +# if this API element is a property then datatype must be set + if tag == 'property': + api['datatype'] = info['datatype'] + # info is ignored + currentAccumulator = Accumulator(api, firstline) + lineno += 1 + while (lineno) < len(lines): + line = lines[lineno].rstrip("\n") + # accumulate any multiline descriptive text belonging to + # the preceding "@" section + if self._is_description_line(line): + currentAccumulator.addline(line) + else: + currentAccumulator.finish() + if line.startswith("<api"): + # then we should recursively handle a nested element + nested_api, lineno = self.parse(lines, lineno) + self._update_working_set(nested_api, working_set) + elif line.startswith("</api"): + # then we have finished parsing this api element + currentAccumulator.finish() + if props and currentPropHolder: + currentPropHolder["props"] = props + self._assemble_api_element(api, working_set) + return api, lineno + else: + # then we are looking at a subcomponent of an <api> element + tag, info, desc = self._parseTypeLine(line, lineno + 1) + currentAccumulator = Accumulator(info, desc) + if tag == "prop": + # build up props[] + props.append(info) + elif tag == "returns": + # close off the @prop list + if props and currentPropHolder: + currentPropHolder["props"] = props + props = [] + api["returns"] = info + currentPropHolder = info + elif tag == "param": + # close off the @prop list + if props and currentPropHolder: + currentPropHolder["props"] = props + props = [] + working_set["params"].append(info) + currentPropHolder = info + elif tag == "argument": + # close off the @prop list + if props and currentPropHolder: + currentPropHolder["props"] = props + props = [] + working_set["arguments"].append(info) + currentPropHolder = info + else: + raise ParseError("unknown '@' section header %s in \ + '%s'" % (tag, line), lineno + 1) + lineno += 1 + raise ParseError("closing </api> tag not found for <api name=\"" + + api["name"] + "\">", lineno + 1) + + def _parse_title_line(self, title_line, lineno): + if "name" not in title_line: + raise ParseError("Opening <api> tag must have a name attribute.", + lineno) + m = re.search("name=['\"]{0,1}([-\w\.]*?)['\"]", title_line) + if not m: + raise ParseError("No value for name attribute found in " + "opening <api> tag.", lineno) + return m.group(1) + + def _is_description_line(self, line): + return not ( (line.lstrip().startswith("@")) or + (line.lstrip().startswith("<api")) or + (line.lstrip().startswith("</api")) ) + + def _initialize_working_set(self): + # working_set accumulates api elements + # that might belong to a parent api element + working_set = {} + working_set["constructors"] = [] + working_set["methods"] = [] + working_set["properties"] = [] + working_set["params"] = [] + working_set["events"] = [] + working_set["arguments"] = [] + return working_set + + def _update_working_set(self, nested_api, working_set): + # add this api element to whichever list is appropriate + if nested_api["type"] == "constructor": + working_set["constructors"].append(nested_api) + if nested_api["type"] == "method": + working_set["methods"].append(nested_api) + if nested_api["type"] == "property": + working_set["properties"].append(nested_api) + if nested_api["type"] == "event": + working_set["events"].append(nested_api) + + def _assemble_signature(self, api_element, params): + signature = api_element["name"] + "(" + if len(params) > 0: + signature += params[0]["name"] + for param in params[1:]: + signature += ", " + param["name"] + signature += ")" + api_element["signature"] = signature + + def _assemble_api_element(self, api_element, working_set): + # if any of this working set's lists are non-empty, + # add it to the current api element + if (api_element["type"] == "constructor") or \ + (api_element["type"] == "function") or \ + (api_element["type"] == "method"): + self._assemble_signature(api_element, working_set["params"]) + if len(working_set["params"]) > 0: + api_element["params"] = working_set["params"] + if len(working_set["properties"]) > 0: + api_element["properties"] = working_set["properties"] + if len(working_set["constructors"]) > 0: + api_element["constructors"] = working_set["constructors"] + if len(working_set["methods"]) > 0: + api_element["methods"] = working_set["methods"] + if len(working_set["events"]) > 0: + api_element["events"] = working_set["events"] + if len(working_set["arguments"]) > 0: + api_element["arguments"] = working_set["arguments"] + + def _validate_info(self, tag, info, line, lineno): + if tag == 'property': + if not 'datatype' in info: + raise ParseError("No type found for @property.", lineno) + elif tag == "param": + if info.get("required", False) and "default" in info: + raise ParseError( + "required parameters should not have defaults: '%s'" + % line, lineno) + elif tag == "prop": + if "datatype" not in info: + raise ParseError("@prop lines must include {type}: '%s'" % + line, lineno) + if "name" not in info: + raise ParseError("@prop lines must provide a name: '%s'" % + line, lineno) + + def _parseTypeLine(self, line, lineno): + # handle these things: + # @method + # @returns description + # @returns {string} description + # @param NAME {type} description + # @param NAME + # @prop NAME {type} description + # @prop NAME + # returns: + # tag: type of api element + # info: linenumber, required, default, name, datatype + # description + + info = {"line_number": lineno} + line = line.rstrip("\n") + pieces = line.split() + + if not pieces: + raise ParseError("line is too short: '%s'" % line, lineno) + if not pieces[0].startswith("@"): + raise ParseError("type line should start with @: '%s'" % line, + lineno) + tag = pieces[0][1:] + skip = 1 + + expect_name = tag in ("param", "prop") + + if len(pieces) == 1: + description = "" + else: + if pieces[1].startswith("{"): + # NAME is missing, pieces[1] is TYPE + pass + else: + if expect_name: + info["required"] = not pieces[1].startswith("[") + name = pieces[1].strip("[ ]") + if "=" in name: + name, info["default"] = name.split("=") + info["name"] = name + skip += 1 + + if len(pieces) > skip and pieces[skip].startswith("{"): + info["datatype"] = pieces[skip].strip("{ }") + skip += 1 + + # we've got the metadata, now extract the description + pieces = line.split(None, skip) + if len(pieces) > skip: + description = pieces[skip] + else: + description = "" + self._validate_info(tag, info, line, lineno) + return tag, info, description + +def parse_hunks(text): + # return a list of tuples. Each is one of: + # ("raw", string) : non-API blocks + # ("api-json", dict) : API blocks + yield ("version", VERSION) + lines = text.splitlines(True) + line_number = 0 + markdown_string = "" + while line_number < len(lines): + line = lines[line_number] + if line.startswith("<api"): + if len(markdown_string) > 0: + yield ("markdown", markdown_string) + markdown_string = "" + api, line_number = APIParser().parse(lines, line_number) + # this business with 'leftover' is a horrible thing to do, + # and exists only to collect the \n after the closing /api tag. + # It's not needed probably, except to help keep compatibility + # with the previous behaviour + leftover = lines[line_number].lstrip("</api>") + if len(leftover) > 0: + markdown_string += leftover + line_number = line_number + 1 + yield ("api-json", api) + else: + markdown_string += line + line_number = line_number + 1 + if len(markdown_string) > 0: + yield ("markdown", markdown_string) + +class TestRenderer: + # render docs for test purposes + + def getm(self, d, key): + return d.get(key, "<MISSING>") + + def join_lines(self, text): + return " ".join([line.strip() for line in text.split("\n")]) + + def render_prop(self, p): + s = "props[%s]: " % self.getm(p, "name") + pieces = [] + for k in ("type", "description", "required", "default"): + if k in p: + pieces.append("%s=%s" % (k, self.join_lines(str(p[k])))) + return s + ", ".join(pieces) + + def render_param(self, p): + pieces = [] + for k in ("name", "type", "description", "required", "default"): + if k in p: + pieces.append("%s=%s" % (k, self.join_lines(str(p[k])))) + yield ", ".join(pieces) + for prop in p.get("props", []): + yield " " + self.render_prop(prop) + + def render_method(self, method): + yield "name= %s" % self.getm(method, "name") + yield "type= %s" % self.getm(method, "type") + yield "description= %s" % self.getm(method, "description") + signature = method.get("signature") + if signature: + yield "signature= %s" % self.getm(method, "signature") + params = method.get("params", []) + if params: + yield "parameters:" + for p in params: + for pline in self.render_param(p): + yield " " + pline + r = method.get("returns", None) + if r: + yield "returns:" + if "type" in r: + yield " type= %s" % r["type"] + if "description" in r: + yield " description= %s" % self.join_lines(r["description"]) + props = r.get("props", []) + for p in props: + yield " " + self.render_prop(p) + + def format_api(self, api): + for mline in self.render_method(api): + yield mline + constructors = api.get("constructors", []) + if constructors: + yield "constructors:" + for m in constructors: + for mline in self.render_method(m): + yield " " + mline + methods = api.get("methods", []) + if methods: + yield "methods:" + for m in methods: + for mline in self.render_method(m): + yield " " + mline + properties = api.get("properties", []) + if properties: + yield "properties:" + for p in properties: + yield " " + self.render_prop(p) + + def render_docs(self, docs_json, outf=sys.stdout): + + for (t,data) in docs_json: + if t == "api-json": + for line in self.format_api(data): + line = line.rstrip("\n") + outf.write("API: " + line + "\n") + else: + for line in str(data).split("\n"): + outf.write("MD :" + line + "\n") + +def hunks_to_dict(docs_json): + exports = {} + for (t,data) in docs_json: + if t != "api-json": + continue + if data["name"]: + exports[data["name"]] = data + return exports + +if __name__ == "__main__": + json = False + if sys.argv[1] == "--json": + json = True + del sys.argv[1] + docs_text = open(sys.argv[1]).read() + docs_parsed = list(parse_hunks(docs_text)) + if json: + import simplejson + print simplejson.dumps(docs_parsed, indent=2) + else: + TestRenderer().render_docs(docs_parsed) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apirenderer.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apirenderer.py new file mode 100644 index 0000000..36e46ef --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/apirenderer.py @@ -0,0 +1,302 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 sys, os +import markdown +import apiparser + +# list of all the 'class' and 'id' attributes assigned to +# <div> and <span> tags by the renderer. +API_REFERENCE = 'api_reference' +MODULE_API_DOCS_CLASS = 'module_api_docs' +MODULE_API_DOCS_ID = '_module_api_docs' +API_HEADER = 'api_header' +API_NAME = 'api_name' +API_COMPONENT_GROUP = 'api_component_group' +API_COMPONENT = 'api_component' +DATATYPE = 'datatype' +RETURNS = 'returns' +PARAMETER_SET = 'parameter_set' +MODULE_DESCRIPTION = 'module_description' + +HTML_HEADER = ''' +<!DOCTYPE html>\n +<html>\n +<head>\n + <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n + <base target="_blank"/>\n + <link rel="stylesheet" type="text/css" media="all"\n + href="../../../css/base.css" />\n + <link rel="stylesheet" type="text/css" media="all"\n + href="../../../css/apidocs.css" />\n + <title>Add-on SDK Documentation</title>\n + <style type="text/css">\n + body {\n + border: 50px solid #FFFFFF;\n + }\n + </style>\n +\n + <script type="text/javascript">\n + function rewrite_links() {\n + var images = document.getElementsByTagName("img");\n + for (var i = 0; i < images.length; i++) {\n + var before = images[i].src.split("packages/")[0];\n + var after = images[i].src.split("/docs")[1];\n + images[i].src = before + after;\n + }\n + }\n + </script>\n +</head>\n +\n +<body onload = "rewrite_links()">\n''' + +HTML_FOOTER = ''' +</body>\n +\n +</html>\n''' + +def indent(text_in): + text_out = '' + lines = text_in.splitlines(True) + indentation_level = 0 + indentation_depth = 2 + for line in lines: + if (line.startswith('<div')): + text_out += ((' ' * indentation_depth) * indentation_level) + line + if not '</div>' in line: + indentation_level += 1 + else: + if (line.startswith('</div>')): + indentation_level -= 1 + text_out += ((' ' * indentation_depth) * indentation_level) + line + return text_out + +def tag_wrap_id(text, classname, id, tag = 'div'): + return ''.join(['\n<'+ tag + ' id="', id, '" class="', \ + classname, '">\n', text + '\n</' + tag +'>\n']) + +def tag_wrap(text, classname, tag = 'div', inline = False): + if inline: + return ''.join(['\n<' + tag + ' class="', classname, '">', \ + text, '</'+ tag + '>\n']) + else: + return ''.join(['\n<' + tag + ' class="', classname, '">', \ + text, '\n</'+ tag + '>\n']) + +def tag_wrap_inline(text, classname, tag = 'div'): + return ''.join(['\n<' + tag + ' class="', classname, '">', \ + text, '</'+ tag + '>\n']) + +def span_wrap(text, classname): + return ''.join(['<span class="', classname, '">', \ + text, '</span>']) + +class API_Renderer(object): + def __init__(self, json, tag): + self.name = json.get('name', None) + self.tag = tag + self.description = json.get('description', '') + self.json = json + + def render_name(self): + raise Exception('not implemented in this class') + + def render_description(self): + return markdown.markdown(self.description) + + def render_subcomponents(self): + raise Exception('not implemented in this class') + + def get_tag(self): + return self.tag + +class Class_Doc(API_Renderer): + def __init__(self, json, tag): + API_Renderer.__init__(self, json, tag) + + def render_name(self): + return self.name + + def render_subcomponents(self): + return render_object_contents(self.json, 'h5', 'h6') + +class Event_Doc(API_Renderer): + def __init__(self, json, tag): + API_Renderer.__init__(self, json, tag) + self.arguments_json = json.get('arguments', None) + + def render_name(self): + return self.name + + def render_subcomponents(self): + if not self.arguments_json: + return '' + text = ''.join([render_comp(Argument_Doc(argument_json, 'div')) \ + for argument_json in self.arguments_json]) + return tag_wrap(text, PARAMETER_SET) + +class Argument_Doc(API_Renderer): + def __init__(self, json, tag): + API_Renderer.__init__(self, json, tag) + self.datatype = json.get('datatype', None) + + def render_name(self): + return span_wrap(self.datatype, DATATYPE) + + def render_subcomponents(self): + return '' + +class Function_Doc(API_Renderer): + def __init__(self, json, tag): + API_Renderer.__init__(self, json, tag) + self.signature = json['signature'] + self.returns = json.get('returns', None) + self.parameters_json = json.get('params', None) + + def render_name(self): + return self.signature + + def render_subcomponents(self): + return self._render_parameters() + self._render_returns() + + def _render_parameters(self): + if not self.parameters_json: + return '' + text = ''.join([render_comp(Parameter_Doc(parameter_json, 'div')) \ + for parameter_json in self.parameters_json]) + return tag_wrap(text, PARAMETER_SET) + + def _render_returns(self): + if not self.returns: + return '' + text = 'Returns: ' + span_wrap(self.returns['datatype'], DATATYPE) + text += markdown.markdown(self.returns['description']) + return tag_wrap(text, RETURNS) + +class Property_Doc(API_Renderer): + def __init__(self, json, tag): + API_Renderer.__init__(self, json, tag) + self.datatype = json.get('datatype', None) + self.required = json.get('required', True) + self.default = json.get('default', False) + + def render_name(self): + rendered = self.name + if self.default: + rendered = rendered + " = " + self.default + if self.datatype: + rendered = rendered + ' : ' + span_wrap(self.datatype, DATATYPE) + if not self.required: + rendered = '[ ' + rendered + ' ]' + return rendered + + def render_subcomponents(self): + return render_object_contents(self.json) + +class Parameter_Doc(Property_Doc): + def __init__(self, json, tag): + Property_Doc.__init__(self, json, tag) + self.properties_json = json.get('props', None) + + def render_subcomponents(self): + if not self.properties_json: + return '' + text = ''.join([render_comp(Property_Doc(property_json, 'div')) \ + for property_json in self.properties_json]) + return text + +def render_object_contents(json, tag = 'div', comp_tag = 'div'): + ctors = json.get('constructors', None) + text = render_comp_group(ctors, 'Constructors', Function_Doc, tag, comp_tag) + methods = json.get('methods', None) + text += render_comp_group(methods, 'Methods', Function_Doc, tag, comp_tag) + properties = json.get('properties', None) + text += render_comp_group(properties, 'Properties', Property_Doc, tag, comp_tag) + events = json.get('events', None) + text += render_comp_group(events, 'Events', Event_Doc, tag, comp_tag) + return text + +def render_comp(component): + # a component is wrapped inside a single div marked 'API_COMPONENT' + # containing: + # 1) the component name, marked 'API_NAME' + text = tag_wrap(component.render_name(), API_NAME, component.get_tag(), True) + # 2) the component description + text += component.render_description() + # 3) the component contents + text += component.render_subcomponents() + return tag_wrap(text, API_COMPONENT) + +def render_comp_group(group, group_name, ctor, tag = 'div', comp_tag = 'div'): + if not group: + return '' + # component group is a list of components in a single div called + # 'API_COMPONENT_GROUP' containing: + # 1) a title for the group marked with 'API_HEADER' + text = tag_wrap(group_name, API_HEADER, tag, True) + # 2) each component + text += ''.join([render_comp(ctor(api, comp_tag)) for api in group]) + return tag_wrap(text, API_COMPONENT_GROUP) + +def render_descriptions(descriptions_md): + text = ''.join([description_md for description_md in descriptions_md]) + return tag_wrap(markdown.markdown(text), MODULE_DESCRIPTION) + +def render_api_reference(api_docs): + if (len(api_docs) == 0): + return '' + # at the top level api reference is in a single div marked 'API_REFERENCE', + # containing: + # 1) a title 'API Reference' marked with 'API_HEADER' + text = tag_wrap('API Reference', API_HEADER, 'h2', True) + # 2) a component group called 'Classes' containing any class elements + classes = [api for api in api_docs if api['type'] == 'class'] + text += render_comp_group(classes, 'Classes', Class_Doc, 'h3', 'h4') + # 3) a component group called 'Functions' containing any global functions + functions = [api for api in api_docs if api['type'] == 'function'] + text += render_comp_group(functions, 'Functions', Function_Doc, 'h3', 'h4') + # 4) a component group called 'Properties' containing any global properties + properties = [api for api in api_docs if api['type'] == 'property'] + text += render_comp_group(properties, 'Properties', Property_Doc, 'h3', 'h4') + # 5) a component group called 'Events' containing any global events + events = [api for api in api_docs if api['type'] == 'event'] + text += render_comp_group(events, 'Events', Event_Doc, 'h3', 'h4') + return tag_wrap(text, API_REFERENCE) + +# take the JSON output of apiparser +# return the HTML DIV containing the rendered component +def json_to_div(json, markdown_filename): + module_name, ext = os.path.splitext(os.path.basename(markdown_filename)) + descriptions = [hunk[1] for hunk in json if hunk[0]=='markdown'] + api_docs = [hunk[1] for hunk in json if hunk[0]=='api-json'] + text = "<h1>" + module_name + "</h1>" + text += render_descriptions(descriptions) + text += render_api_reference(api_docs) + text = tag_wrap_id(text, MODULE_API_DOCS_CLASS, \ + module_name + MODULE_API_DOCS_ID) + return text.encode('utf8') + +# take the JSON output of apiparser +# return standalone HTML containing the rendered component +def json_to_html(json, markdown_filename): + return indent(HTML_HEADER + \ + json_to_div(json, markdown_filename) + HTML_FOOTER) + +# take the name of a Markdown file +# return the HTML DIV containing the rendered component +def md_to_div(markdown_filename): + markdown_contents = open(markdown_filename).read().decode('utf8') + json = list(apiparser.parse_hunks(markdown_contents)) + return json_to_div(json, markdown_filename) + +# take the name of a Markdown file +# return standalone HTML containing the rendered component +def md_to_html(markdown_filename): + return indent(HTML_HEADER + md_to_div(markdown_filename) + HTML_FOOTER) + +if __name__ == '__main__': + if (len(sys.argv) == 0): + print 'Supply the name of a docs file to parse' + else: + print md_to_html(sys.argv[1]) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/generate.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/generate.py new file mode 100644 index 0000000..19ef109 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/generate.py @@ -0,0 +1,292 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import sys +import shutil +import hashlib +import tarfile +import StringIO +import HTMLParser +import urlparse + +from cuddlefish import packaging +from cuddlefish.docs import apiparser +from cuddlefish.docs import apirenderer +from cuddlefish.docs import webdocs +import simplejson as json + +DIGEST = "status.md5" +TGZ_FILENAME = "addon-sdk-docs.tgz" + +def get_sdk_docs_path(env_root): + return os.path.join(env_root, "doc") + +def get_base_url(env_root): + sdk_docs_path = get_sdk_docs_path(env_root).lstrip("/") + return "file://"+"/"+"/".join(sdk_docs_path.split(os.sep))+"/" + +def clean_generated_docs(docs_dir): + status_file = os.path.join(docs_dir, "status.md5") + if os.path.exists(status_file): + os.remove(status_file) + index_file = os.path.join(docs_dir, "index.html") + if os.path.exists(index_file): + os.remove(index_file) + dev_guide_dir = os.path.join(docs_dir, "dev-guide") + if os.path.exists(dev_guide_dir): + shutil.rmtree(dev_guide_dir) + api_doc_dir = os.path.join(docs_dir, "packages") + if os.path.exists(api_doc_dir): + shutil.rmtree(api_doc_dir) + +def generate_static_docs(env_root): + clean_generated_docs(get_sdk_docs_path(env_root)) + generate_docs(env_root, stdout=StringIO.StringIO()) + tgz = tarfile.open(TGZ_FILENAME, 'w:gz') + tgz.add(get_sdk_docs_path(env_root), "doc") + tgz.close() + return TGZ_FILENAME + +def generate_local_docs(env_root): + return generate_docs(env_root, get_base_url(env_root)) + +def generate_named_file(env_root, filename): + web_docs = webdocs.WebDocs(env_root, get_base_url(env_root)) + # next, generate api doc or guide doc + abs_path = os.path.abspath(filename) + if abs_path.startswith(os.path.join(env_root, 'packages')): + doc_html, dest_dir, filename = generate_api_doc(env_root, abs_path, web_docs) + write_file(env_root, doc_html, dest_dir, filename) + elif abs_path.startswith(os.path.join(get_sdk_docs_path(env_root), 'dev-guide-source')): + doc_html, dest_dir, filename = generate_guide_doc(env_root, abs_path, web_docs) + write_file(env_root, doc_html, dest_dir, filename, False) + else: + raise ValueError("Not a valid path to a documentation file") + +def generate_docs(env_root, base_url=None, stdout=sys.stdout): + docs_dir = get_sdk_docs_path(env_root) + # if the generated docs don't exist, generate everything + if not os.path.exists(os.path.join(docs_dir, "dev-guide")): + print >>stdout, "Generating documentation..." + generate_docs_from_scratch(env_root, base_url) + current_status = calculate_current_status(env_root) + open(os.path.join(docs_dir, DIGEST), "w").write(current_status) + else: + current_status = calculate_current_status(env_root) + previous_status_file = os.path.join(docs_dir, DIGEST) + docs_are_up_to_date = False + if os.path.exists(previous_status_file): + docs_are_up_to_date = current_status == open(previous_status_file, "r").read() + # if the docs are not up to date, generate everything + if not docs_are_up_to_date: + print >>stdout, "Regenerating documentation..." + generate_docs_from_scratch(env_root, base_url) + open(os.path.join(docs_dir, DIGEST), "w").write(current_status) + return get_base_url(env_root) + "index.html" + +# this function builds a hash of the name and last modification date of: +# * every file in "packages" which ends in ".md" +# * every file in "static-files" which does not start with "." +def calculate_current_status(env_root): + docs_dir = get_sdk_docs_path(env_root) + current_status = hashlib.md5() + package_src_dir = os.path.join(env_root, "packages") + for (dirpath, dirnames, filenames) in os.walk(package_src_dir): + for filename in filenames: + if filename.endswith(".md"): + current_status.update(filename) + current_status.update(str(os.path.getmtime(os.path.join(dirpath, filename)))) + guide_src_dir = os.path.join(docs_dir, "dev-guide-source") + for (dirpath, dirnames, filenames) in os.walk(guide_src_dir): + for filename in filenames: + if filename.endswith(".md"): + current_status.update(filename) + current_status.update(str(os.path.getmtime(os.path.join(dirpath, filename)))) + base_html_file = os.path.join(docs_dir, "static-files", "base.html") + current_status.update(base_html_file) + current_status.update(str(os.path.getmtime(os.path.join(dirpath, base_html_file)))) + return current_status.digest() + +def generate_docs_from_scratch(env_root, base_url): + docs_dir = get_sdk_docs_path(env_root) + web_docs = webdocs.WebDocs(env_root, base_url) + must_rewrite_links = True + if base_url: + must_rewrite_links = False + clean_generated_docs(docs_dir) + + # py2.5 doesn't have ignore=, so we delete tempfiles afterwards. If we + # required >=py2.6, we could use ignore=shutil.ignore_patterns("*~") + for (dirpath, dirnames, filenames) in os.walk(docs_dir): + for n in filenames: + if n.endswith("~"): + os.unlink(os.path.join(dirpath, n)) + + # generate api docs from all packages + os.mkdir(os.path.join(docs_dir, "packages")) + # create the index file and save that + pkg_cfg = packaging.build_pkg_cfg(env_root) + index = json.dumps(packaging.build_pkg_index(pkg_cfg)) + index_path = os.path.join(docs_dir, "packages", 'index.json') + open(index_path, 'w').write(index) + + # for each package, generate its docs + for pkg_name, pkg in pkg_cfg['packages'].items(): + src_dir = pkg.root_dir + package_dirname = os.path.basename(src_dir) + dest_dir = os.path.join(docs_dir, "packages", package_dirname) + os.mkdir(dest_dir) + + src_readme = os.path.join(src_dir, "README.md") + if os.path.exists(src_readme): + shutil.copyfile(src_readme, + os.path.join(dest_dir, "README.md")) + + # create the package page + package_filename = os.path.join(dest_dir, "index.html") + if not os.path.exists(package_filename): + package_doc_html = web_docs.create_package_page(pkg_name) + replace_file(env_root, package_filename, package_doc_html, must_rewrite_links) + + # generate all the API docs + docs_src_dir = os.path.join(src_dir, "doc") + if os.path.isdir(os.path.join(src_dir, "docs")): + docs_src_dir = os.path.join(src_dir, "docs") + generate_file_tree(env_root, docs_src_dir, web_docs, generate_api_doc, must_rewrite_links) + + # generate all the guide docs + dev_guide_src = os.path.join(docs_dir, "dev-guide-source") + generate_file_tree(env_root, dev_guide_src, web_docs, generate_guide_doc, must_rewrite_links) + + # make /md/dev-guide/welcome.html the top level index file + doc_html, dest_dir, filename = generate_guide_doc(env_root, os.path.join(docs_dir, 'dev-guide-source', 'index.md'), web_docs) + write_file(env_root, doc_html, docs_dir, 'index', False) + +def generate_file_tree(env_root, src_dir, web_docs, generate_file, must_rewrite_links): + for (dirpath, dirnames, filenames) in os.walk(src_dir): + assert dirpath.startswith(src_dir) # what is this for?? + for filename in filenames: + if filename.endswith("~"): + continue + src_path = os.path.join(dirpath, filename) + if src_path.endswith(".md"): + # write the standalone HTML files + doc_html, dest_dir, filename = generate_file(env_root, src_path, web_docs) + write_file(env_root, doc_html, dest_dir, filename, must_rewrite_links) + +def generate_api_doc(env_root, src_dir, web_docs): + doc_html = web_docs.create_module_page(src_dir) + dest_dir, filename = get_api_doc_dest_path(env_root, src_dir) + return doc_html, dest_dir, filename + +def generate_guide_doc(env_root, src_dir, web_docs): + doc_html = web_docs.create_guide_page(src_dir) + dest_dir, filename = get_guide_doc_dest_path(env_root, src_dir) + return doc_html, dest_dir, filename + +def write_file(env_root, doc_html, dest_dir, filename, must_rewrite_links): + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + dest_path_html = os.path.join(dest_dir, filename) + ".html" + replace_file(env_root, dest_path_html, doc_html, must_rewrite_links) + return dest_path_html + +def replace_file(env_root, dest_path, file_contents, must_rewrite_links): + if os.path.exists(dest_path): + os.remove(dest_path) + # before we copy the final version, we'll rewrite the links + # I'll do this last, just because we know definitely what the dest_path is at this point + if must_rewrite_links and dest_path.endswith(".html"): + file_contents = rewrite_links(env_root, file_contents, dest_path) + open(dest_path, "w").write(file_contents) + +def rewrite_links(env_root, page, dest_path): + dest_path_depth = len(dest_path.split(os.sep)) -1 # because dest_path includes filename + docs_root_depth = len(get_sdk_docs_path(env_root).split(os.sep)) + relative_depth = dest_path_depth - docs_root_depth + linkRewriter = LinkRewriter("../" * relative_depth) + return linkRewriter.rewrite_links(page) + +# Given the full path to an API source file, and the root, +# return a tuple of: +# 1) the full path to the corresponding HTML file, without the filename +# 2) the filename without the extension +def get_guide_doc_dest_path(env_root, src_dir): + src_dir_relative = src_dir[len(os.path.join(get_sdk_docs_path(env_root), "dev-guide-source")) + 1:] + return os.path.split(os.path.join(get_sdk_docs_path(env_root), "dev-guide", src_dir_relative)[:-3]) + +# Given the full path to a dev guide source file, and the root, +# return a tuple of: +# 1) the full path to the corresponding HTML file, without the filename +# 2) the filename without the extension +def get_api_doc_dest_path(env_root, src_dir): + src_dir_relative = src_dir[len(env_root) + 1:] + src_dir_relative_pieces = src_dir_relative.split(os.sep) + del src_dir_relative_pieces[2] + src_dir_relative = os.sep.join(src_dir_relative_pieces) + return os.path.split(os.path.join(get_sdk_docs_path(env_root), src_dir_relative)[:-3]) + +class LinkRewriter(HTMLParser.HTMLParser): + def __init__(self, link_prefix): + HTMLParser.HTMLParser.__init__(self) + self.stack = [] + self.link_prefix = link_prefix + + def rewrite_links(self, page): + self.feed(page) + self.close() + page = ''.join(self.stack) + self.stack = [] + return page + + def handle_decl(self, decl): + self.stack.append("<!" + decl + ">") + + def handle_comment(self, decl): + self.stack.append("<!--" + decl + "-->") + + def handle_starttag(self, tag, attrs): + self.stack.append(self.__html_start_tag(tag, self._rewrite_link(attrs))) + + def handle_entityref(self, name): + self.stack.append("&" + name + ";") + + def handle_endtag(self, tag): + self.stack.append(self.__html_end_tag(tag)) + + def handle_startendtag(self, tag, attrs): + self.stack.append(self.__html_startend_tag(tag, self._rewrite_link(attrs))) + + def _rewrite_link(self, attrs): + attrs = dict(attrs) + href = attrs.get('href', '') + if href: + parsed = urlparse.urlparse(href) + if not parsed.scheme: + attrs['href'] = self.link_prefix + href + src = attrs.get('src', '') + if src: + parsed = urlparse.urlparse(src) + if not parsed.scheme: + attrs['src'] = self.link_prefix + src + return attrs + + def handle_data(self, data): + self.stack.append(data) + + def __html_start_tag(self, tag, attrs): + return '<%s%s>' % (tag, self.__html_attrs(attrs)) + + def __html_startend_tag(self, tag, attrs): + return '<%s%s/>' % (tag, self.__html_attrs(attrs)) + + def __html_end_tag(self, tag): + return '</%s>' % (tag) + + def __html_attrs(self, attrs): + _attrs = '' + if attrs: + _attrs = ' %s' % (' '.join([('%s="%s"' % (k,v)) for k,v in dict(attrs).iteritems()])) + return _attrs diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/renderapi.readme.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/renderapi.readme.md new file mode 100644 index 0000000..627c2a6 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/renderapi.readme.md @@ -0,0 +1,210 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + + +This document describes the structure of the HTML generated by the renderapi.py +tool, both for use in the API docs shown by "cfx docs" and as exported by +"cfx sdocs". The particular HTML id and class attributes embedded in the files, +as well as their organization, represent the interface between the tool and any +front-end code wanting to style the docs in some particular way. + +renderapi generates two sorts of files: + +- a file called "<module-name>.div": this is the contents of the parsed +Markdown file rendered inside a well-defined DIV tag + +- a file called "<module-name>.html": this is the DIV from above inserted into +a simple HTML template that references a sample CSS file which styles the +contents of the DIV. This CSS file is the same as the one used by the SDK +itself. + +DIV tags +-------- +The following class and id attributes are used in the DIV: + +renderapi uses a number of class attributes and a single id attribute in the DIV: + +id attribute <module_name>"_module_api_docs" +class attribute "api_reference" +class attribute "module_api_docs" +class attribute "api_header" +class attribute "api_name" +class attribute "api_component_group" +class attribute "api_component" +class attribute "datatype" +class attribute "returns" +class attribute "parameter_set" +class attribute "module_description" + +DIV structure +------------- +The top level DIV is marked with the id attribute and the "module_api_docs" class +attribute: + + <div id='tabs_module_api_docs' class='module_api_docs'> + //module doc contents + </div> + + +Inside this: + +- the first item is an <h1> heading containing the name of the module: + +- all "markdown" hunks (that is, all descriptive text not occurring +inside <api></api> tags) are rendered inside a DIV marked with the +"module-description" class attribute + +- all <api></api> content is rendered, enclosed in a single tag marked +with the "api_reference" class attribute: + + <div id='tabs_module_api_docs' class='module_api_docs'> + <div class='module_description'> + //descriptions + </div> + <div class='api_reference'> + //api reference + </div> + </div> + +If there is no <api></api> content, then the "api-reference" section is absent. + +### API Reference structure ### + +The first item in API reference is an <h2> heading title marked with the +"api_header" attribute. This might have the text content "API Reference" +(but you should not rely on that): + + <div class='api_reference'> + + <h2 class='api_header'>API Reference</h2> + + //api contents + + </div> + +After the title come one or more component groups. + +#### Component Group #### + +A component group is marked with the "api_component_group" attribute. The +component group is a collection of some sort of component: for example, a group +of classes, a group of functions, or a group of events. + +Each component group starts off with a header marked with the +"api_header" attribute and is followed by one or more sections marked with the +"api_component" attribute. +At the top level (that is, when they are directly under the "API Reference" +heading), the "api_header" items are <h3> headings, otherwise they are divs. + + <div class='api_reference'> + + <h2 class='api_header'>API Reference</h2> + + <div class='api_component_group'> + + <h3 class='api_header'>Classes</h3> + + <div class='api_component'> + // the first class + </div> + + <div class='api_component'> + // another class + </div> + + </div> + + <div class='api_component_group'> + //some different components + + <h3 class='api_header'>Functions</h3> + + <div class='api_component'> + the first function + </div> + + <div class='api_component'> + another function + </div> + + </div> + + </div> + +#### Component #### + +API components represent actual objects in the API like classes, functions, +properties and events. + +Each component starts with a section marked with the +"api_name" tag, which includes the name of the component in the API: for +example "postMessage(message)". + +Components at the top level (i.e., directly under h3 headings) are <h4> +headings, otherwise they are divs. + +After the name, the component's contents are listed. Different sorts of +components may have different sorts of contents: for example, a function might +have parameters. If the component is composite then it may contain its own +component group. For example, a class may contain methods and properties, +which might be grouped together. + + <div class='api_component'> + + <h4 class='api_name'>Panel</h4> + + <div class='api_component_group'> + + <div class='api_header'> + Methods + </div> + + <div class='api_component'> + show() + </div> + + </div> + + </div> + +Other attributes +----------------------------- + +### Datatype ### +All primitive data types, like "string" and "number", are marked with the +"datatype" class attribute: + + <div class="api_component"> + + <div class="api_name"> + label : <span class="datatype">string</span> + </div> + + <p>A required string description of the widget used for accessibility, + title bars, and error reporting.</p> + + </div> + +### Returns ### + +Functions mark return values with the "returns" class attribute. + + <div class="api_component"> + + <div class="api_name"> + get() + </div> + + Make a `GET` request. + + <div class="returns"> + Returns: <span class="datatype">Request</span> + </div> + + </div> + +### Parameter_set ### + +Functions that take parameters mark them with the parameter_set class +attribute. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/webdocs.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/webdocs.py new file mode 100644 index 0000000..dcecc78 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/docs/webdocs.py @@ -0,0 +1,193 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os, re, errno +import markdown +import cgi + +from cuddlefish import packaging +from cuddlefish.docs import apirenderer +from cuddlefish._version import get_versions + +INDEX_PAGE = '/doc/static-files/base.html' +BASE_URL_INSERTION_POINT = '<base ' +VERSION_INSERTION_POINT = '<div id="version">' +THIRD_PARTY_PACKAGE_SUMMARIES = '<ul id="third-party-package-summaries">' +HIGH_LEVEL_PACKAGE_SUMMARIES = '<ul id="high-level-package-summaries">' +LOW_LEVEL_PACKAGE_SUMMARIES = '<ul id="low-level-package-summaries">' +CONTENT_ID = '<div id="main-content">' +TITLE_ID = '<title>' +DEFAULT_TITLE = 'Add-on SDK Documentation' + +def get_documentation(package_name, modules_json, doc_path): + documented_modules = [] + for root, dirs, files in os.walk(doc_path): + subdir_path = root.split(os.sep)[len(doc_path.split(os.sep)):] + for filename in files: + if filename.endswith(".md"): + modname = filename[:-len(".md")] + modpath = subdir_path + [modname] + documented_modules.append(modpath) + return documented_modules + +def tag_wrap(text, tag, attributes={}): + result = '\n<' + tag + for name in attributes.keys(): + result += ' ' + name + '=' + '"' + attributes[name] + '"' + result +='>' + text + '</'+ tag + '>\n' + return result + +def is_third_party(package_json): + return (not is_high_level(package_json)) and \ + (not(is_low_level(package_json))) + +def is_high_level(package_json): + return 'jetpack-high-level' in package_json.get('keywords', []) + +def is_low_level(package_json): + return 'jetpack-low-level' in package_json.get('keywords', []) + +def insert_after(target, insertion_point_id, text_to_insert): + insertion_point = target.find(insertion_point_id) + len(insertion_point_id) + return target[:insertion_point] + text_to_insert + target[insertion_point:] + +class WebDocs(object): + def __init__(self, root, base_url = None): + self.root = root + self.pkg_cfg = packaging.build_pkg_cfg(root) + self.packages_json = packaging.build_pkg_index(self.pkg_cfg) + self.base_page = self._create_base_page(root, base_url) + + def create_guide_page(self, path): + path, ext = os.path.splitext(path) + md_path = path + '.md' + md_content = unicode(open(md_path, 'r').read(), 'utf8') + guide_content = markdown.markdown(md_content) + return self._create_page(guide_content) + + def create_module_page(self, path): + path, ext = os.path.splitext(path) + md_path = path + '.md' + module_content = apirenderer.md_to_div(md_path) + return self._create_page(module_content) + + def create_package_page(self, package_name): + package_content = self._create_package_detail(package_name) + return self._create_page(package_content) + + def _create_page(self, page_content): + page = self._insert_title(self.base_page, page_content) + page = insert_after(page, CONTENT_ID, page_content) + return page.encode('utf8') + + def _create_module_list(self, package_json): + package_name = package_json['name'] + libs = package_json['files'][1]['lib'][1] + doc_path = package_json.get('doc', None) + if not doc_path: + return '' + modules = get_documentation(package_name, libs, doc_path) + modules.sort() + module_items = '' + relative_doc_path = doc_path[len(self.root) + 1:] + relative_doc_path_pieces = relative_doc_path.split(os.sep) + del relative_doc_path_pieces[-1] + relative_doc_URL = "/".join(relative_doc_path_pieces) + for module in modules: + module_link = tag_wrap('/'.join(module), 'a', \ + {'href': relative_doc_URL + '/' + '/'.join(module) + '.html'}) + module_items += module_link + return module_items + + def _create_package_summaries(self, packages_json, include): + packages = '' + for package_name in packages_json.keys(): + package_json = packages_json[package_name] + if not include(package_json): + continue + package_path = self.pkg_cfg["packages"][package_name]["root_dir"] + package_directory = package_path[len(self.root) + 1:] + package_directory = "/".join(package_directory.split(os.sep)) + package_link = tag_wrap(package_name, 'a', {'href': \ + package_directory + "/" \ + + 'index.html'}) + text = tag_wrap(package_link, 'h4') + text += self._create_module_list(package_json) + packages += tag_wrap(text, 'li', {'class':'package-summary', \ + 'style':'display: block;'}) + return packages + + def _create_base_page(self, root, base_url): + base_page = unicode(open(root + INDEX_PAGE, 'r').read(), 'utf8') + if base_url: + base_tag = 'href="' + base_url + '"' + base_page = insert_after(base_page, BASE_URL_INSERTION_POINT, base_tag) + sdk_version = get_versions()["version"] + base_page = insert_after(base_page, VERSION_INSERTION_POINT, "Version " + sdk_version) + third_party_summaries = \ + self._create_package_summaries(self.packages_json, is_third_party) + base_page = insert_after(base_page, \ + THIRD_PARTY_PACKAGE_SUMMARIES, third_party_summaries) + high_level_summaries = \ + self._create_package_summaries(self.packages_json, is_high_level) + base_page = insert_after(base_page, \ + HIGH_LEVEL_PACKAGE_SUMMARIES, high_level_summaries) + low_level_summaries = \ + self._create_package_summaries(self.packages_json, is_low_level) + base_page = insert_after(base_page, \ + LOW_LEVEL_PACKAGE_SUMMARIES, low_level_summaries) + return base_page + + def _create_package_detail_row(self, field_value, \ + field_descriptor, field_name): + meta = tag_wrap(tag_wrap(field_descriptor, 'span', \ + {'class':'meta-header'}), 'td') + value = tag_wrap(tag_wrap(field_value, 'span', \ + {'class':field_name}), 'td') + return tag_wrap(meta + value, 'tr') + + def _create_package_detail_table(self, package_json): + table_contents = '' + if package_json.get('author', None): + table_contents += self._create_package_detail_row(\ + cgi.escape(package_json['author']), 'Author', 'author') + if package_json.get('version', None): + table_contents += self._create_package_detail_row(\ + package_json['version'], 'Version', 'version') + if package_json.get('license', None): + table_contents += self._create_package_detail_row(\ + package_json['license'], 'License', 'license') + if package_json.get('dependencies', None): + table_contents += self._create_package_detail_row(\ + ', '.join(package_json['dependencies']), \ + 'Dependencies', 'dependencies') + table_contents += self._create_package_detail_row(\ + self._create_module_list(package_json), 'Modules', 'modules') + return tag_wrap(tag_wrap(table_contents, 'tbody'), 'table', \ + {'class':'meta-table'}) + + def _create_package_detail(self, package_name): + package_json = self.packages_json.get(package_name, None) + if not package_json: + raise IOError(errno.ENOENT, 'Package not found') + # pieces of the package detail: 1) title, 2) table, 3) description + package_title = tag_wrap(package_name, 'h1') + table = self._create_package_detail_table(package_json) + description = '' + if package_json.get('readme', None): + description += tag_wrap(tag_wrap(\ + markdown.markdown(\ + package_json['readme']), 'p'), 'div', {'class':'docs'}) + return tag_wrap(package_title + table + description, 'div', \ + {'class':'package-detail'}) + + def _insert_title(self, target, content): + match = re.search('<h1>.*</h1>', content) + if match: + title = match.group(0)[len('<h1>'):-len('</h1>')] + ' - ' + \ + DEFAULT_TITLE + else: + title = DEFAULT_TITLE + target = insert_after(target, TITLE_ID, title) + return target diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/manifest.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/manifest.py new file mode 100644 index 0000000..abe37d3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/manifest.py @@ -0,0 +1,751 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os, sys, re, hashlib +import simplejson as json +SEP = os.path.sep +from cuddlefish.util import filter_filenames, filter_dirnames + +def js_zipname(packagename, modulename): + return "%s-lib/%s.js" % (packagename, modulename) +def docs_zipname(packagename, modulename): + return "%s-docs/%s.md" % (packagename, modulename) +def datamap_zipname(packagename): + return "%s-data.json" % packagename +def datafile_zipname(packagename, datapath): + return "%s-data/%s" % (packagename, datapath) + +def to_json(o): + return json.dumps(o, indent=1).encode("utf-8")+"\n" + +class ModuleNotFoundError(Exception): + def __init__(self, requirement_type, requirement_name, + used_by, line_number, looked_in): + Exception.__init__(self) + self.requirement_type = requirement_type # "require" or "define" + self.requirement_name = requirement_name # string, what they require()d + self.used_by = used_by # string, full path to module which did require() + self.line_number = line_number # int, 1-indexed line number of first require() + self.looked_in = looked_in # list of full paths to potential .js files + def __str__(self): + what = "%s(%s)" % (self.requirement_type, self.requirement_name) + where = self.used_by + if self.line_number is not None: + where = "%s:%d" % (self.used_by, self.line_number) + searched = "Looked for it in:\n %s\n" % "\n ".join(self.looked_in) + return ("ModuleNotFoundError: unable to satisfy: %s from\n" + " %s:\n" % (what, where)) + searched + +class BadModuleIdentifier(Exception): + pass +class BadSection(Exception): + pass +class UnreachablePrefixError(Exception): + pass + +class ManifestEntry: + def __init__(self): + self.docs_filename = None + self.docs_hash = None + self.requirements = {} + self.datamap = None + + def get_path(self): + path = "%s/%s/%s" % \ + (self.packageName, self.sectionName, self.moduleName) + if not path.endswith(".js"): + path += ".js" + return path + + def get_entry_for_manifest(self): + entry = { "packageName": self.packageName, + "sectionName": self.sectionName, + "moduleName": self.moduleName, + "jsSHA256": self.js_hash, + "docsSHA256": self.docs_hash, + "requirements": {}, + } + for req in self.requirements: + if isinstance(self.requirements[req], ManifestEntry): + them = self.requirements[req] # this is another ManifestEntry + them_path = them.get_path() + entry["requirements"][req] = {"path": them_path} + else: + # something magic. The manifest entry indicates that they're + # allowed to require() it + entry["requirements"][req] = self.requirements[req] + assert isinstance(entry["requirements"][req], dict) + if self.datamap: + entry["requirements"]["self"] = { + "path": "self", + "mapSHA256": self.datamap.data_manifest_hash, + "mapName": self.packageName+"-data", + "dataURIPrefix": "%s/data/" % (self.packageName), + } + return entry + + def add_js(self, js_filename): + self.js_filename = js_filename + self.js_hash = hash_file(js_filename) + def add_docs(self, docs_filename): + self.docs_filename = docs_filename + self.docs_hash = hash_file(docs_filename) + def add_requirement(self, reqname, reqdata): + self.requirements[reqname] = reqdata + def add_data(self, datamap): + self.datamap = datamap + + def get_js_zipname(self): + return js_zipname(self.packagename, self.modulename) + def get_docs_zipname(self): + if self.docs_hash: + return docs_zipname(self.packagename, self.modulename) + return None + # self.js_filename + # self.docs_filename + + +def hash_file(fn): + return hashlib.sha256(open(fn,"rb").read()).hexdigest() + +def get_datafiles(datadir): + # yields pathnames relative to DATADIR, ignoring some files + for dirpath, dirnames, filenames in os.walk(datadir): + filenames = list(filter_filenames(filenames)) + # this tells os.walk to prune the search + dirnames[:] = filter_dirnames(dirnames) + for filename in filenames: + fullname = os.path.join(dirpath, filename) + assert fullname.startswith(datadir+SEP), "%s%s not in %s" % (datadir, SEP, fullname) + yield fullname[len(datadir+SEP):] + + +class DataMap: + # one per package + def __init__(self, pkg): + self.pkg = pkg + self.name = pkg.name + self.files_to_copy = [] + datamap = {} + datadir = os.path.join(pkg.root_dir, "data") + for dataname in get_datafiles(datadir): + absname = os.path.join(datadir, dataname) + zipname = datafile_zipname(pkg.name, dataname) + datamap[dataname] = hash_file(absname) + self.files_to_copy.append( (zipname, absname) ) + self.data_manifest = to_json(datamap) + self.data_manifest_hash = hashlib.sha256(self.data_manifest).hexdigest() + self.data_manifest_zipname = datamap_zipname(pkg.name) + self.data_uri_prefix = "%s/data/" % (self.name) + +class BadChromeMarkerError(Exception): + pass + +class ModuleInfo: + def __init__(self, package, section, name, js, docs): + self.package = package + self.section = section + self.name = name + self.js = js + self.docs = docs + + def __hash__(self): + return hash( (self.package.name, self.section, self.name, + self.js, self.docs) ) + def __eq__(self, them): + if them.__class__ is not self.__class__: + return False + if ((them.package.name, them.section, them.name, them.js, them.docs) != + (self.package.name, self.section, self.name, self.js, self.docs) ): + return False + return True + + def __repr__(self): + return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name, + self.section, + self.name, + self.js, self.docs) + +class ManifestBuilder: + def __init__(self, target_cfg, pkg_cfg, deps, extra_modules, + stderr=sys.stderr): + self.manifest = {} # maps (package,section,module) to ManifestEntry + self.target_cfg = target_cfg # the entry point + self.pkg_cfg = pkg_cfg # all known packages + self.deps = deps # list of package names to search + self.used_packagenames = set() + self.stderr = stderr + self.extra_modules = extra_modules + self.modules = {} # maps ModuleInfo to URI in self.manifest + self.datamaps = {} # maps package name to DataMap instance + self.files = [] # maps manifest index to (absfn,absfn) js/docs pair + self.test_modules = [] # for runtime + + def build(self, scan_tests, test_filter_re): + # process the top module, which recurses to process everything it + # reaches + if "main" in self.target_cfg: + top_me = self.process_module(self.find_top(self.target_cfg)) + self.top_path = top_me.get_path() + if scan_tests: + mi = self._find_module_in_package("test-harness", "lib", "run-tests", []) + self.process_module(mi) + # also scan all test files in all packages that we use. By making + # a copy of self.used_packagenames first, we refrain from + # processing tests in packages that our own tests depend upon. If + # we're running tests for package A, and either modules in A or + # tests in A depend upon modules from package B, we *don't* want + # to run tests for package B. + test_modules = [] + dirnames = self.target_cfg["tests"] + if isinstance(dirnames, basestring): + dirnames = [dirnames] + dirnames = [os.path.join(self.target_cfg.root_dir, d) + for d in dirnames] + for d in dirnames: + for filename in os.listdir(d): + if filename.startswith("test-") and filename.endswith(".js"): + testname = filename[:-3] # require(testname) + if test_filter_re: + if not re.search(test_filter_re, testname): + continue + tmi = ModuleInfo(self.target_cfg, "tests", testname, + os.path.join(d, filename), None) + # scan the test's dependencies + tme = self.process_module(tmi) + test_modules.append( (testname, tme) ) + # also add it as an artificial dependency of unit-test-finder, so + # the runtime dynamic load can work. + test_finder = self.get_manifest_entry("api-utils", "lib", + "unit-test-finder") + for (testname,tme) in test_modules: + test_finder.add_requirement(testname, tme) + # finally, tell the runtime about it, so they won't have to + # search for all tests. self.test_modules will be passed + # through the harness-options.json file in the + # .allTestModules property. + self.test_modules.append(testname) + + # include files used by the loader + for em in self.extra_modules: + (pkgname, section, modname, js) = em + mi = ModuleInfo(self.pkg_cfg.packages[pkgname], section, modname, + js, None) + self.process_module(mi) + + + def get_module_entries(self): + return frozenset(self.manifest.values()) + def get_data_entries(self): + return frozenset(self.datamaps.values()) + + def get_used_packages(self): + used = set() + for index in self.manifest: + (package, section, module) = index + used.add(package) + return sorted(used) + + def get_used_files(self): + # returns all .js files that we reference, plus data/ files. You will + # need to add the loader, off-manifest files that it needs, and + # generated metadata. + for me in self.get_module_entries(): + yield me.js_filename + if me.datamap: + for (zipname, absname) in me.datamap.files_to_copy: + yield absname + + def get_all_test_modules(self): + return self.test_modules + + def get_harness_options_manifest(self): + manifest = {} + for me in self.get_module_entries(): + path = me.get_path() + manifest[path] = me.get_entry_for_manifest() + return manifest + + def get_manifest_entry(self, package, section, module): + index = (package, section, module) + if index not in self.manifest: + m = self.manifest[index] = ManifestEntry() + m.packageName = package + m.sectionName = section + m.moduleName = module + self.used_packagenames.add(package) + return self.manifest[index] + + def uri_name_from_path(self, pkg, fn): + # given a filename like .../pkg1/lib/bar/foo.js, and a package + # specification (with a .root_dir like ".../pkg1" and a .lib list of + # paths where .lib[0] is like "lib"), return the appropriate NAME + # that can be put into a URI like resource://JID-pkg1-lib/NAME . This + # will throw an exception if the file is outside of the lib/ + # directory, since that means we can't construct a URI that points to + # it. + # + # This should be a lot easier, and shouldn't fail when the file is in + # the root of the package. Both should become possible when the XPI + # is rearranged and our URI scheme is simplified. + fn = os.path.abspath(fn) + pkglib = pkg.lib[0] + libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib)) + # AARGH, section and name! we need to reverse-engineer a + # ModuleInfo instance that will produce a URI (in the form + # PREFIX/PKGNAME-SECTION/JS) that will map to the existing file. + # Until we fix URI generation to get rid of "sections", this is + # limited to files in the same .directories.lib as the rest of + # the package uses. So if the package's main files are in lib/, + # but the main.js is in the package root, there is no URI we can + # construct that will point to it, and we must fail. + # + # This will become much easier (and the failure case removed) + # when we get rid of sections and change the URIs to look like + # (PREFIX/PKGNAME/PATH-TO-JS). + + # AARGH 2, allowing .lib to be a list is really getting in the + # way. That needs to go away eventually too. + if not fn.startswith(libdir): + raise UnreachablePrefixError("Sorry, but the 'main' file (%s) in package %s is outside that package's 'lib' directory (%s), so I cannot construct a URI to reach it." + % (fn, pkg.name, pkglib)) + name = fn[len(libdir):].lstrip(SEP)[:-len(".js")] + return name + + + def parse_main(self, root_dir, main, check_lib_dir=None): + # 'main' can be like one of the following: + # a: ./lib/main.js b: ./lib/main c: lib/main + # we require it to be a path to the file, though, and ignore the + # .directories stuff. So just "main" is insufficient if you really + # want something in a "lib/" subdirectory. + if main.endswith(".js"): + main = main[:-len(".js")] + if main.startswith("./"): + main = main[len("./"):] + # package.json must always use "/", but on windows we'll replace that + # with "\" before using it as an actual filename + main = os.sep.join(main.split("/")) + paths = [os.path.join(root_dir, main+".js")] + if check_lib_dir is not None: + paths.append(os.path.join(root_dir, check_lib_dir, main+".js")) + return paths + + def find_top_js(self, target_cfg): + for libdir in target_cfg.lib: + for n in self.parse_main(target_cfg.root_dir, target_cfg.main, + libdir): + if os.path.exists(n): + return n + raise KeyError("unable to find main module '%s.js' in top-level package" % target_cfg.main) + + def find_top(self, target_cfg): + top_js = self.find_top_js(target_cfg) + n = os.path.join(target_cfg.root_dir, "README.md") + if os.path.exists(n): + top_docs = n + else: + top_docs = None + name = self.uri_name_from_path(target_cfg, top_js) + return ModuleInfo(target_cfg, "lib", name, top_js, top_docs) + + def process_module(self, mi): + pkg = mi.package + #print "ENTERING", pkg.name, mi.name + # mi.name must be fully-qualified + assert (not mi.name.startswith("./") and + not mi.name.startswith("../")) + # create and claim the manifest row first + me = self.get_manifest_entry(pkg.name, mi.section, mi.name) + + me.add_js(mi.js) + if mi.docs: + me.add_docs(mi.docs) + + js_lines = open(mi.js,"r").readlines() + requires, problems, locations = scan_module(mi.js,js_lines,self.stderr) + if problems: + # the relevant instructions have already been written to stderr + raise BadChromeMarkerError() + + # We update our requirements on the way out of the depth-first + # traversal of the module graph + + for reqname in sorted(requires.keys()): + if reqname in ("chrome", "@packaging", "@loader"): + me.add_requirement(reqname, {"path": reqname}) + elif reqname == "self": + # this might reference bundled data, so: + # 1: hash that data, add the hash as a dependency + # 2: arrange for the data to be copied into the XPI later + if pkg.name not in self.datamaps: + self.datamaps[pkg.name] = DataMap(pkg) + dm = self.datamaps[pkg.name] + me.add_data(dm) # 'self' is implicit + else: + # when two modules require() the same name, do they get a + # shared instance? This is a deep question. For now say yes. + + # find_req_for() returns an entry to put in our + # 'requirements' dict, and will recursively process + # everything transitively required from here. It will also + # populate the self.modules[] cache. Note that we must + # tolerate cycles in the reference graph. + looked_in = [] # populated by subroutines + them_me = self.find_req_for(mi, reqname, looked_in) + if them_me is None: + if mi.section == "tests": + # tolerate missing modules in tests, because + # test-securable-module.js, and the modules/red.js + # that it imports, both do that intentionally + continue + lineno = locations.get(reqname) # None means define() + if lineno is None: + reqtype = "define" + else: + reqtype = "require" + err = ModuleNotFoundError(reqtype, reqname, + mi.js, lineno, looked_in) + raise err + else: + me.add_requirement(reqname, them_me) + + return me + #print "LEAVING", pkg.name, mi.name + + def find_req_for(self, from_module, reqname, looked_in): + # handle a single require(reqname) statement from from_module . + # Return a uri that exists in self.manifest + # Populate looked_in with places we looked. + def BAD(msg): + return BadModuleIdentifier(msg + " in require(%s) from %s" % + (reqname, from_module)) + + if not reqname: + raise BAD("no actual modulename") + + # Allow things in tests/*.js to require both test code and real code. + # But things in lib/*.js can only require real code. + if from_module.section == "tests": + lookfor_sections = ["tests", "lib"] + elif from_module.section == "lib": + lookfor_sections = ["lib"] + else: + raise BadSection(from_module.section) + modulename = from_module.name + + #print " %s require(%s))" % (from_module, reqname) + bits = reqname.split("/") + + if reqname.startswith("./") or reqname.startswith("../"): + # 1: they want something relative to themselves, always from + # their own package + them = modulename.split("/")[:-1] + while bits[0] in (".", ".."): + if not bits: + raise BAD("no actual modulename") + if bits[0] == "..": + if not them: + raise BAD("too many ..") + them.pop() + bits.pop(0) + bits = them+bits + lookfor_pkg = from_module.package.name + lookfor_mod = "/".join(bits) + return self._get_module_from_package(lookfor_pkg, + lookfor_sections, lookfor_mod, + looked_in) + + # non-relative import. Might be a short name (requiring a search + # through "library" packages), or a fully-qualified one. + + if "/" in reqname: + # 2: PKG/MOD: find PKG, look inside for MOD + lookfor_pkg = bits[0] + lookfor_mod = "/".join(bits[1:]) + mi = self._get_module_from_package(lookfor_pkg, + lookfor_sections, lookfor_mod, + looked_in) + if mi: # caution, 0==None + return mi + else: + # 3: try finding PKG, if found, use its main.js entry point + lookfor_pkg = reqname + mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in) + if mi: + return mi + + # 4: search packages for MOD or MODPARENT/MODCHILD. We always search + # their own package first, then the list of packages defined by their + # .dependencies list + from_pkg = from_module.package.name + return self._search_packages_for_module(from_pkg, + lookfor_sections, reqname, + looked_in) + + def _handle_module(self, mi): + if not mi: + return None + + # we tolerate cycles in the reference graph, which means we need to + # populate the self.modules cache before recursing into + # process_module() . We must also check the cache first, so recursion + # can terminate. + if mi in self.modules: + return self.modules[mi] + + # this creates the entry + new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name) + # and populates the cache + self.modules[mi] = new_entry + self.process_module(mi) + return new_entry + + def _get_module_from_package(self, pkgname, sections, modname, looked_in): + if pkgname not in self.pkg_cfg.packages: + return None + mi = self._find_module_in_package(pkgname, sections, modname, + looked_in) + return self._handle_module(mi) + + def _get_entrypoint_from_package(self, pkgname, looked_in): + if pkgname not in self.pkg_cfg.packages: + return None + pkg = self.pkg_cfg.packages[pkgname] + main = pkg.get("main", None) + if not main: + return None + for js in self.parse_main(pkg.root_dir, main): + looked_in.append(js) + if os.path.exists(js): + section = "lib" + name = self.uri_name_from_path(pkg, js) + docs = None + mi = ModuleInfo(pkg, section, name, js, docs) + return self._handle_module(mi) + return None + + def _search_packages_for_module(self, from_pkg, sections, reqname, + looked_in): + searchpath = [] # list of package names + searchpath.append(from_pkg) # search self first + us = self.pkg_cfg.packages[from_pkg] + if 'dependencies' in us: + # only look in dependencies + searchpath.extend(us['dependencies']) + else: + # they didn't declare any dependencies (or they declared an empty + # list, but we'll treat that as not declaring one, because it's + # easier), so look in all deps, sorted alphabetically, so + # addon-kit comes first. Note that self.deps includes all + # packages found by traversing the ".dependencies" lists in each + # package.json, starting from the main addon package, plus + # everything added by --extra-packages + searchpath.extend(sorted(self.deps)) + for pkgname in searchpath: + mi = self._find_module_in_package(pkgname, sections, reqname, + looked_in) + if mi: + return self._handle_module(mi) + return None + + def _find_module_in_package(self, pkgname, sections, name, looked_in): + # require("a/b/c") should look at ...\a\b\c.js on windows + filename = os.sep.join(name.split("/")) + # normalize filename, make sure that we do not add .js if it already has + # it. + if not filename.endswith(".js"): + filename += ".js" + basename = filename[:-3] + + pkg = self.pkg_cfg.packages[pkgname] + if isinstance(sections, basestring): + sections = [sections] + for section in sections: + for sdir in pkg.get(section, []): + js = os.path.join(pkg.root_dir, sdir, filename) + looked_in.append(js) + if os.path.exists(js): + docs = None + maybe_docs = os.path.join(pkg.root_dir, "docs", + basename+".md") + if section == "lib" and os.path.exists(maybe_docs): + docs = maybe_docs + return ModuleInfo(pkg, section, name, js, docs) + return None + +def build_manifest(target_cfg, pkg_cfg, deps, scan_tests, + test_filter_re=None, extra_modules=[]): + """ + Perform recursive dependency analysis starting from entry_point, + building up a manifest of modules that need to be included in the XPI. + Each entry will map require() names to the URL of the module that will + be used to satisfy that dependency. The manifest will be used by the + runtime's require() code. + + This returns a ManifestBuilder object, with two public methods. The + first, get_module_entries(), returns a set of ManifestEntry objects, each + of which can be asked for the following: + + * its contribution to the harness-options.json '.manifest' + * the local disk name + * the name in the XPI at which it should be placed + + The second is get_data_entries(), which returns a set of DataEntry + objects, each of which has: + + * local disk name + * name in the XPI + + note: we don't build the XPI here, but our manifest is passed to the + code which does, so it knows what to copy into the XPI. + """ + + mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules) + mxt.build(scan_tests, test_filter_re) + return mxt + + + +COMMENT_PREFIXES = ["//", "/*", "*", "dump("] + +REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)" +#REQUIRE_RE = r"(?<!goog[.])(?<![\'\"])\brequire\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)" + +# detect the define idiom of the form: +# define("module name", ["dep1", "dep2", "dep3"], function() {}) +# by capturing the contents of the list in a group. +DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]") + +# Out of the async dependencies, do not allow quotes in them. +DEF_RE_ALLOWED = re.compile(r"^[\'\"][^\'\"]+[\'\"]$") + +def scan_requirements_with_grep(fn, lines): + requires = {} + first_location = {} + for (lineno0, line) in enumerate(lines): + for clause in line.split(";"): + clause = clause.strip() + iscomment = False + for commentprefix in COMMENT_PREFIXES: + if clause.startswith(commentprefix): + iscomment = True + if iscomment: + continue + mo = re.search(REQUIRE_RE, clause) + if mo: + modname = mo.group(1) + requires[modname] = {} + if modname not in first_location: + first_location[modname] = lineno0+1 + + # define() can happen across multiple lines, so join everyone up. + wholeshebang = "\n".join(lines) + for match in DEF_RE.finditer(wholeshebang): + # this should net us a list of string literals separated by commas + for strbit in match.group(3).split(","): + strbit = strbit.strip() + # There could be a trailing comma netting us just whitespace, so + # filter that out. Make sure that only string values with + # quotes around them are allowed, and no quotes are inside + # the quoted value. + if strbit and DEF_RE_ALLOWED.match(strbit): + modname = strbit[1:-1] + if modname not in ["exports"]: + requires[modname] = {} + # joining all the lines means we lose line numbers, so we + # can't fill first_location[] + + return requires, first_location + +CHROME_ALIASES = [ + (re.compile(r"Components\.classes"), "Cc"), + (re.compile(r"Components\.interfaces"), "Ci"), + (re.compile(r"Components\.utils"), "Cu"), + (re.compile(r"Components\.results"), "Cr"), + (re.compile(r"Components\.manager"), "Cm"), + ] +OTHER_CHROME = re.compile(r"Components\.[a-zA-Z]") + +def scan_for_bad_chrome(fn, lines, stderr): + problems = False + old_chrome = set() # i.e. "Cc" when we see "Components.classes" + old_chrome_lines = [] # list of (lineno, line.strip()) tuples + for lineno,line in enumerate(lines): + # note: this scanner is not obligated to spot all possible forms of + # chrome access. The scanner is detecting voluntary requests for + # chrome. Runtime tools will enforce allowance or denial of access. + line = line.strip() + iscomment = False + for commentprefix in COMMENT_PREFIXES: + if line.startswith(commentprefix): + iscomment = True + break + if iscomment: + continue + old_chrome_in_this_line = set() + for (regexp,alias) in CHROME_ALIASES: + if regexp.search(line): + old_chrome_in_this_line.add(alias) + if not old_chrome_in_this_line: + if OTHER_CHROME.search(line): + old_chrome_in_this_line.add("components") + old_chrome.update(old_chrome_in_this_line) + if old_chrome_in_this_line: + old_chrome_lines.append( (lineno+1, line) ) + + if old_chrome: + print >>stderr, """ +The following lines from file %(fn)s: +%(lines)s +use 'Components' to access chrome authority. To do so, you need to add a +line somewhat like the following: + + const {%(needs)s} = require("chrome"); + +Then you can use 'Components' as well as any shortcuts to its properties +that you import from the 'chrome' module ('Cc', 'Ci', 'Cm', 'Cr', and +'Cu' for the 'classes', 'interfaces', 'manager', 'results', and 'utils' +properties, respectively). + +(Note: once bug 636145 is fixed, to access 'Components' directly you'll +need to retrieve it from the 'chrome' module by adding it to the list of +symbols you import from the module. To avoid having to make this change +in the future, replace all occurrences of 'Components' in your code with +the equivalent shortcuts now.) +""" % { "fn": fn, "needs": ",".join(sorted(old_chrome)), + "lines": "\n".join([" %3d: %s" % (lineno,line) + for (lineno, line) in old_chrome_lines]), + } + problems = True + return problems + +def scan_module(fn, lines, stderr=sys.stderr): + filename = os.path.basename(fn) + requires, locations = scan_requirements_with_grep(fn, lines) + if filename == "cuddlefish.js": + # this is the loader: don't scan for chrome + problems = False + elif "chrome" in requires: + # if they declare require("chrome"), we tolerate the use of + # Components (see bug 663541 for rationale) + problems = False + else: + problems = scan_for_bad_chrome(fn, lines, stderr) + return requires, problems, locations + + + +if __name__ == '__main__': + for fn in sys.argv[1:]: + requires, problems, locations = scan_module(fn, open(fn).readlines()) + print + print "---", fn + if problems: + print "PROBLEMS" + sys.exit(1) + print "requires: %s" % (",".join(sorted(requires.keys()))) + print "locations: %s" % locations + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/bootstrap.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/bootstrap.js new file mode 100644 index 0000000..7bf6d84 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/bootstrap.js @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +const DEBUG = false; + +let log = DEBUG ? dump : function (){}; + + +function startup(data, reason) { + // This code allow to make all stdIO work + try { + Components.utils.import("resource://gre/modules/ctypes.jsm"); + let libdvm = ctypes.open("libdvm.so"); + let dvmStdioConverterStartup = libdvm.declare("dvmStdioConverterStartup", ctypes.default_abi, ctypes.void_t); + dvmStdioConverterStartup(); + log("MU: console redirected to adb logcat.\n"); + } catch(e) { + Cu.reportError("MU: unable to execute jsctype hack: "+e); + } + + // This code allow to kill firefox from adb + try { + let Watcher = { + window: null, + onOpenWindow: function(window) { + window = window.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); + window.addEventListener("keydown", this, true); + }, + onCloseWindow: function (window) {}, + onWindowTitleChange: function () {}, + handleEvent: function(event) { + // This event is dispatched via: abd shell input keycode 19 + // KEYCODE_DPAD_UP = 19, UP can't be fired by virtual keyboard, + // so it should be safe to take this event as a kill signal. + // `adb shell input` and `JS keyCode` values doesn't map to same values + // In JS, KeyUp maps to DOM_VK_UP = 38: + // https://developer.mozilla.org/en/DOM/KeyboardEvent + if (event.keyCode == 38 && event.which == 38) { + Cu.reportError("Mobile killer triggered!"); + let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. + getService(Ci.nsIAppStartup); + appStartup.quit(Ci.nsIAppStartup.eForceQuit); + } + } + }; + Services.wm.addListener(Watcher); + log("MU: key listener to close firefox set.\n"); + } + catch(e) { + log("MU: Unable to register window watcher: " + e + "\n"); + } + + try { + let QuitObserver = { + observe: function (aSubject, aTopic, aData) { + Services.obs.removeObserver(QuitObserver, "quit-application", false); + dump("MU: APPLICATION-QUIT\n"); + } + }; + Services.obs.addObserver(QuitObserver, "quit-application", false); + log("MU: ready to watch firefox exit.\n"); + } catch(e) { + log("MU: unable to register quit-application observer: " + e + "\n"); + } +} + +function install() {} +function shutdown() {} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/install.rdf b/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/install.rdf new file mode 100644 index 0000000..0b81a0e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/mobile-utils/install.rdf @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + + +<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>mobile-utils@mozilla.com</em:id> + <em:version>1.0</em:version> + <em:type>2</em:type> + <em:bootstrap>true</em:bootstrap> + + <!-- Fennec-XUL --> + <em:targetApplication> + <Description> + <em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Fennec-NativeUI --> + <em:targetApplication> + <Description> + <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Mobile Addon-SDK utility addon</em:name> + <em:description>Allow better integration with cfx tool.</em:description> + <em:creator>Mozilla Corporation</em:creator> + + </Description> +</RDF> diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/options_defaults.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/options_defaults.py new file mode 100644 index 0000000..5931a26 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/options_defaults.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +def parse_options_defaults(options, jetpack_id): + # this returns a unicode string + pref_list = [] + + for pref in options: + if ('value' in pref): + value = pref["value"] + + if isinstance(value, float): + continue + elif isinstance(value, bool): + value = str(pref["value"]).lower() + elif isinstance(value, str): # presumably ASCII + value = "\"" + unicode(pref["value"]) + "\"" + elif isinstance(value, unicode): + value = "\"" + pref["value"] + "\"" + else: + value = str(pref["value"]) + + pref_list.append("pref(\"extensions." + jetpack_id + "." + pref["name"] + "\", " + value + ");") + + return "\n".join(pref_list) + "\n" diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/options_xul.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/options_xul.py new file mode 100644 index 0000000..6f11311 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/options_xul.py @@ -0,0 +1,64 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +from xml.dom.minidom import Document + +VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color', 'file', + 'directory', 'control'] + +class Error(Exception): + pass + +class BadPrefTypeError(Error): + pass + +class MissingPrefAttr(Error): + pass + +def validate_prefs(options): + for pref in options: + # Make sure there is a 'title' + if ("title" not in pref): + raise MissingPrefAttr("The '%s' pref requires a 'title'" % (pref["name"])) + + # Make sure that the pref type is a valid inline pref type + if (pref["type"] not in VALID_PREF_TYPES): + raise BadPrefTypeError('%s is not a valid inline pref type' % (pref["type"])) + + # Make sure the 'control' type has a 'label' + if (pref["type"] == "control"): + if ("label" not in pref): + raise MissingPrefAttr("The 'control' inline pref type requires a 'label'") + + # TODO: Check that pref["type"] matches default value type + +def parse_options(options, jetpack_id): + doc = Document() + root = doc.createElement("vbox") + root.setAttribute("xmlns", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") + doc.appendChild(root) + + for pref in options: + setting = doc.createElement("setting") + setting.setAttribute("pref", "extensions." + jetpack_id + "." + pref["name"]) + setting.setAttribute("type", pref["type"]) + setting.setAttribute("title", pref["title"]) + + if ("description" in pref): + setting.appendChild(doc.createTextNode(pref["description"])) + + if (pref["type"] == "control"): + button = doc.createElement("button") + button.setAttribute("label", pref["label"]) + button.setAttribute("oncommand", "Services.obs.notifyObservers(null, '" + + jetpack_id + "-cmdPressed', '" + + pref["name"] + "');"); + setting.appendChild(button) + elif (pref["type"] == "boolint"): + setting.setAttribute("on", pref["on"]) + setting.setAttribute("off", pref["off"]) + + root.appendChild(setting) + + return doc.toprettyxml(indent=" ") diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/packaging.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/packaging.py new file mode 100644 index 0000000..757d5ac --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/packaging.py @@ -0,0 +1,435 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import sys +import re +import copy + +import simplejson as json +from cuddlefish.bunch import Bunch + +MANIFEST_NAME = 'package.json' + +DEFAULT_LOADER = 'api-utils' + +DEFAULT_PROGRAM_MODULE = 'main' + +DEFAULT_ICON = 'icon.png' +DEFAULT_ICON64 = 'icon64.png' + +METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version', + 'contributors', 'license', 'homepage', 'icon', 'icon64', + 'main', 'directories'] + +RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$') + +class Error(Exception): + pass + +class MalformedPackageError(Error): + pass + +class MalformedJsonFileError(Error): + pass + +class DuplicatePackageError(Error): + pass + +class PackageNotFoundError(Error): + def __init__(self, missing_package, reason): + self.missing_package = missing_package + self.reason = reason + def __str__(self): + return "%s (%s)" % (self.missing_package, self.reason) + +class BadChromeMarkerError(Error): + pass + +def validate_resource_hostname(name): + """ + Validates the given hostname for a resource: URI. + + For more information, see: + + https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13 + + Examples: + + >>> validate_resource_hostname('blarg') + + >>> validate_resource_hostname('bl arg') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains an invalid character. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: bl arg + + >>> validate_resource_hostname('BLARG') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains upper-case letters. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: BLARG + + >>> validate_resource_hostname('foo@bar') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains an invalid character. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: foo@bar + """ + + # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details. + if not name.islower(): + raise ValueError("""Error: the name of your package contains upper-case letters. +Package names can contain only lower-case letters, numbers, underscores, and dashes. +Current package name: %s""" % name) + + if not RESOURCE_HOSTNAME_RE.match(name): + raise ValueError("""Error: the name of your package contains an invalid character. +Package names can contain only lower-case letters, numbers, underscores, and dashes. +Current package name: %s""" % name) + +def find_packages_with_module(pkg_cfg, name): + # TODO: Make this support more than just top-level modules. + filename = "%s.js" % name + packages = [] + for cfg in pkg_cfg.packages.itervalues(): + if 'lib' in cfg: + matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib) + if os.path.exists(os.path.join(dirname, filename))] + if matches: + packages.append(cfg.name) + return packages + +def resolve_dirs(pkg_cfg, dirnames): + for dirname in dirnames: + yield resolve_dir(pkg_cfg, dirname) + +def resolve_dir(pkg_cfg, dirname): + return os.path.join(pkg_cfg.root_dir, dirname) + +def get_metadata(pkg_cfg, deps): + metadata = Bunch() + for pkg_name in deps: + cfg = pkg_cfg.packages[pkg_name] + metadata[pkg_name] = Bunch() + for prop in METADATA_PROPS: + if cfg.get(prop): + metadata[pkg_name][prop] = cfg[prop] + return metadata + +def set_section_dir(base_json, name, base_path, dirnames, allow_root=False): + resolved = compute_section_dir(base_json, base_path, dirnames, allow_root) + if resolved: + base_json[name] = os.path.abspath(resolved) + +def compute_section_dir(base_json, base_path, dirnames, allow_root): + # PACKAGE_JSON.lib is highest priority + # then PACKAGE_JSON.directories.lib + # then lib/ (if it exists) + # then . (but only if allow_root=True) + for dirname in dirnames: + if base_json.get(dirname): + return os.path.join(base_path, base_json[dirname]) + if "directories" in base_json: + for dirname in dirnames: + if dirname in base_json.directories: + return os.path.join(base_path, base_json.directories[dirname]) + for dirname in dirnames: + if os.path.isdir(os.path.join(base_path, dirname)): + return os.path.join(base_path, dirname) + if allow_root: + return os.path.abspath(base_path) + return None + +def normalize_string_or_array(base_json, key): + if base_json.get(key): + if isinstance(base_json[key], basestring): + base_json[key] = [base_json[key]] + +def load_json_file(path): + data = open(path, 'r').read() + try: + return Bunch(json.loads(data)) + except ValueError, e: + raise MalformedJsonFileError('%s when reading "%s"' % (str(e), + path)) + +def get_config_in_dir(path): + package_json = os.path.join(path, MANIFEST_NAME) + if not (os.path.exists(package_json) and + os.path.isfile(package_json)): + raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME, + path)) + base_json = load_json_file(package_json) + + if 'name' not in base_json: + base_json.name = os.path.basename(path) + + # later processing steps will expect to see the following keys in the + # base_json that we return: + # + # name: name of the package + # lib: list of directories with .js files + # test: list of directories with test-*.js files + # doc: list of directories with documentation .md files + # data: list of directories with bundled arbitrary data files + # packages: ? + + if (not base_json.get('tests') and + os.path.isdir(os.path.join(path, 'test'))): + base_json['tests'] = 'test' + + set_section_dir(base_json, 'lib', path, ['lib'], True) + set_section_dir(base_json, 'tests', path, ['test', 'tests'], False) + set_section_dir(base_json, 'doc', path, ['doc', 'docs']) + set_section_dir(base_json, 'data', path, ['data']) + set_section_dir(base_json, 'packages', path, ['packages']) + set_section_dir(base_json, 'locale', path, ['locale']) + + if (not base_json.get('icon') and + os.path.isfile(os.path.join(path, DEFAULT_ICON))): + base_json['icon'] = DEFAULT_ICON + + if (not base_json.get('icon64') and + os.path.isfile(os.path.join(path, DEFAULT_ICON64))): + base_json['icon64'] = DEFAULT_ICON64 + + for key in ['lib', 'tests', 'dependencies', 'packages']: + # TODO: lib/tests can be an array?? consider interaction with + # compute_section_dir above + normalize_string_or_array(base_json, key) + + if 'main' not in base_json and 'lib' in base_json: + for dirname in base_json['lib']: + program = os.path.join(path, dirname, + '%s.js' % DEFAULT_PROGRAM_MODULE) + if os.path.exists(program): + base_json['main'] = DEFAULT_PROGRAM_MODULE + break + + base_json.root_dir = path + + return base_json + +def _is_same_file(a, b): + if hasattr(os.path, 'samefile'): + return os.path.samefile(a, b) + return a == b + +def build_config(root_dir, target_cfg, packagepath=[]): + dirs_to_scan = [] + + def add_packages_from_config(pkgconfig): + if 'packages' in pkgconfig: + for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages): + dirs_to_scan.append(package_dir) + + add_packages_from_config(target_cfg) + + packages_dir = os.path.join(root_dir, 'packages') + if os.path.exists(packages_dir) and os.path.isdir(packages_dir): + dirs_to_scan.append(packages_dir) + dirs_to_scan.extend(packagepath) + + packages = Bunch({target_cfg.name: target_cfg}) + + while dirs_to_scan: + packages_dir = dirs_to_scan.pop() + if os.path.exists(os.path.join(packages_dir, "package.json")): + package_paths = [packages_dir] + else: + package_paths = [os.path.join(packages_dir, dirname) + for dirname in os.listdir(packages_dir) + if not dirname.startswith('.')] + package_paths = [dirname for dirname in package_paths + if os.path.isdir(dirname)] + + for path in package_paths: + pkgconfig = get_config_in_dir(path) + if pkgconfig.name in packages: + otherpkg = packages[pkgconfig.name] + if not _is_same_file(otherpkg.root_dir, path): + raise DuplicatePackageError(path, otherpkg.root_dir) + else: + packages[pkgconfig.name] = pkgconfig + add_packages_from_config(pkgconfig) + + return Bunch(packages=packages) + +def get_deps_for_targets(pkg_cfg, targets): + visited = [] + deps_left = [[dep, None] for dep in list(targets)] + + while deps_left: + [dep, required_by] = deps_left.pop() + if dep not in visited: + visited.append(dep) + if dep not in pkg_cfg.packages: + required_reason = ("required by '%s'" % (required_by)) \ + if required_by is not None \ + else "specified as target" + raise PackageNotFoundError(dep, required_reason) + dep_cfg = pkg_cfg.packages[dep] + deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])]) + deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])]) + + return visited + +def generate_build_for_target(pkg_cfg, target, deps, + include_tests=True, + include_dep_tests=False, + default_loader=DEFAULT_LOADER): + + build = Bunch(# Contains section directories for all packages: + packages=Bunch(), + locale=Bunch() + ) + + def add_section_to_build(cfg, section, is_code=False, + is_data=False): + if section in cfg: + dirnames = cfg[section] + if isinstance(dirnames, basestring): + # This is just for internal consistency within this + # function, it has nothing to do w/ a non-canonical + # configuration dict. + dirnames = [dirnames] + for dirname in resolve_dirs(cfg, dirnames): + # ensure that package name is valid + try: + validate_resource_hostname(cfg.name) + except ValueError, err: + print err + sys.exit(1) + # ensure that this package has an entry + if not cfg.name in build.packages: + build.packages[cfg.name] = Bunch() + # detect duplicated sections + if section in build.packages[cfg.name]: + raise KeyError("package's section already defined", + cfg.name, section) + # Register this section (lib, data, tests) + build.packages[cfg.name][section] = dirname + + def add_locale_to_build(cfg): + path = resolve_dir(cfg, cfg['locale']) + files = os.listdir(path) + for filename in files: + fullpath = os.path.join(path, filename) + if os.path.isfile(fullpath) and filename.endswith('.properties'): + language = filename[:-len('.properties')] + + from property_parser import parse_file, MalformedLocaleFileError + try: + content = parse_file(fullpath) + except MalformedLocaleFileError, msg: + print msg[0] + sys.exit(1) + + # Merge current locales into global locale hashtable. + # Locale files only contains one big JSON object + # that act as an hastable of: + # "keys to translate" => "translated keys" + if language in build.locale: + merge = (build.locale[language].items() + + content.items()) + build.locale[language] = Bunch(merge) + else: + build.locale[language] = content + + def add_dep_to_build(dep): + dep_cfg = pkg_cfg.packages[dep] + add_section_to_build(dep_cfg, "lib", is_code=True) + add_section_to_build(dep_cfg, "data", is_data=True) + if include_tests and include_dep_tests: + add_section_to_build(dep_cfg, "tests", is_code=True) + if 'locale' in dep_cfg: + add_locale_to_build(dep_cfg) + if ("loader" in dep_cfg) and ("loader" not in build): + build.loader = "%s/%s" % (dep, + dep_cfg.loader) + + target_cfg = pkg_cfg.packages[target] + + if include_tests and not include_dep_tests: + add_section_to_build(target_cfg, "tests", is_code=True) + + for dep in deps: + add_dep_to_build(dep) + + if 'loader' not in build: + add_dep_to_build(DEFAULT_LOADER) + + if 'icon' in target_cfg: + build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon) + del target_cfg['icon'] + + if 'icon64' in target_cfg: + build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64) + del target_cfg['icon64'] + + if ('preferences' in target_cfg): + build['preferences'] = target_cfg.preferences + + return build + +def _get_files_in_dir(path): + data = {} + files = os.listdir(path) + for filename in files: + fullpath = os.path.join(path, filename) + if os.path.isdir(fullpath): + data[filename] = _get_files_in_dir(fullpath) + else: + try: + info = os.stat(fullpath) + data[filename] = ("file", dict(size=info.st_size)) + except OSError: + pass + return ("directory", data) + +def build_pkg_index(pkg_cfg): + pkg_cfg = copy.deepcopy(pkg_cfg) + for pkg in pkg_cfg.packages: + root_dir = pkg_cfg.packages[pkg].root_dir + files = _get_files_in_dir(root_dir) + pkg_cfg.packages[pkg].files = files + try: + readme = open(root_dir + '/README.md').read() + pkg_cfg.packages[pkg].readme = readme + except IOError: + pass + del pkg_cfg.packages[pkg].root_dir + return pkg_cfg.packages + +def build_pkg_cfg(root): + pkg_cfg = build_config(root, Bunch(name='dummy')) + del pkg_cfg.packages['dummy'] + return pkg_cfg + +def call_plugins(pkg_cfg, deps): + for dep in deps: + dep_cfg = pkg_cfg.packages[dep] + dirnames = dep_cfg.get('python-lib', []) + for dirname in resolve_dirs(dep_cfg, dirnames): + sys.path.append(dirname) + module_names = dep_cfg.get('python-plugins', []) + for module_name in module_names: + module = __import__(module_name) + module.init(root_dir=dep_cfg.root_dir) + +def call_cmdline_tool(env_root, pkg_name): + pkg_cfg = build_config(env_root, Bunch(name='dummy')) + if pkg_name not in pkg_cfg.packages: + print "This tool requires the '%s' package." % pkg_name + sys.exit(1) + cfg = pkg_cfg.packages[pkg_name] + for dirname in resolve_dirs(cfg, cfg['python-lib']): + sys.path.append(dirname) + module_name = cfg.get('python-cmdline-tool') + module = __import__(module_name) + module.run() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/preflight.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/preflight.py new file mode 100755 index 0000000..8b500ec --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/preflight.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os, sys +import base64 +import simplejson as json + +def create_jid(): + """Return 'jid1-XYZ', where 'XYZ' is a randomly-generated string. (in the + previous jid0- series, the string securely identified a specific public + key). To get a suitable add-on ID, append '@jetpack' to this string. + """ + # per https://developer.mozilla.org/en/Install_Manifests#id all XPI id + # values must either be in the form of a 128-bit GUID (crazy braces + # and all) or in the form of an email address (crazy @ and all). + # Firefox will refuse to install an add-on with an id that doesn't + # match one of these forms. The actual regexp is at: + # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/XPIProvider.jsm#130 + # So the JID needs an @-suffix, and the only legal punctuation is + # "-._". So we start with a base64 encoding, and replace the + # punctuation (+/) with letters (AB), losing a few bits of integrity. + + # even better: windows has a maximum path length limitation of 256 + # characters: + # http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx + # (unless all paths are prefixed with "\\?\", I kid you not). The + # typical install will put add-on code in a directory like: + # C:\Documents and Settings\<username>\Application Data\Mozilla\Firefox\Profiles\232353483.default\extensions\$JID\... + # (which is 108 chars long without the $JID). + # Then the unpacked XPI contains packaged resources like: + # resources/$JID-api-utils-lib/main.js (35 chars plus the $JID) + # + # We create a random 80 bit string, base64 encode that (with + # AB instead of +/ to be path-safe), then bundle it into + # "jid1-XYZ@jetpack". This gives us 27 characters. The resulting + # main.js will have a path length of 211 characters, leaving us 45 + # characters of margin. + # + # 80 bits is enough to generate one billion JIDs and still maintain lower + # than a one-in-a-million chance of accidental collision. (1e9 JIDs is 30 + # bits, square for the "birthday-paradox" to get 60 bits, add 20 bits for + # the one-in-a-million margin to get 80 bits) + + # if length were no issue, we'd prefer to use this: + h = os.urandom(80/8) + s = base64.b64encode(h, "AB").strip("=") + jid = "jid1-" + s + return jid + +def preflight_config(target_cfg, filename, stderr=sys.stderr): + modified = False + config = json.load(open(filename, 'r')) + + if "id" not in config: + print >>stderr, ("No 'id' in package.json: creating a new ID for you.") + jid = create_jid() + config["id"] = jid + modified = True + + if modified: + i = 0 + backup = filename + ".backup" + while os.path.exists(backup): + if i > 1000: + raise ValueError("I'm having problems finding a good name" + " for the backup file. Please move %s out" + " of the way and try again." + % (filename + ".backup")) + backup = filename + ".backup-%d" % i + i += 1 + os.rename(filename, backup) + new_json = json.dumps(config, indent=4) + open(filename, 'w').write(new_json+"\n") + return False, True + + return True, False diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/prefs.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/prefs.py new file mode 100644 index 0000000..8d20039 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/prefs.py @@ -0,0 +1,115 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +DEFAULT_COMMON_PREFS = { + # allow debug output via dump to be printed to the system console + # (setting it here just in case, even though PlainTextConsole also + # sets this preference) + 'browser.dom.window.dump.enabled': True, + # warn about possibly incorrect code + 'javascript.options.strict': True, + 'javascript.options.showInConsole': True, + + 'extensions.checkCompatibility.nightly' : False, + + # Disable extension updates and notifications. + 'extensions.update.enabled' : False, + 'extensions.update.notifyUser' : False, + + # From: + # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l372 + # Only load extensions from the application and user profile. + # AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION + 'extensions.enabledScopes' : 5, + # Disable metadata caching for installed add-ons by default + 'extensions.getAddons.cache.enabled' : False, + # Disable intalling any distribution add-ons + 'extensions.installDistroAddons' : False, + # Allow installing extensions dropped into the profile folder + 'extensions.autoDisableScopes' : 10, + + # Point update checks to a nonexistent local URL for fast failures. + 'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL', + 'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL', + # Make sure opening about:addons won't hit the network. + 'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL' +} + +DEFAULT_FENNEC_PREFS = { + 'browser.console.showInPanel': True, + 'browser.firstrun.show.uidiscovery': False +} + +# When launching a temporary new Firefox profile, use these preferences. +DEFAULT_FIREFOX_PREFS = { + 'browser.startup.homepage' : 'about:blank', + 'startup.homepage_welcome_url' : 'about:blank', + 'devtools.errorconsole.enabled' : True, + + # Disable the feedback extension + 'extensions.testpilot.runStudies' : False, + + # From: + # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388 + # Make url-classifier updates so rare that they won't affect tests. + 'urlclassifier.updateinterval' : 172800, + # Point the url-classifier to a nonexistent local URL for fast failures. + 'browser.safebrowsing.provider.0.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash', + 'browser.safebrowsing.provider.0.keyURL' : 'http://localhost/safebrowsing-dummy/newkey', + 'browser.safebrowsing.provider.0.updateURL' : 'http://localhost/safebrowsing-dummy/update', + } + +# When launching a temporary new Thunderbird profile, use these preferences. +# Note that these were taken from: +# http://mxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py +DEFAULT_THUNDERBIRD_PREFS = { + # say no to slow script warnings + 'dom.max_chrome_script_run_time': 200, + 'dom.max_script_run_time': 0, + # do not ask about being the default mail client + 'mail.shell.checkDefaultClient': False, + # disable non-gloda indexing daemons + 'mail.winsearch.enable': False, + 'mail.winsearch.firstRunDone': True, + 'mail.spotlight.enable': False, + 'mail.spotlight.firstRunDone': True, + # disable address books for undisclosed reasons + 'ldap_2.servers.osx.position': 0, + 'ldap_2.servers.oe.position': 0, + # disable the first use junk dialog + 'mailnews.ui.junk.firstuse': False, + # other unknown voodoo + # -- dummied up local accounts to stop the account wizard + 'mail.account.account1.server' : "server1", + 'mail.account.account2.identities' : "id1", + 'mail.account.account2.server' : "server2", + 'mail.accountmanager.accounts' : "account1,account2", + 'mail.accountmanager.defaultaccount' : "account2", + 'mail.accountmanager.localfoldersserver' : "server1", + 'mail.identity.id1.fullName' : "Tinderbox", + 'mail.identity.id1.smtpServer' : "smtp1", + 'mail.identity.id1.useremail' : "tinderbox@invalid.com", + 'mail.identity.id1.valid' : True, + 'mail.root.none-rel' : "[ProfD]Mail", + 'mail.root.pop3-rel' : "[ProfD]Mail", + 'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders", + 'mail.server.server1.hostname' : "Local Folders", + 'mail.server.server1.name' : "Local Folders", + 'mail.server.server1.type' : "none", + 'mail.server.server1.userName' : "nobody", + 'mail.server.server2.check_new_mail' : False, + 'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox", + 'mail.server.server2.download_on_biff' : True, + 'mail.server.server2.hostname' : "tinderbox", + 'mail.server.server2.login_at_startup' : False, + 'mail.server.server2.name' : "tinderbox@invalid.com", + 'mail.server.server2.type' : "pop3", + 'mail.server.server2.userName' : "tinderbox", + 'mail.smtp.defaultserver' : "smtp1", + 'mail.smtpserver.smtp1.hostname' : "tinderbox", + 'mail.smtpserver.smtp1.username' : "tinderbox", + 'mail.smtpservers' : "smtp1", + 'mail.startup.enabledMailCheckOnce' : True, + 'mailnews.start_page_override.mstone' : "ignore", + } diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/property_parser.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/property_parser.py new file mode 100644 index 0000000..b3f554d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/property_parser.py @@ -0,0 +1,106 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 re +import codecs + +class MalformedLocaleFileError(Exception): + pass + +def parse_file(path): + return parse(read_file(path), path) + +def read_file(path): + try: + return codecs.open( path, "r", "utf-8" ).readlines() + except UnicodeDecodeError, e: + raise MalformedLocaleFileError( + 'Following locale file is not a valid ' + + 'UTF-8 file: %s\n%s"' % (path, str(e))) + +COMMENT = re.compile(r'\s*#') +EMPTY = re.compile(r'^\s+$') +KEYVALUE = re.compile(r"\s*([^=:]+)(=|:)\s*(.*)") + +def parse(lines, path=None): + lines = iter(lines) + lineNo = 1 + pairs = dict() + for line in lines: + if COMMENT.match(line) or EMPTY.match(line) or len(line) == 0: + continue + m = KEYVALUE.match(line) + if not m: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Line %d is incorrect:\n%s' % (path, lineNo, line)) + + # All spaces are strip. Spaces at the beginning are stripped + # by the regular expression. We have to strip spaces at the end. + key = m.group(1).rstrip() + val = m.group(3).rstrip() + + # `key` can be empty when key is only made of spaces + if not key: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Key is invalid on line %d is incorrect:\n%s' % + (path, lineNo, line)) + + # Multiline value: keep reading lines, while lines end with backslash + # and strip spaces at the beginning of lines except the last line + # that doesn't end up with backslash, we strip all spaces for this one. + if val.endswith("\\"): + val = val[:-1] + try: + # remove spaces before/after and especially the \n at EOL + line = lines.next().strip() + while line.endswith("\\"): + val += line[:-1].lstrip() + line = lines.next() + lineNo += 1 + val += line.strip() + except StopIteration: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Unexpected EOF in multiline sequence at line %d:\n%s' % + (path, lineNo, line)) + # Save this new pair + pairs[key] = val + lineNo += 1 + + normalize_plural(path, pairs) + return pairs + +# Plural forms in properties files are defined like this: +# key = other form +# key[one] = one form +# key[...] = ... +# Parse them and merge each key into one object containing all forms: +# key: { +# other: "other form", +# one: "one form", +# ...: ... +# } +PLURAL_FORM = re.compile(r'^(.*)\[(zero|one|two|few|many|other)\]$') +def normalize_plural(path, pairs): + for key in list(pairs.keys()): + m = PLURAL_FORM.match(key) + if not m: + continue + main_key = m.group(1) + plural_form = m.group(2) + if not main_key in pairs: + raise MalformedLocaleFileError( + 'Following locale file is not a valid UTF-8 file: %s\n' + 'This plural form doesn\'t have a matching generic form:\n' + '%s\n' + 'You have to defined following key:\n%s' + % (path, key, main_key)) + # convert generic form into an object if it is still a string + if isinstance(pairs[main_key], unicode): + pairs[main_key] = {"other": pairs[main_key]} + # then, add this new plural form + pairs[main_key][plural_form] = pairs[key] + del pairs[key] diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/rdf.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/rdf.py new file mode 100644 index 0000000..389f512 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/rdf.py @@ -0,0 +1,190 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import xml.dom.minidom +import StringIO + +RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +EM_NS = "http://www.mozilla.org/2004/em-rdf#" + +class RDF(object): + def __str__(self): + # real files have an .encoding attribute and use it when you + # write() unicode into them: they read()/write() unicode and + # put encoded bytes in the backend file. StringIO objects + # read()/write() unicode and put unicode in the backing store, + # so we must encode the output of getvalue() to get a + # bytestring. (cStringIO objects are weirder: they effectively + # have .encoding hardwired to "ascii" and put only bytes in + # the backing store, so we can't use them here). + # + # The encoding= argument to dom.writexml() merely sets the XML header's + # encoding= attribute. It still writes unencoded unicode to the output file, + # so we have to encode it for real afterwards. + # + # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660 + + buf = StringIO.StringIO() + self.dom.writexml(buf, encoding="utf-8") + return buf.getvalue().encode('utf-8') + +class RDFUpdate(RDF): + def __init__(self): + impl = xml.dom.minidom.getDOMImplementation() + self.dom = impl.createDocument(RDF_NS, "RDF", None) + self.dom.documentElement.setAttribute("xmlns", RDF_NS) + self.dom.documentElement.setAttribute("xmlns:em", EM_NS) + + def _make_node(self, name, value, parent): + elem = self.dom.createElement(name) + elem.appendChild(self.dom.createTextNode(value)) + parent.appendChild(elem) + return elem + + def add(self, manifest, update_link): + desc = self.dom.createElement("Description") + desc.setAttribute( + "about", + "urn:mozilla:extension:%s" % manifest.get("em:id") + ) + self.dom.documentElement.appendChild(desc) + + updates = self.dom.createElement("em:updates") + desc.appendChild(updates) + + seq = self.dom.createElement("Seq") + updates.appendChild(seq) + + li = self.dom.createElement("li") + seq.appendChild(li) + + li_desc = self.dom.createElement("Description") + li.appendChild(li_desc) + + self._make_node("em:version", manifest.get("em:version"), + li_desc) + + apps = manifest.dom.documentElement.getElementsByTagName( + "em:targetApplication" + ) + + for app in apps: + target_app = self.dom.createElement("em:targetApplication") + li_desc.appendChild(target_app) + + ta_desc = self.dom.createElement("Description") + target_app.appendChild(ta_desc) + + for name in ["em:id", "em:minVersion", "em:maxVersion"]: + elem = app.getElementsByTagName(name)[0] + self._make_node(name, elem.firstChild.nodeValue, ta_desc) + + self._make_node("em:updateLink", update_link, ta_desc) + +class RDFManifest(RDF): + def __init__(self, path): + self.dom = xml.dom.minidom.parse(path) + + def set(self, property, value): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + raise ValueError("Element with value not found: %s" % property) + if not elements[0].firstChild: + elements[0].appendChild(self.dom.createTextNode(value)) + else: + elements[0].firstChild.nodeValue = value + + def get(self, property, default=None): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + return default + return elements[0].firstChild.nodeValue + + def remove(self, property): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + return True + else: + for i in elements: + i.parentNode.removeChild(i); + + return True; + +def gen_manifest(template_root_dir, target_cfg, jid, + update_url=None, bootstrap=True, enable_mobile=False): + install_rdf = os.path.join(template_root_dir, "install.rdf") + manifest = RDFManifest(install_rdf) + dom = manifest.dom + + manifest.set("em:id", jid) + manifest.set("em:version", + target_cfg.get('version', '1.0')) + manifest.set("em:name", + target_cfg.get('fullName', target_cfg['name'])) + manifest.set("em:description", + target_cfg.get("description", "")) + manifest.set("em:creator", + target_cfg.get("author", "")) + manifest.set("em:bootstrap", str(bootstrap).lower()) + # XPIs remain packed by default, but package.json can override that. The + # RDF format accepts "true" as True, anything else as False. We expect + # booleans in the .json file, not strings. + manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false") + + for contributor in target_cfg.get("contributors", [ ]): + elem = dom.createElement("em:contributor"); + elem.appendChild(dom.createTextNode(contributor)) + dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) + + if update_url: + manifest.set("em:updateURL", update_url) + else: + manifest.remove("em:updateURL") + + if target_cfg.get("preferences"): + manifest.set("em:optionsType", "2") + else: + manifest.remove("em:optionsType") + + if enable_mobile: + target_app = dom.createElement("em:targetApplication") + dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app) + + ta_desc = dom.createElement("Description") + target_app.appendChild(ta_desc) + + elem = dom.createElement("em:id") + elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}")) + ta_desc.appendChild(elem) + + elem = dom.createElement("em:minVersion") + elem.appendChild(dom.createTextNode("10.0")) + ta_desc.appendChild(elem) + + elem = dom.createElement("em:maxVersion") + elem.appendChild(dom.createTextNode("13.0a1")) + ta_desc.appendChild(elem) + + if target_cfg.get("homepage"): + manifest.set("em:homepageURL", target_cfg.get("homepage")) + else: + manifest.remove("em:homepageURL") + + return manifest + +if __name__ == "__main__": + print "Running smoke test." + root = os.path.join(os.path.dirname(__file__), 'app-extension') + manifest = gen_manifest(root, {'name': 'test extension'}, + 'fakeid', 'http://foo.com/update.rdf') + update = RDFUpdate() + update.add(manifest, "https://foo.com/foo.xpi") + exercise_str = str(manifest) + str(update) + for tagname in ["em:targetApplication", "em:version", "em:id"]: + if not len(update.dom.getElementsByTagName(tagname)): + raise Exception("tag does not exist: %s" % tagname) + if not update.dom.getElementsByTagName(tagname)[0].firstChild: + raise Exception("tag has no children: %s" % tagname) + print "Success!" diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/runner.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/runner.py new file mode 100644 index 0000000..76be215 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/runner.py @@ -0,0 +1,696 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import sys +import time +import tempfile +import atexit +import shlex +import subprocess +import re + +import mozrunner +from cuddlefish.prefs import DEFAULT_COMMON_PREFS +from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS +from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS +from cuddlefish.prefs import DEFAULT_FENNEC_PREFS + +# Used to remove noise from ADB output +CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$') +# Used to filter only messages send by `console` module +FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$') + +# Maximum time we'll wait for tests to finish, in seconds. +# The purpose of this timeout is to recover from infinite loops. It should be +# longer than the amount of time any test run takes, including those on slow +# machines running slow (debug) versions of Firefox. +RUN_TIMEOUT = 30 * 60 # 30 minutes + +# Maximum time we'll wait for tests to emit output, in seconds. +# The purpose of this timeout is to recover from hangs. It should be longer +# than the amount of time any test takes to report results. +OUTPUT_TIMEOUT = 60 # one minute + +def follow_file(filename): + """ + Generator that yields the latest unread content from the given + file, or None if no new content is available. + + For example: + + >>> f = open('temp.txt', 'w') + >>> f.write('hello') + >>> f.flush() + >>> tail = follow_file('temp.txt') + >>> tail.next() + 'hello' + >>> tail.next() is None + True + >>> f.write('there') + >>> f.flush() + >>> tail.next() + 'there' + >>> f.close() + >>> os.remove('temp.txt') + """ + + last_pos = 0 + last_size = 0 + while True: + newstuff = None + if os.path.exists(filename): + size = os.stat(filename).st_size + if size > last_size: + last_size = size + f = open(filename, 'r') + f.seek(last_pos) + newstuff = f.read() + last_pos = f.tell() + f.close() + yield newstuff + +# subprocess.check_output only appeared in python2.7, so this code is taken +# from python source code for compatibility with py2.5/2.6 +class CalledProcessError(Exception): + def __init__(self, returncode, cmd, output=None): + self.returncode = returncode + self.cmd = cmd + self.output = output + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + +def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd, output=output) + return output + + +class FennecProfile(mozrunner.Profile): + preferences = {} + names = ['fennec'] + +class FennecRunner(mozrunner.Runner): + profile_class = FennecProfile + + names = ['fennec'] + + __DARWIN_PATH = '/Applications/Fennec.app/Contents/MacOS/fennec' + + def __init__(self, binary=None, **kwargs): + if sys.platform == 'darwin' and binary and binary.endswith('.app'): + # Assume it's a Fennec app dir. + binary = os.path.join(binary, 'Contents/MacOS/fennec') + + self.__real_binary = binary + + mozrunner.Runner.__init__(self, **kwargs) + + def find_binary(self): + if not self.__real_binary: + if sys.platform == 'darwin': + if os.path.exists(self.__DARWIN_PATH): + return self.__DARWIN_PATH + self.__real_binary = mozrunner.Runner.find_binary(self) + return self.__real_binary + + +class RemoteFennecRunner(mozrunner.Runner): + profile_class = FennecProfile + + names = ['fennec'] + + _REMOTE_PATH = '/mnt/sdcard/jetpack-profile' + _INTENT_PREFIX = 'org.mozilla.' + + _adb_path = None + + def __init__(self, binary=None, **kwargs): + # Check that we have a binary set + if not binary: + raise ValueError("You have to define `--binary` option set to the " + "path to your ADB executable.") + # Ensure that binary refer to a valid ADB executable + output = subprocess.Popen([binary], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate() + output = "".join(output) + if not ("Android Debug Bridge" in output): + raise ValueError("`--binary` option should be the path to your " + "ADB executable.") + self.binary = binary + + mobile_app_name = kwargs['cmdargs'][0] + self.profile = kwargs['profile'] + self._adb_path = binary + + # This pref has to be set to `false` otherwise, we do not receive + # output of adb commands! + subprocess.call([self._adb_path, "shell", + "setprop log.redirect-stdio false"]) + + # Android apps are launched by their "intent" name, + # Automatically detect already installed firefox by using `pm` program + # or use name given as cfx `--mobile-app` argument. + intents = self.getIntentNames() + if not intents: + raise ValueError("Unable to found any Firefox " + "application on your device.") + elif mobile_app_name: + if not mobile_app_name in intents: + raise ValueError("Unable to found Firefox application " + "with intent name '%s'\n" + "Available ones are: %s" % + (mobile_app_name, ", ".join(intents))) + self._intent_name = self._INTENT_PREFIX + mobile_app_name + else: + if "firefox" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox" + elif "firefox_beta" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox_beta" + elif "firefox_nightly" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox_nightly" + else: + self._intent_name = self._INTENT_PREFIX + intents[0] + + print "Launching mobile application with intent name " + self._intent_name + + # First try to kill firefox if it is already running + pid = self.getProcessPID(self._intent_name) + if pid != None: + # Send a key "up" signal to mobile-utils addon + # in order to kill running firefox instance + # KEYCODE_DPAD_UP = 19 + # http://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_DPAD_UP + print "Killing running Firefox instance ..." + subprocess.call([self._adb_path, "shell", "input keyevent 19"]) + subprocess.Popen(self.command, stdout=subprocess.PIPE).wait() + time.sleep(2) + + print "Pushing the addon to your device" + + # Create a clean empty profile on the sd card + subprocess.call([self._adb_path, "shell", "rm -r " + self._REMOTE_PATH]) + subprocess.call([self._adb_path, "shell", "mkdir " + self._REMOTE_PATH]) + + # Push the profile folder created by mozrunner to the device + # (we can't simply use `adb push` as it doesn't copy empty folders) + localDir = self.profile.profile + remoteDir = self._REMOTE_PATH + for root, dirs, files in os.walk(localDir, followlinks='true'): + relRoot = os.path.relpath(root, localDir) + # Note about os.path usage below: + # Local files may be using Windows `\` separators but + # remote are always `/`, so we need to convert local ones to `/` + for file in files: + localFile = os.path.join(root, file) + remoteFile = remoteDir.replace("/", os.sep) + if relRoot != ".": + remoteFile = os.path.join(remoteFile, relRoot) + remoteFile = os.path.join(remoteFile, file) + remoteFile = "/".join(remoteFile.split(os.sep)) + subprocess.Popen([self._adb_path, "push", localFile, remoteFile], + stderr=subprocess.PIPE).wait() + for dir in dirs: + targetDir = remoteDir.replace("/", os.sep) + if relRoot != ".": + targetDir = os.path.join(targetDir, relRoot) + targetDir = os.path.join(targetDir, dir) + targetDir = "/".join(targetDir.split(os.sep)) + # `-p` option is not supported on all devices! + subprocess.call([self._adb_path, "shell", "mkdir " + targetDir]) + + @property + def command(self): + """Returns the command list to run.""" + return [self._adb_path, + "shell", + "am start " + + "-a android.activity.MAIN " + + "-n " + self._intent_name + "/" + self._intent_name + ".App " + + "--es args \"-profile " + self._REMOTE_PATH + "\"" + ] + + def start(self): + subprocess.call(self.command) + + def getProcessPID(self, processName): + p = subprocess.Popen([self._adb_path, "shell", "ps"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + line = p.stdout.readline() + while line: + columns = line.split() + pid = columns[1] + name = columns[-1] + line = p.stdout.readline() + if processName in name: + return pid + return None + + def getIntentNames(self): + p = subprocess.Popen([self._adb_path, "shell", "pm list packages"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + names = [] + for line in p.stdout.readlines(): + line = re.sub("(^package:)|\s", "", line) + if self._INTENT_PREFIX in line: + names.append(line.replace(self._INTENT_PREFIX, "")) + return names + + +class XulrunnerAppProfile(mozrunner.Profile): + preferences = {} + names = [] + +class XulrunnerAppRunner(mozrunner.Runner): + """ + Runner for any XULRunner app. Can use a Firefox binary in XULRunner + mode to execute the app, or can use XULRunner itself. Expects the + app's application.ini to be passed in as one of the items in + 'cmdargs' in the constructor. + + This class relies a lot on the particulars of mozrunner.Runner's + implementation, and does some unfortunate acrobatics to get around + some of the class' limitations/assumptions. + """ + + profile_class = XulrunnerAppProfile + + # This is a default, and will be overridden in the instance if + # Firefox is used in XULRunner mode. + names = ['xulrunner'] + + # Default location of XULRunner on OS X. + __DARWIN_PATH = "/Library/Frameworks/XUL.framework/xulrunner-bin" + __LINUX_PATH = "/usr/bin/xulrunner" + + # What our application.ini's path looks like if it's part of + # an "installed" XULRunner app on OS X. + __DARWIN_APP_INI_SUFFIX = '.app/Contents/Resources/application.ini' + + def __init__(self, binary=None, **kwargs): + if sys.platform == 'darwin' and binary and binary.endswith('.app'): + # Assume it's a Firefox app dir. + binary = os.path.join(binary, 'Contents/MacOS/firefox-bin') + + self.__app_ini = None + self.__real_binary = binary + + mozrunner.Runner.__init__(self, **kwargs) + + # See if we're using a genuine xulrunner-bin from the XULRunner SDK, + # or if we're being asked to use Firefox in XULRunner mode. + self.__is_xulrunner_sdk = 'xulrunner' in self.binary + + if sys.platform == 'linux2' and not self.env.get('LD_LIBRARY_PATH'): + self.env['LD_LIBRARY_PATH'] = os.path.dirname(self.binary) + + newargs = [] + for item in self.cmdargs: + if 'application.ini' in item: + self.__app_ini = item + else: + newargs.append(item) + self.cmdargs = newargs + + if not self.__app_ini: + raise ValueError('application.ini not found in cmdargs') + if not os.path.exists(self.__app_ini): + raise ValueError("file does not exist: '%s'" % self.__app_ini) + + if (sys.platform == 'darwin' and + self.binary == self.__DARWIN_PATH and + self.__app_ini.endswith(self.__DARWIN_APP_INI_SUFFIX)): + # If the application.ini is in an app bundle, then + # it could be inside an "installed" XULRunner app. + # If this is the case, use the app's actual + # binary instead of the XUL framework's, so we get + # a proper app icon, etc. + new_binary = '/'.join(self.__app_ini.split('/')[:-2] + + ['MacOS', 'xulrunner']) + if os.path.exists(new_binary): + self.binary = new_binary + + @property + def command(self): + """Returns the command list to run.""" + + if self.__is_xulrunner_sdk: + return [self.binary, self.__app_ini, '-profile', + self.profile.profile] + else: + return [self.binary, '-app', self.__app_ini, '-profile', + self.profile.profile] + + def __find_xulrunner_binary(self): + if sys.platform == 'darwin': + if os.path.exists(self.__DARWIN_PATH): + return self.__DARWIN_PATH + if sys.platform == 'linux2': + if os.path.exists(self.__LINUX_PATH): + return self.__LINUX_PATH + return None + + def find_binary(self): + # This gets called by the superclass constructor. It will + # always get called, even if a binary was passed into the + # constructor, because we want to have full control over + # what the exact setting of self.binary is. + + if not self.__real_binary: + self.__real_binary = self.__find_xulrunner_binary() + if not self.__real_binary: + dummy_profile = {} + runner = mozrunner.FirefoxRunner(profile=dummy_profile) + self.__real_binary = runner.find_binary() + self.names = runner.names + return self.__real_binary + +def run_app(harness_root_dir, manifest_rdf, harness_options, + app_type, binary=None, profiledir=None, verbose=False, + enforce_timeouts=False, + logfile=None, addons=None, args=None, extra_environment={}, + norun=None, + used_files=None, enable_mobile=False, + mobile_app_name=None): + if binary: + binary = os.path.expanduser(binary) + + if addons is None: + addons = [] + else: + addons = list(addons) + + cmdargs = [] + preferences = dict(DEFAULT_COMMON_PREFS) + + # For now, only allow running on Mobile with --force-mobile argument + if app_type in ["fennec", "fennec-on-device"] and not enable_mobile: + print """ + WARNING: Firefox Mobile support is still experimental. + If you would like to run an addon on this platform, use --force-mobile flag: + + cfx --force-mobile""" + return 0 + + if app_type == "fennec-on-device": + profile_class = FennecProfile + preferences.update(DEFAULT_FENNEC_PREFS) + runner_class = RemoteFennecRunner + # We pass the intent name through command arguments + cmdargs.append(mobile_app_name) + elif enable_mobile or app_type == "fennec": + profile_class = FennecProfile + preferences.update(DEFAULT_FENNEC_PREFS) + runner_class = FennecRunner + elif app_type == "xulrunner": + profile_class = XulrunnerAppProfile + runner_class = XulrunnerAppRunner + cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) + elif app_type == "firefox": + profile_class = mozrunner.FirefoxProfile + preferences.update(DEFAULT_FIREFOX_PREFS) + runner_class = mozrunner.FirefoxRunner + elif app_type == "thunderbird": + profile_class = mozrunner.ThunderbirdProfile + preferences.update(DEFAULT_THUNDERBIRD_PREFS) + runner_class = mozrunner.ThunderbirdRunner + else: + raise ValueError("Unknown app: %s" % app_type) + if sys.platform == 'darwin' and app_type != 'xulrunner': + cmdargs.append('-foreground') + + if args: + cmdargs.extend(shlex.split(args)) + + # TODO: handle logs on remote device + if app_type != "fennec-on-device": + # tempfile.gettempdir() was constant, preventing two simultaneous "cfx + # run"/"cfx test" on the same host. On unix it points at /tmp (which is + # world-writeable), enabling a symlink attack (e.g. imagine some bad guy + # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile + # gives us a unique filename that fixes both problems. We leave the + # (0-byte) file in place until the browser-side code starts writing to + # it, otherwise the symlink attack becomes possible again. + fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") + os.close(fileno) + harness_options['resultFile'] = resultfile + + def maybe_remove_logfile(): + if os.path.exists(logfile): + os.remove(logfile) + + logfile_tail = None + + # We always buffer output through a logfile for two reasons: + # 1. On Windows, it's the only way to print console output to stdout/err. + # 2. It enables us to keep track of the last time output was emitted, + # so we can raise an exception if the test runner hangs. + if not logfile: + fileno,logfile = tempfile.mkstemp(prefix="harness-log-") + os.close(fileno) + logfile_tail = follow_file(logfile) + atexit.register(maybe_remove_logfile) + + logfile = os.path.abspath(os.path.expanduser(logfile)) + maybe_remove_logfile() + + if app_type != "fennec-on-device": + harness_options['logFile'] = logfile + + env = {} + env.update(os.environ) + env['MOZ_NO_REMOTE'] = '1' + env['XPCOM_DEBUG_BREAK'] = 'stack' + env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1' + env.update(extra_environment) + if norun: + cmdargs.append("-no-remote") + + # Create the addon XPI so mozrunner will copy it to the profile it creates. + # We delete it below after getting mozrunner to create the profile. + from cuddlefish.xpi import build_xpi + xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') + build_xpi(template_root_dir=harness_root_dir, + manifest=manifest_rdf, + xpi_path=xpi_path, + harness_options=harness_options, + limit_to=used_files) + addons.append(xpi_path) + + starttime = last_output_time = time.time() + + # Redirect runner output to a file so we can catch output not generated + # by us. + # In theory, we could do this using simple redirection on all platforms + # other than Windows, but this way we only have a single codepath to + # maintain. + fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-") + os.close(fileno) + outfile_tail = follow_file(outfile) + def maybe_remove_outfile(): + if os.path.exists(outfile): + os.remove(outfile) + atexit.register(maybe_remove_outfile) + outf = open(outfile, "w") + popen_kwargs = { 'stdout': outf, 'stderr': outf} + + profile = None + + if app_type == "fennec-on-device": + # Install a special addon when we run firefox on mobile device + # in order to be able to kill it + mydir = os.path.dirname(os.path.abspath(__file__)) + addon_dir = os.path.join(mydir, "mobile-utils") + addons.append(addon_dir) + + # the XPI file is copied into the profile here + profile = profile_class(addons=addons, + profile=profiledir, + preferences=preferences) + + # Delete the temporary xpi file + os.remove(xpi_path) + + runner = runner_class(profile=profile, + binary=binary, + env=env, + cmdargs=cmdargs, + kp_kwargs=popen_kwargs) + + sys.stdout.flush(); sys.stderr.flush() + + if app_type == "fennec-on-device": + if not enable_mobile: + print >>sys.stderr, """ + WARNING: Firefox Mobile support is still experimental. + If you would like to run an addon on this platform, use --force-mobile flag: + + cfx --force-mobile""" + return 0 + + # In case of mobile device, we need to get stdio from `adb logcat` cmd: + + # First flush logs in order to avoid catching previous ones + subprocess.call([binary, "logcat", "-c"]) + + # Launch adb command + runner.start() + + # We can immediatly remove temporary profile folder + # as it has been uploaded to the device + profile.cleanup() + # We are not going to use the output log file + outf.close() + + # Then we simply display stdout of `adb logcat` + p = subprocess.Popen([binary, "logcat", "stderr:V stdout:V GeckoConsole:V *:S"], stdout=subprocess.PIPE) + while True: + line = p.stdout.readline() + if line == '': + break + # mobile-utils addon contains an application quit event observer + # that will print this string: + if "APPLICATION-QUIT" in line: + break + + if verbose: + # if --verbose is given, we display everything: + # All JS Console messages, stdout and stderr. + m = CLEANUP_ADB.match(line) + if not m: + print line.rstrip() + continue + print m.group(3) + else: + # Otherwise, display addons messages dispatched through + # console.[info, log, debug, warning, error](msg) + m = FILTER_ONLY_CONSOLE_FROM_ADB.match(line) + if m: + print m.group(2) + + print >>sys.stderr, "Program terminated successfully." + return 0 + + + print >>sys.stderr, "Using binary at '%s'." % runner.binary + + # Ensure cfx is being used with Firefox 4.0+. + # TODO: instead of dying when Firefox is < 4, warn when Firefox is outside + # the minVersion/maxVersion boundaries. + version_output = check_output(runner.command + ["-v"]) + # Note: this regex doesn't handle all valid versions in the Toolkit Version + # Format <https://developer.mozilla.org/en/Toolkit_version_format>, just the + # common subset that we expect Mozilla apps to use. + mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec) ((\d+)\.\S*)", + version_output) + if not mo: + # cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox + # version. + print """ + WARNING: cannot determine Firefox version; please ensure you are running + a Mozilla application equivalent to Firefox 4.0 or greater. + """ + elif mo.group(1) == "Fennec": + # For now, only allow running on Mobile with --force-mobile argument + if not enable_mobile: + print """ + WARNING: Firefox Mobile support is still experimental. + If you would like to run an addon on this platform, use --force-mobile flag: + + cfx --force-mobile""" + return + else: + version = mo.group(3) + if int(version) < 4: + print """ + cfx requires Firefox 4 or greater and is unable to find a compatible + binary. Please install a newer version of Firefox or provide the path to + your existing compatible version with the --binary flag: + + cfx --binary=PATH_TO_FIREFOX_BINARY""" + return + + # Set the appropriate extensions.checkCompatibility preference to false, + # so the tests run even if the SDK is not marked as compatible with the + # version of Firefox on which they are running, and we don't have to + # ensure we update the maxVersion before the version of Firefox changes + # every six weeks. + # + # The regex we use here is effectively the same as BRANCH_REGEX from + # /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps + # use to determine whether or not to load an incompatible addon. + # + br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I) + if br: + prefname = 'extensions.checkCompatibility.' + br.group(1) + profile.preferences[prefname] = False + # Calling profile.set_preferences here duplicates the list of prefs + # in prefs.js, since the profile calls self.set_preferences in its + # constructor, but that is ok, because it doesn't change the set of + # preferences that are ultimately registered in Firefox. + profile.set_preferences(profile.preferences) + + print >>sys.stderr, "Using profile at '%s'." % profile.profile + sys.stderr.flush() + + if norun: + print "To launch the application, enter the following command:" + print " ".join(runner.command) + " " + (" ".join(runner.cmdargs)) + return 0 + + runner.start() + + done = False + result = None + try: + while not done: + time.sleep(0.05) + for tail in (logfile_tail, outfile_tail): + if tail: + new_chars = tail.next() + if new_chars: + last_output_time = time.time() + sys.stderr.write(new_chars) + sys.stderr.flush() + if os.path.exists(resultfile): + result = open(resultfile).read() + if result: + if result in ['OK', 'FAIL']: + done = True + else: + sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result))) + sys.stderr.write("'"+result+"'\n") + if enforce_timeouts: + if time.time() - last_output_time > OUTPUT_TIMEOUT: + raise Exception("Test output exceeded timeout (%ds)." % + OUTPUT_TIMEOUT) + if time.time() - starttime > RUN_TIMEOUT: + raise Exception("Test run exceeded timeout (%ds)." % + RUN_TIMEOUT) + except: + runner.stop() + raise + else: + runner.wait(10) + finally: + outf.close() + if profile: + profile.cleanup() + + print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime) + + if result == 'OK': + print >>sys.stderr, "Program terminated successfully." + return 0 + else: + print >>sys.stderr, "Program terminated unsuccessfully." + return -1 diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/templates.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/templates.py new file mode 100644 index 0000000..a04bb9a --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/templates.py @@ -0,0 +1,83 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +#Template used by main.js +MAIN_JS = '''\ +const widgets = require("widget"); +const tabs = require("tabs"); + +var widget = widgets.Widget({ + id: "mozilla-link", + label: "Mozilla website", + contentURL: "http://www.mozilla.org/favicon.ico", + onClick: function() { + tabs.open("http://www.mozilla.org/"); + } +}); + +console.log("The add-on is running."); +''' + +#Template used by test-main.js +TEST_MAIN_JS = '''\ +const main = require("main"); + +exports.test_test_run = function(test) { + test.pass("Unit test running!"); +}; + +exports.test_id = function(test) { + test.assert(require("self").id.length > 0); +}; + +exports.test_url = function(test) { + require("request").Request({ + url: "http://www.mozilla.org/", + onComplete: function(response) { + test.assertEqual(response.statusText, "OK"); + test.done(); + } + }).get(); + test.waitUntilDone(20000); +}; + +exports.test_open_tab = function(test) { + const tabs = require("tabs"); + tabs.open({ + url: "http://www.mozilla.org/", + onReady: function(tab) { + test.assertEqual(tab.url, "http://www.mozilla.org/"); + test.done(); + } + }); + test.waitUntilDone(20000); +}; +''' + +#Template used by main.md +MAIN_JS_DOC = '''\ +The main module is a program that creates a widget. When a user clicks on +the widget, the program loads the mozilla.org website in a new tab. +''' + +#Template used by README.md +README_DOC = '''\ +This is the %(name)s add-on. It contains: + +* A program (lib/main.js). +* A few tests. +* Some meager documentation. +''' + +#Template used by package.json +PACKAGE_JSON = '''\ +{ + "name": "%(name)s", + "fullName": "%(fullName)s", + "description": "a basic add-on", + "author": "", + "license": "MPL 2.0", + "version": "0.1" +} +''' diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/__init__.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/__init__.py new file mode 100644 index 0000000..c1a7fd2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/__init__.py @@ -0,0 +1,65 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest +import doctest +import glob + +env_root = os.environ['CUDDLEFISH_ROOT'] + +def get_tests(): + import cuddlefish + import cuddlefish.tests + + tests = [] + packages = [cuddlefish, cuddlefish.tests] + for package in packages: + path = os.path.abspath(package.__path__[0]) + pynames = glob.glob(os.path.join(path, '*.py')) + for filename in pynames: + basename = os.path.basename(filename) + module_name = os.path.splitext(basename)[0] + full_name = "%s.%s" % (package.__name__, module_name) + module = __import__(full_name, fromlist=[package.__name__]) + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(module) + for test in suite: + tests.append(test) + + finder = doctest.DocTestFinder() + doctests = finder.find(module) + for test in doctests: + if len(test.examples) > 0: + tests.append(doctest.DocTestCase(test)) + + md_dir = os.path.join(env_root, 'dev-guide') + doctest_opts = (doctest.NORMALIZE_WHITESPACE | + doctest.REPORT_UDIFF) + for dirpath, dirnames, filenames in os.walk(md_dir): + for filename in filenames: + if filename.endswith('.md'): + absname = os.path.join(dirpath, filename) + tests.append(doctest.DocFileTest( + absname, + module_relative=False, + optionflags=doctest_opts + )) + + return tests + +def run(verbose=False): + if verbose: + verbosity = 2 + else: + verbosity = 1 + + tests = get_tests() + suite = unittest.TestSuite(tests) + runner = unittest.TextTestRunner(verbosity=verbosity) + return runner.run(suite) + +if __name__ == '__main__': + run() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/main.js new file mode 100644 index 0000000..980ea88 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/main.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +const { Cc, Ci } = require("chrome"); + +exports.main = function(options, callbacks) { + // Close Firefox window. Firefox should quit. + require("api-utils/window-utils").activeBrowserWindow.close(); + + // But not on Mac where it stay alive! We have to request application quit. + if (require("api-utils/runtime").OS == "Darwin") { + let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. + getService(Ci.nsIAppStartup); + appStartup.quit(appStartup.eAttemptQuit); + } +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/package.json new file mode 100644 index 0000000..afbc158 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/package.json @@ -0,0 +1,6 @@ +{ + "id": "simplest-test", + "directories": { + "lib": "." + } +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/tests/test-minimal.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/tests/test-minimal.js new file mode 100644 index 0000000..533cd34 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/addons/simplest-test/tests/test-minimal.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.minimalTest = function(test) { + test.assert(true); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json new file mode 100644 index 0000000..8d56d74 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json @@ -0,0 +1,5 @@ +{ + "loader": "lib/main.js", + "icon": "explicit-icon.png", + "icon64": "explicit-icon64.png" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json new file mode 100644 index 0000000..3f0e241 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json new file mode 100644 index 0000000..3f0e241 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json new file mode 100644 index 0000000..e83a9d4 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/bar-loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json new file mode 100644 index 0000000..4648df6 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json @@ -0,0 +1,4 @@ +{ + "loader": "lib/foo-loader.js", + "dependencies": ["bar"] +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md new file mode 100644 index 0000000..54518d3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +minimal docs diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js new file mode 100644 index 0000000..aeda0e7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json new file mode 100644 index 0000000..45d409a --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json @@ -0,0 +1,5 @@ +{ + "name": "jspath-one", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension." +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json new file mode 100644 index 0000000..ccc61b2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/foo-loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json new file mode 100644 index 0000000..ccc61b2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/foo-loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json new file mode 100644 index 0000000..c2d22aa --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json @@ -0,0 +1 @@ +{} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json new file mode 100644 index 0000000..100249f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json @@ -0,0 +1,3 @@ +{ + "loader": "loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json new file mode 100644 index 0000000..f79250e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json @@ -0,0 +1,4 @@ +{ + "directories": { "lib": "./alt-lib" }, + "loader": "alt-lib/loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js new file mode 100644 index 0000000..2daeb35 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md new file mode 100644 index 0000000..c92ddb8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +I am documentation for foo. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json new file mode 100644 index 0000000..b017baa --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json @@ -0,0 +1,4 @@ +{ + "lib": "./alt2-lib", + "loader": "alt2-lib/loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js new file mode 100644 index 0000000..bd0cfa9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md new file mode 100644 index 0000000..54518d3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +minimal docs diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js new file mode 100644 index 0000000..aeda0e7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json new file mode 100644 index 0000000..de868f7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json @@ -0,0 +1,6 @@ +{ + "name": "extra-options", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "loader": "lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json new file mode 100644 index 0000000..d18aa3d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json @@ -0,0 +1,10 @@ +{ + "name": "empty", + "license": "MPL 2.0", + "author": "", + "version": "0.1", + "fullName": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "unpack": false, + "description": "test unpack= support" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json new file mode 100644 index 0000000..9bcf1eb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json @@ -0,0 +1,10 @@ +{ + "name": "empty", + "license": "MPL 2.0", + "author": "", + "version": "0.1", + "fullName": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "unpack": true, + "description": "test unpack= support" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json new file mode 100644 index 0000000..45d1d91 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json @@ -0,0 +1,9 @@ +{ + "name": "empty", + "license": "MPL 2.0", + "author": "", + "version": "0.1", + "fullName": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "description": "test unpack= support" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js new file mode 100644 index 0000000..ad54ae7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +if (this.sendMessage) { +} else { + require('bar'); + + exports.register = function(process) { + }; +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js new file mode 100644 index 0000000..fe9e4fb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +require('chrome'); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js new file mode 100644 index 0000000..55633d1 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +require('bar'); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/lib/main.js new file mode 100644 index 0000000..e32a30f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = "'main' mainly reigns in main(.js)"; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/package.json new file mode 100644 index 0000000..98e4b85 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/five/package.json @@ -0,0 +1,3 @@ +{ "name": "five", + "main": "./lib/main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js new file mode 100644 index 0000000..7e1ce7e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = 42; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json new file mode 100644 index 0000000..3010fae --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json @@ -0,0 +1,4 @@ +{ "name": "four-a", + "directories": {"lib": "lib"}, + "main": "./topfiles/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js new file mode 100644 index 0000000..7e1ce7e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = 42; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/lib/main.js new file mode 100644 index 0000000..b95f7bd --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +var a = require("four-a"); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/package.json new file mode 100644 index 0000000..53180b9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/four/package.json @@ -0,0 +1,3 @@ +{ "name": "four", + "main": "main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/main.js new file mode 100644 index 0000000..7b1f0de --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/main.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +var panel = require("panel"); +var two = require("two.js"); +var a = require("./two"); +var b = require("addon-kit/tabs.js"); +var c = require("./subdir/three"); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js new file mode 100644 index 0000000..b594f3c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.foo = 1; +var main = require("../main"); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/two.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/two.js new file mode 100644 index 0000000..9765219 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/lib/two.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + + +// this ought to find our sibling, not packages/development-mode/lib/main.js +var main = require("main"); +exports.foo = 1; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/package.json new file mode 100644 index 0000000..edd2b17 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/one/package.json @@ -0,0 +1,4 @@ +{ "name": "one", + "id": "jid1@jetpack", + "main": "main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/data/text.data b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/data/text.data new file mode 100644 index 0000000..1269488 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/data/text.data @@ -0,0 +1 @@ +data diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js new file mode 100644 index 0000000..b860821 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +var self = require("self"); // trigger inclusion of data +exports.main = function () { console.log("main"); }; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js new file mode 100644 index 0000000..a7b1c14 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.unused = "just pretend I'm not here"; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/package.json new file mode 100644 index 0000000..922c77d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/seven/package.json @@ -0,0 +1,4 @@ +{ + "name": "seven", + "id": "jid7" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js new file mode 100644 index 0000000..ada31ef --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.unused = "I am."; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/package.json new file mode 100644 index 0000000..906b249 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/package.json @@ -0,0 +1,3 @@ +{ "name": "six", + "main": "./unreachable" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/unreachable.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/unreachable.js new file mode 100644 index 0000000..e8b229c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/six/unreachable.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = "I am outside lib/ and cannot be reached, yet"; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/msg.txt b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/msg.txt new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/msg.txt @@ -0,0 +1 @@ +hello world diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/subdir/submsg.txt b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/subdir/submsg.txt new file mode 100644 index 0000000..d2cfe80 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/subdir/submsg.txt @@ -0,0 +1 @@ +hello subdir diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js new file mode 100644 index 0000000..cee7380 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = 42; +require("./subdir/subfile"); +require("self"); // trigger inclusion of our data/ directory + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js new file mode 100644 index 0000000..aec24d0 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = "I should be included in a subdir"; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js new file mode 100644 index 0000000..36c4a4e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = "unused, linker should not include me in the XPI"; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties new file mode 100644 index 0000000..980ac46 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +Yes= Oui diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json new file mode 100644 index 0000000..6b796fc --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json @@ -0,0 +1,3 @@ +{ "name": "three-a", + "main": "./lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js new file mode 100644 index 0000000..7e1ce7e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = 42; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties new file mode 100644 index 0000000..c1bf146 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +No= Non +one= un diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json new file mode 100644 index 0000000..c0ff5ee --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json @@ -0,0 +1,3 @@ +{ "name": "three-b", + "main": "./lib/main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js new file mode 100644 index 0000000..7e1ce7e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = 42; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js new file mode 100644 index 0000000..5878496 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.foo = "you found me down here"; + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties new file mode 100644 index 0000000..dac3f13 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +No= Nein +What?= Quoi? +plural=other +plural[one]=one +uft8_value=é diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json new file mode 100644 index 0000000..169c914 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json @@ -0,0 +1,3 @@ +{ "name": "three-c", + "main": "lib/main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/lib/main.js new file mode 100644 index 0000000..4f59443 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +var a = require("three-a"); +var b = require("three-b"); +var c = require("three-c"); +var c3 = require("three-c/sub/foo"); diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/package.json new file mode 100644 index 0000000..cbfbc5b --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/package.json @@ -0,0 +1,3 @@ +{ "name": "three", + "main": "main" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js new file mode 100644 index 0000000..edbc08e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// dummy diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js new file mode 100644 index 0000000..edbc08e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// dummy diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js new file mode 100644 index 0000000..edbc08e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// dummy diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/lib/main.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/package.json new file mode 100644 index 0000000..f1d51b0 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/no-prefs/package.json @@ -0,0 +1,6 @@ +{ + "id": "jid1-fZHqN9JfrDBa8A", + "fullName": "No Prefs Test", + "author": "Erik Vold", + "loader": "lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/lib/main.js new file mode 100644 index 0000000..b7e0a1d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/package.json new file mode 100644 index 0000000..1b82728 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/preferences-files/packages/simple-prefs/package.json @@ -0,0 +1,18 @@ +{ + "id": "jid1-fZHqN9JfrDBa8A", + "fullName": "Simple Prefs Test", + "author": "Erik Vold", + "preferences": [{ + "name": "test", + "type": "bool", + "title": "tëst", + "value": false + }, + { + "name": "test2", + "type": "string", + "title": "tëst", + "value": "ünicødé" + }], + "loader": "lib/main.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/index.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/index.md new file mode 100644 index 0000000..0f03259 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/index.md @@ -0,0 +1,7 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +# An Imposing Title # + +*Some words!* diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md new file mode 100644 index 0000000..9859f33 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md @@ -0,0 +1,7 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +## A heading ## + +*Some words!* diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html new file mode 100644 index 0000000..d40a2e8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +another file
\ No newline at end of file diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html new file mode 100644 index 0000000..56c993d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html @@ -0,0 +1,161 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" +"http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> +<head> + <base > + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <script type="text/javascript" src="static-files/syntaxhighlighter/scripts/shCore.js"></script> + <script type="text/javascript" src="static-files/syntaxhighlighter/scripts/shBrushCss.js"></script> + <script type="text/javascript" src="static-files/syntaxhighlighter/scripts/shBrushXml.js"></script> + <script type="text/javascript" src="static-files/syntaxhighlighter/scripts/shBrushJScript.js"></script> + <link rel="stylesheet" type="text/css" media="all" href="static-files/css/base.css"> + <link rel="stylesheet" type="text/css" media="all" href="static-files/css/header.css"> + <link rel="stylesheet" type="text/css" media="all" href="static-files/css/footer.css"> + <link rel="stylesheet" type="text/css" media="all" href="static-files/css/sdk-docs.css"> + <link rel="stylesheet" type="text/css" media="all" href="static-files/css/api-reference.css"> + <link rel="stylesheet" type="text/css" href="static-files/syntaxhighlighter/styles/shCore.css"> + <link rel="stylesheet" type="text/css" href="static-files/syntaxhighlighter/styles/shThemeDefault.css"> + <!--[if IE]> + <style type="text/css"> + .package-summary .module, + .package-entry .module, + .package-detail .module { + display: block; + } + </style> + <![endif]--> + + <link rel="shortcut icon" type="image/x-icon" href="static-files/media/favicon.png"> + <title></title> +</head> +<body> + +<header id="global-header"> + <div class="funnel"> + <a id="mozilla-tab" href="http://www.mozilla.org/?ref=logo">Mozilla</a> + <div class="menu"> + <p> + <a href="https://builder.addons.mozilla.org/">Add-on Builder</a> + </p> + <p> + <a href="https://addons.mozilla.org/en-US/developers/">Developer Hub</a> + </p> + </div> +</header> + + +<header id="site-header"> + <div class="funnel"> + <h1> + <a href="dev-guide/welcome.html">Add-on SDK<span></span></a> + </h1> + <div id="version"></div> + </div> +</header> + + <div id="container"> + + <div id="columns"> + + <div id="main-content-column" class="column"> + <div id="toc"></div> + <div id="main-content"></div> + </div> + + <div id="sidebar" class="column"> + <div class="sidebar-section" id="addon-development"> + <h2 class="sidebar-section-header">Developer Guide</h2> + <ul class="sidebar-section-contents" id="default-section-contents"> + + <li class="sidebar-subsection"> + <a href="dev-guide/tutorials/installation.html"><h3>Installation</h3></a> + </li> + <li class="sidebar-subsection"> + <a href="dev-guide/tutorials/tutorials.html"><h3 class="sidebar-subsection-header">Tutorials</h3></a> + </li> + + <li class="sidebar-subsection"> + <a href="dev-guide/guides/index.html"><h3 class="sidebar-subsection-header">Guides</h3></a> + </li> + + <li class="sidebar-subsection" id="third-party-packages-subsection"> + <a href="dev-guide/third-party-apis.html"><h3 class="sidebar-subsection-header">Third-Party APIs</h3></a> + <div class="sidebar-subsection-contents"> + <ul id="third-party-package-summaries"></ul> + </div> + </li> + + <li class="sidebar-subsection"> + <a href="dev-guide/high-level-apis.html"><h3 class="sidebar-subsection-header">High-Level APIs</h3></a> + <div class="sidebar-subsection-contents"> + <ul id="high-level-package-summaries"></ul> + </div> + </li> + + <li class="sidebar-subsection"> + <h3 class="sidebar-subsection-header">Tools Reference</h3> + <div class="sidebar-subsection-contents"> + <a href="dev-guide/console.html">console</a> + <a href="dev-guide/cfx-tool.html">cfx</a> + <a href="dev-guide/package-spec.html">package.json</a> + </div> + </li> + + <li class="sidebar-subsection"> + <a href="dev-guide/low-level-apis.html"><h3 class="sidebar-subsection-header">Low-Level APIs</h3></a> + <div class="sidebar-subsection-contents"> + <ul id="low-level-package-summaries"></ul> + </div> + </li> + + </ul> + </div> + + <ul class="sidebar-section" id="appendices"> + <li><a href="https://wiki.mozilla.org/Labs/Jetpack/Release_Notes"><h3>Release Notes</h3></a></li> + <li><a href="https://wiki.mozilla.org/Labs/Jetpack"><h3>Jetpack Wiki</h3></a></li> + <li><a href="dev-guide/glossary.html"><h3>Glossary</h3></a></li> + <li><a href="dev-guide/credits.html"><h3>Credits</h3></a></li> + + </ul> +<!--end of sidebar column--> + </div> +<!--end of 'columns'--> +<div class="clearfooter"></div> +</div> +</div> + +<div role="contentinfo" id="footer"> + <div class="section"> + <img alt="" src="static-files/media/footer-logo-med.png" class="footerlogo"> + <div id="social-footer"> + <ul> + <li>get to know <b>add-ons</b></li> + <li><a href="https://addons.mozilla.org/en-US/firefox/about">About</a></li> + <li><a href="http://blog.mozilla.com/addons">Blog</a></li> + <li class="footer-devhub-link"><a href="https://addons.mozilla.org/en-US/developers/">Developer Hub</a></li> + <li><a href="https://addons.mozilla.org/en-US/firefox/faq">FAQ</a></li> + <li><a href="https://forums.addons.mozilla.org">Forum</a></li> + </ul> + </div> + + <div id="copyright"> + <p id="footer-links"> + <a href="http://mozilla.com/privacy-policy.html">Privacy Policy</a> | + <a href="http://mozilla.com/about/legal.html">Legal Notices</a> | + <a href="http://mozilla.com/legal/fraud-report/index.html">Report Trademark Abuse</a> + | <a href="https://addons.mozilla.org/z/en-US/developers/" class="mobile-link">View Mobile Site</a> + </p> + <p> + Except where otherwise <a href="http://mozilla.com/about/legal.html#site">noted</a>, content on this site is licensed under the <br> <a href="http://creativecommons.org/licenses/by-sa/3.0/"> Creative Commons Attribution Share-Alike License v3.0 </a> or any later version. + </p> + </div> + </div> +</div> + +<script type="text/javascript" src="static-files/js/jquery.js"></script> +<script type="text/javascript" src="static-files/js/main.js"></script> + +</body> + +</html> diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html new file mode 100644 index 0000000..1909a18 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html @@ -0,0 +1,27 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +<html> + +<head> + + <title></title> + +</head> + +<body> + +<div id="left-column"> + +<ul><li id="high-level-package-summaries"></li></ul> + +<ul><li id="low-level-package-summaries"></li></ul> + +</div> + +<div id="right-column"></div> + +</body> + +</html> diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIreference.html b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIreference.html new file mode 100644 index 0000000..e302641 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIreference.html @@ -0,0 +1,469 @@ + +<!DOCTYPE html> + +<html> + +<head> + + <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> + + <base target="_blank"/> + + <link rel="stylesheet" type="text/css" media="all" + + href="../../../css/base.css" /> + + <link rel="stylesheet" type="text/css" media="all" + + href="../../../css/apidocs.css" /> + + <title>Add-on SDK Documentation</title> + + <style type="text/css"> + + body { + + border: 50px solid #FFFFFF; + + } + + </style> + + + + <script type="text/javascript"> + + function rewrite_links() { + + var images = document.getElementsByTagName("img"); + + for (var i = 0; i < images.length; i++) { + + var before = images[i].src.split("packages/")[0]; + + var after = images[i].src.split("/docs")[1]; + + images[i].src = before + after; + + } + + } + + </script> + +</head> + + + +<body onload = "rewrite_links()"> + +<div id="APIsample_module_api_docs" class="module_api_docs"> + <h1>APIsample</h1> + <div class="module_description"><!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + + <h1>Title</h1> + <p>Some text here</p> + <p>This text appears between the API blocks.</p> + <p>Wooo, more text.</p> + <p>Some more text here, at the end of the file.</p> + </div> + + <div class="api_reference"> + <h2 class="api_header">API Reference</h2> + + <div class="api_component_group"> + <h3 class="api_header">Classes</h3> + + <div class="api_component"> + <h4 class="api_name">empty-class</h4> + <p>This class contains nothing.</p> + </div> + + <div class="api_component"> + <h4 class="api_name">only-one-ctor</h4> + <p>This class contains only one constructor.</p> + <div class="api_component_group"> + <h5 class="api_header">Constructors</h5> + + <div class="api_component"> + <h6 class="api_name">one-constructor(options)</h6> + + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + </div> + + </div> + + </div> + + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">two-ctors</h4> + <p>This class contains two constructors.</p> + <div class="api_component_group"> + <h5 class="api_header">Constructors</h5> + + <div class="api_component"> + <h6 class="api_name">one-constructor(options)</h6> + <p>The first constructor.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + </div> + + </div> + + </div> + + <div class="api_component"> + <h6 class="api_name">another-constructor(options)</h6> + <p>The second constructor.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + </div> + + </div> + + </div> + + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">ctor-and-method</h4> + <p>This class contains one constructor and one method.</p> + <div class="api_component_group"> + <h5 class="api_header">Constructors</h5> + + <div class="api_component"> + <h6 class="api_name">one-constructor(options)</h6> + <p>The first constructor.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + </div> + + </div> + + </div> + + </div> + + <div class="api_component_group"> + <h5 class="api_header">Methods</h5> + + <div class="api_component"> + <h6 class="api_name">a-method(options)</h6> + <p>Does things.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An argument.</p> + </div> + + </div> + + </div> + + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">ctor-method-prop-event</h4> + <p>This class contains one constructor, one method, one property and an event.</p> + <div class="api_component_group"> + <h5 class="api_header">Constructors</h5> + + <div class="api_component"> + <h6 class="api_name">one-constructor(options)</h6> + <p>The first constructor.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + </div> + + </div> + + </div> + + </div> + + <div class="api_component_group"> + <h5 class="api_header">Methods</h5> + + <div class="api_component"> + <h6 class="api_name">a-method(options)</h6> + <p>Does things.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An argument.</p> + </div> + + </div> + + </div> + + </div> + + <div class="api_component_group"> + <h5 class="api_header">Properties</h5> + + <div class="api_component"> + <h6 class="api_name">a-property : <span class="datatype">bool</span></h6> + <p>Represents stuff.</p> + </div> + + </div> + + <div class="api_component_group"> + <h5 class="api_header">Events</h5> + + <div class="api_component"> + <h6 class="api_name">message</h6> + <p>Event emitted when the content script sends a message to the add-on.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name"><span class="datatype">JSON</span></div> + <p>The message itself as a JSON-serialized object.</p> + </div> + + </div> + + </div> + + </div> + + </div> + + </div> + + <div class="api_component_group"> + <h3 class="api_header">Functions</h3> + + <div class="api_component"> + <h4 class="api_name">test(argOne, argTwo, argThree, options)</h4> + <p>This is a function which does nothing in particular.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">argOne : <span class="datatype">string</span></div> + <p>This is the first argument.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ argTwo : <span class="datatype">bool</span> ]</div> + <p>This is the second argument.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ argThree = default : <span class="datatype">uri</span> ]</div> + <p>This is the third and final argument. And this is + a test of the ability to do multiple lines of + text.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>Options Bag</p> + <div class="api_component"> + <div class="api_name">[ style : <span class="datatype">string</span> ]</div> + <p>Some style information.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ secondToLastOption = True : <span class="datatype">bool</span> ]</div> + <p>The last property.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ lastOption : <span class="datatype">uri</span> ]</div> + <p>And this time we have + A multiline description + Written as haiku</p> + </div> + + </div> + + </div> + + <div class="returns">Returns: <span class="datatype">object</span> + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">append(options)</h4> + <p>This is a list of options to specify modifications to your slideBar instance.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">options</div> + <p>Pass in all of your options here.</p> + <div class="api_component"> + <div class="api_name">[ icon : <span class="datatype">uri</span> ]</div> + <p>The HREF of an icon to show as the method of accessing your features slideBar</p> + </div> + + <div class="api_component"> + <div class="api_name">[ html : <span class="datatype">string/xml</span> ]</div> + <p>The content of the feature, either as an HTML string, + or an E4X document fragment.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ url : <span class="datatype">uri</span> ]</div> + <p>The url to load into the content area of the feature</p> + </div> + + <div class="api_component"> + <div class="api_name">[ width : <span class="datatype">int</span> ]</div> + <p>Width of the content area and the selected slide size</p> + </div> + + <div class="api_component"> + <div class="api_name">[ persist : <span class="datatype">bool</span> ]</div> + <p>Default slide behavior when being selected as follows: + If true: blah; If false: double blah.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ autoReload : <span class="datatype">bool</span> ]</div> + <p>Automatically reload content on select</p> + </div> + + <div class="api_component"> + <div class="api_name">[ onClick : <span class="datatype">function</span> ]</div> + <p>Callback when the icon is clicked</p> + </div> + + <div class="api_component"> + <div class="api_name">[ onSelect : <span class="datatype">function</span> ]</div> + <p>Callback when the feature is selected</p> + </div> + + <div class="api_component"> + <div class="api_name">[ onReady : <span class="datatype">function</span> ]</div> + <p>Callback when featured is loaded</p> + </div> + + </div> + + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">cool-func.dot(howMuch, double, options, onemore, options2)</h4> + + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name">howMuch : <span class="datatype">string</span></div> + <p>How much cool it is.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ double = true : <span class="datatype">bool</span> ]</div> + <p>In case you just really need to double it.</p> + </div> + + <div class="api_component"> + <div class="api_name">[ options ]</div> + <p>An object-bag of goodies.</p> + <div class="api_component"> + <div class="api_name">callback : <span class="datatype">function</span></div> + <p>The callback</p> + </div> + + <div class="api_component"> + <div class="api_name">[ random : <span class="datatype">bool</span> ]</div> + <p>Do something random?</p> + </div> + + </div> + + <div class="api_component"> + <div class="api_name">[ onemore : <span class="datatype">bool</span> ]</div> + <p>One more paramater</p> + </div> + + <div class="api_component"> + <div class="api_name">[ options2 ]</div> + <p>This is a full description of something + that really sucks. Because I now have a multiline + description of this thingy.</p> + <div class="api_component"> + <div class="api_name">monkey : <span class="datatype">string</span></div> + <p>You heard me right</p> + </div> + + <div class="api_component"> + <div class="api_name">[ freak = true : <span class="datatype">bool</span> ]</div> + <p>Yes, you are a freak.</p> + </div> + + </div> + + </div> + + <div class="returns">Returns: <span class="datatype">string</span><p>A value telling you just how cool you are. + A boa-constructor! + This description can go on for a while, and can even contain + some <strong>realy</strong> fancy things. Like <code>code</code>, or even + ~~~~{.javascript} + // Some code! + ~~~~</p> + </div> + + </div> + + <div class="api_component"> + <h4 class="api_name">random()</h4> + <p>A function that returns a random integer between 0 and 10.</p> + <div class="returns">Returns: <span class="datatype">int</span><p>The random number.</p> + </div> + + </div> + + </div> + + <div class="api_component_group"> + <h3 class="api_header">Events</h3> + + <div class="api_component"> + <h4 class="api_name">open</h4> + <p>A module-level event called open.</p> + <div class="parameter_set"> + <div class="api_component"> + <div class="api_name"><span class="datatype">bool</span></div> + <p>Yes, it's open.</p> + </div> + + </div> + + </div> + + </div> + + </div> + +</div> + +</body> + + + +</html> + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIsample.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIsample.md new file mode 100644 index 0000000..c5090c3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/docs/APIsample.md @@ -0,0 +1,162 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +# Title # + +Some text here + +<api name="test"> +@function +This is a function which does nothing in particular. +@returns {object} + @prop firststring {string} First string + @prop firsturl {url} First URL +@param argOne {string} This is the first argument. +@param [argTwo] {bool} This is the second argument. +@param [argThree=default] {uri} + This is the third and final argument. And this is + a test of the ability to do multiple lines of + text. +@param [options] Options Bag + @prop [style] {string} Some style information. + @prop [secondToLastOption=True] {bool} The last property. + @prop [lastOption] {uri} + And this time we have + A multiline description + Written as haiku +</api> + +This text appears between the API blocks. + +<api name="append"> +@function +This is a list of options to specify modifications to your slideBar instance. +@param options + Pass in all of your options here. + @prop [icon] {uri} The HREF of an icon to show as the method of accessing your features slideBar + @prop [html] {string/xml} + The content of the feature, either as an HTML string, + or an E4X document fragment. + @prop [url] {uri} The url to load into the content area of the feature + @prop [width] {int} Width of the content area and the selected slide size + @prop [persist] {bool} + Default slide behavior when being selected as follows: + If true: blah; If false: double blah. + @prop [autoReload] {bool} Automatically reload content on select + @prop [onClick] {function} Callback when the icon is clicked + @prop [onSelect] {function} Callback when the feature is selected + @prop [onReady] {function} Callback when featured is loaded +</api> + +Wooo, more text. + +<api name="cool-func.dot"> +@function +@returns {string} A value telling you just how cool you are. +A boa-constructor! +This description can go on for a while, and can even contain +some **realy** fancy things. Like `code`, or even +~~~~{.javascript} +// Some code! +~~~~ +@param howMuch {string} How much cool it is. +@param [double=true] {bool} + In case you just really need to double it. +@param [options] An object-bag of goodies. + @prop callback {function} The callback + @prop [random] {bool} Do something random? +@param [onemore] {bool} One more paramater +@param [options2] + This is a full description of something + that really sucks. Because I now have a multiline + description of this thingy. + @prop monkey {string} You heard me right + @prop [freak=true] {bool} + Yes, you are a freak. +</api> + +<api name="random"> +@function +A function that returns a random integer between 0 and 10. +@returns {int} The random number. +</api> + +<api name="empty-class"> +@class +This class contains nothing. +</api> + +<api name="only-one-ctor"> +@class +This class contains only one constructor. +<api name="one-constructor"> +@constructor +@param [options] An object-bag of goodies. +</api> +</api> + +<api name="two-ctors"> +@class +This class contains two constructors. +<api name="one-constructor"> +@constructor +The first constructor. +@param [options] An object-bag of goodies. +</api> +<api name="another-constructor"> +@constructor +The second constructor. +@param [options] An object-bag of goodies. +</api> +</api> + +<api name="ctor-and-method"> +@class +This class contains one constructor and one method. +<api name="one-constructor"> +@constructor +The first constructor. +@param [options] An object-bag of goodies. +</api> +<api name="a-method"> +@method +Does things. +@param [options] An argument. +</api> +</api> + +<api name="ctor-method-prop-event"> +@class +This class contains one constructor, one method, one property and an event. +<api name="one-constructor"> +@constructor +The first constructor. +@param [options] An object-bag of goodies. +</api> +<api name="a-method"> +@method +Does things. +@param [options] An argument. +</api> +<api name="a-property"> +@property {bool} +Represents stuff. +</api> +<api name="message"> +@event +Event emitted when the content script sends a message to the add-on. +@argument {JSON} +The message itself as a JSON-serialized object. +</api> +</api> + +<api name="open"> +@event +A module-level event called open. +@argument {bool} +Yes, it's open. +</api> + +Some more text here, at the end of the file. + diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md new file mode 100644 index 0000000..3845955 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md @@ -0,0 +1,12 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +The `aardvark-feeder` module simplifies feeding aardvarks. + +<api name="feed"> +@function + Feed the aardvark. +@param food {string} + The food. Aardvarks will eat anything. +</api> diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me new file mode 100644 index 0000000..014242c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me @@ -0,0 +1,3 @@ +The docs processor should tolerate (by ignoring) random non-.js files in lib +directories, such as those left around by editors, version-control systems, +or OS metadata like .DS_Store . This file exercises that tolerance. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js new file mode 100644 index 0000000..0264872 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = function(options, callbacks) { + console.log("1 + 1 =", require("bar-module").add(1, 1)); + callbacks.quit(); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too new file mode 100644 index 0000000..066f9b5 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too @@ -0,0 +1,2 @@ +The docs processor should also ignore directories named *.js, and their +contents. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json new file mode 100644 index 0000000..07eb9b9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json @@ -0,0 +1,7 @@ +{ + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "keywords": ["potato"], + "version": "1.0", + "dependencies": ["api-utils", "barbeque"] +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js new file mode 100644 index 0000000..0264872 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = function(options, callbacks) { + console.log("1 + 1 =", require("bar-module").add(1, 1)); + callbacks.quit(); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json new file mode 100644 index 0000000..0e2b552 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json @@ -0,0 +1,8 @@ +{ + "name": "anteater", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "keywords": ["potato"], + "version": "1.0", + "dependencies": ["api-utils", "barbeque"] +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js new file mode 100644 index 0000000..361846d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// This module will be imported by the XPCOM harness/boostrapper +// via Components.utils.import() and is responsible for creating a +// CommonJS module loader. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json new file mode 100644 index 0000000..64eb065 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json @@ -0,0 +1,5 @@ +{ + "description": "A foundational package that provides a CommonJS module loader implementation.", + "keywords": ["potato", "jetpack-low-level"], + "loader": "lib/loader.js" +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js new file mode 100644 index 0000000..ff982ae --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.add = function add(a, b) { + return a + b; +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json new file mode 100644 index 0000000..62e3c12 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json @@ -0,0 +1,4 @@ +{ + "keywords": ["potato", "jetpack-low-level"], + "description": "A package used by 'aardvark' as a library." +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md new file mode 100644 index 0000000..54518d3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +minimal docs diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js new file mode 100644 index 0000000..aeda0e7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json new file mode 100644 index 0000000..530f3c2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json @@ -0,0 +1,4 @@ +{ + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension." +} diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js new file mode 100644 index 0000000..a20bf3f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// This file contains XPCOM code that bootstraps an SDK-based add-on +// by loading its harness-options.json, registering all its resource +// directories, executing its loader, and then executing its program's +// main() function. diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apiparser.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apiparser.py new file mode 100644 index 0000000..c24f5c3 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apiparser.py @@ -0,0 +1,538 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest +from cuddlefish.docs.apiparser import parse_hunks, ParseError + +tests_path = os.path.abspath(os.path.dirname(__file__)) +static_files_path = os.path.join(tests_path, "static-files") + +class ParserTests(unittest.TestCase): + def pathname(self, filename): + return os.path.join(static_files_path, "docs", filename) + + def parse_text(self, text): + return list(parse_hunks(text)) + + def parse(self, pathname): + return self.parse_text(open(pathname).read()) + + def test_parser(self): + parsed = self.parse(self.pathname("APIsample.md")) + #for i,h in enumerate(parsed): + # print i, h + self.assertEqual(parsed[0], + ("version", 4)) + self.assertEqual(parsed[1], + ("markdown", """\ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - 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/. --> + +# Title # + +Some text here + +""")) + + self.assertEqual(parsed[2][0], "api-json") + p_test = parsed[2][1] + self.assertEqual(p_test["name"], "test") + self.assertEqual(p_test["type"], "function") + self.assertEqual(p_test["signature"], "test(argOne, argTwo, \ +argThree, options)") + self.assertEqual(p_test["description"], + "This is a function which does nothing in \ +particular.") + r = p_test["returns"] + self.assertEqual(r["datatype"], "object") + self.assertEqual(r["description"], "") + self.assertEqual(len(r["props"]), 2) + self.assertEqual(r["props"][0]["datatype"], "string") + self.assertEqual(r["props"][0]["description"], "First string") + self.assertEqual(r["props"][1]["datatype"], "url") + self.assertEqual(r["props"][1]["description"], "First URL") + + self.assertEqual(p_test["params"][0], + {"name": "argOne", + "required": True, + "datatype": "string", + "description": "This is the first argument.", + "line_number": 15, + }) + + self.assertEqual(p_test["params"][1], + {"name": "argTwo", + "required": False, + "datatype": "bool", + "description": "This is the second argument.", + "line_number": 16, + }) + + self.assertEqual(p_test["params"][2], + {"name": "argThree", + "required": False, + "default": "default", + "datatype": "uri", + "line_number": 17, + "description": """\ +This is the third and final argument. And this is +a test of the ability to do multiple lines of +text.""", + }) + p3 = p_test["params"][3] + self.assertEqual(p3["name"], "options") + self.assertEqual(p3["required"], False) + self.failIf("type" in p3) + self.assertEqual(p3["description"], "Options Bag") + self.assertEqual(p3["props"][0], + {"name": "style", + "required": False, + "datatype": "string", + "description": "Some style information.", + "line_number": 22, + }) + self.assertEqual(p3["props"][1], + {"name": "secondToLastOption", + "required": False, + "default": "True", + "datatype": "bool", + "description": "The last property.", + "line_number": 23, + }) + self.assertEqual(p3["props"][2]["name"], "lastOption") + self.assertEqual(p3["props"][2]["required"], False) + self.assertEqual(p3["props"][2]["datatype"], "uri") + self.assertEqual(p3["props"][2]["description"], """\ +And this time we have +A multiline description +Written as haiku""") + + self.assertEqual(parsed[3][0], "markdown") + self.assertEqual(parsed[3][1], "\n\nThis text appears between the \ +API blocks.\n\n") + + self.assertEqual(parsed[4][0], "api-json") + p_test = parsed[4][1] + + expected = {'line_number': 32, + 'name': 'append', + 'params': [{'props':[{'line_number': 37, + 'required': False, + 'datatype': 'uri', + 'name': 'icon', + 'description': 'The HREF of an icon to show as the \ +method of accessing your features slideBar'}, + {'line_number': 38, + 'required': False, + 'datatype': 'string/xml', + 'name': 'html', + 'description': 'The content of the feature, either \ +as an HTML string,\nor an E4X document fragment.'}, + {'line_number': 41, + 'required': False, + 'datatype': 'uri', + 'name': 'url', + 'description': 'The url to load into the content area \ +of the feature'}, + {'line_number': 42, + 'required': False, + 'datatype': 'int', + 'name': 'width', + 'description': 'Width of the content area and the \ +selected slide size'}, + {'line_number': 43, + 'required': False, + 'datatype': 'bool', + 'name': 'persist', + 'description': 'Default slide behavior when being \ +selected as follows:\nIf true: blah; If false: double blah.'}, + {'line_number': 46, + 'required': False, + 'datatype': 'bool', + 'name': 'autoReload', + 'description': 'Automatically reload content on \ +select'}, + {'line_number': 47, + 'required': False, + 'datatype': 'function', + 'name': 'onClick', + 'description': 'Callback when the icon is \ +clicked'}, + {'line_number': 48, + 'required': False, + 'datatype': 'function', + 'name': 'onSelect', + 'description': 'Callback when the feature is selected'}, + {'line_number': 49, + 'required': False, + 'datatype': 'function', + 'name': 'onReady', + 'description': + 'Callback when featured is loaded'}], + 'line_number': 35, + 'required': True, + 'name': 'options', + 'description': 'Pass in all of your options here.'}], + 'signature': 'append(options)', + 'type': 'function', + 'description': 'This is a list of options to specify modifications to your \ +slideBar instance.'} + self.assertEqual(p_test, expected) + + self.assertEqual(parsed[6][0], "api-json") + p_test = parsed[6][1] + self.assertEqual(p_test["name"], "cool-func.dot") + self.assertEqual(p_test["signature"], "cool-func.dot(howMuch, double, \ +options, onemore, options2)") + self.assertEqual(p_test["returns"]["description"], + """\ +A value telling you just how cool you are. +A boa-constructor! +This description can go on for a while, and can even contain +some **realy** fancy things. Like `code`, or even +~~~~{.javascript} +// Some code! +~~~~""") + self.assertEqual(p_test["params"][2]["props"][0], + {"name": "callback", + "required": True, + "datatype": "function", + "line_number": 67, + "description": "The callback", + }) + self.assertEqual(p_test["params"][2]["props"][1], + {"name": "random", + "required": False, + "datatype": "bool", + "line_number": 68, + "description": "Do something random?", + }) + + p_test = parsed[8][1] + self.assertEqual(p_test["signature"],"random()") + + # tests for classes + #1) empty class + p_test = parsed[10][1] + self.assertEqual(len(p_test), 4) + self.assertEqual(p_test["name"], "empty-class") + self.assertEqual(p_test["description"], "This class contains nothing.") + self.assertEqual(p_test["type"], "class") + # 2) class with just one ctor + p_test = parsed[12][1] + self.assertEqual(len(p_test), 5) + self.assertEqual(p_test["name"], "only-one-ctor") + self.assertEqual(p_test["description"], "This class contains only \ +one constructor.") + self.assertEqual(p_test["type"], "class") + constructors = p_test["constructors"] + self.assertEqual(len(constructors), 1) + self._test_class_constructor(constructors[0], "one-constructor") + # 3) class with 2 ctors + p_test = parsed[14][1] + self.assertEqual(len(p_test), 5) + self.assertEqual(p_test["name"], "two-ctors") + self.assertEqual(p_test["description"], "This class contains two \ +constructors.") + self.assertEqual(p_test["type"], "class") + constructors = p_test["constructors"] + self.assertEqual(len(constructors), 2) + self._test_class_constructor(constructors[0], "one-constructor") + self._test_class_constructor(constructors[1], "another-constructor") + # 4) class with ctor + method + p_test = parsed[16][1] + self.assertEqual(len(p_test), 6) + self.assertEqual(p_test["name"], "ctor-and-method") + self.assertEqual(p_test["description"], "This class contains one \ +constructor and one method.") + self.assertEqual(p_test["type"], "class") + constructors = p_test["constructors"] + self.assertEqual(len(constructors), 1) + self._test_class_constructor(constructors[0], "one-constructor") + methods = p_test["methods"] + self.assertEqual(len(methods), 1) + self._test_class_method(methods[0]) + # 5) class with ctor + method + property + p_test = parsed[18][1] + self.assertEqual(len(p_test), 8) + self.assertEqual(p_test["name"], "ctor-method-prop-event") + self.assertEqual(p_test["description"], "This class contains one \ +constructor, one method, one property and an event.") + self.assertEqual(p_test["type"], "class") + constructors = p_test["constructors"] + self.assertEqual(len(constructors), 1) + self._test_class_constructor(constructors[0], "one-constructor") + methods = p_test["methods"] + self.assertEqual(len(methods), 1) + self._test_class_method(methods[0]) + properties = p_test["properties"] + self.assertEqual(len(properties), 1) + self._test_class_property(properties[0]) + events = p_test["events"] + self.assertEqual(len(events), 1) + self._test_class_event(events[0]) + + self.assertEqual(parsed[-1][0], "markdown") + self.assertEqual(parsed[-1][1], "\n\nSome more text here, \ +at the end of the file.\n\n") + + def _test_class_constructor(self, constructor, name): + self.assertEqual(constructor["type"], "constructor") + self.assertEqual(constructor["name"], name) + params = constructor["params"] + self.assertEqual(len(params), 1) + self.assertEqual(params[0]["name"], "options") + self.assertEqual(params[0]["description"], "An object-bag of goodies.") + + def _test_class_method(self, method): + self.assertEqual(method["type"], "method") + self.assertEqual(method["name"], "a-method") + self.assertEqual(method["description"], "Does things.") + params = method["params"] + self.assertEqual(len(params), 1) + self.assertEqual(params[0]["name"], "options") + self.assertEqual(params[0]["description"], "An argument.") + + def _test_class_property(self, prop): + self.assertEqual(prop["type"], "property") + self.assertEqual(prop["name"], "a-property") + self.assertEqual(prop["description"], "Represents stuff.") + self.assertEqual(prop["datatype"], "bool") + + def _test_class_event(self, event): + self.assertEqual(event["type"], "event") + self.assertEqual(event["name"], "message") + self.assertEqual(event["description"], "Event emitted when the \ +content script sends a message to the add-on.") + arguments = event["arguments"] + self.assertEqual(len(arguments), 1) + argument = arguments[0] + self.assertEqual(argument["datatype"], "JSON") + self.assertEqual(argument["description"], "The message itself as a \ +JSON-serialized object.") + + def test_missing_return_propname(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns {object} + @prop {string} First string, but the property name is missing + @prop {url} First URL, same problem +@param argOne {string} This is the first argument. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_missing_return_proptype(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns {object} + @prop untyped It is an error to omit the type of a return property. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_return_propnames(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns {object} + @prop firststring {string} First string. + @prop [firsturl] {url} First URL, not always provided. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + parsed = self.parse_text(md) + r = parsed[1][1]["returns"] + self.assertEqual(r["props"][0]["name"], "firststring") + self.assertEqual(r["props"][0], + {"name": "firststring", + "datatype": "string", + "description": "First string.", + "required": True, + "line_number": 5, # 1-indexed + }) + self.assertEqual(r["props"][1], + {"name": "firsturl", + "datatype": "url", + "description": "First URL, not always provided.", + "required": False, + "line_number": 6, + }) + + def test_return_description_1(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns {object} A one-line description. + @prop firststring {string} First string. + @prop [firsturl] {url} First URL, not always provided. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + parsed = self.parse_text(md) + r = parsed[1][1]["returns"] + self.assertEqual(r["description"], "A one-line description.") + + def test_return_description_2(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns {object} A six-line description + which is consistently indented by two spaces + except for this line + and preserves the following empty line + + from which a two-space indentation will be removed. + @prop firststring {string} First string. + @prop [firsturl] {url} First URL, not always provided. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + parsed = self.parse_text(md) + r = parsed[1][1]["returns"] + self.assertEqual(r["description"], + "A six-line description\n" + "which is consistently indented by two spaces\n" + " except for this line\n" + "and preserves the following empty line\n" + "\n" + "from which a two-space indentation will be removed.") + + def test_return_description_3(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns A one-line untyped description. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + parsed = self.parse_text(md) + r = parsed[1][1]["returns"] + self.assertEqual(r["description"], "A one-line untyped description.") + + # if the return value was supposed to be an array, the correct syntax + # would not have any @prop tags: + # @returns {array} + # Array consists of two elements, a string and a url... + + def test_return_array(self): + md = '''\ +<api name="test"> +@method +This is a function which returns an array. +@returns {array} + Array consists of two elements, a string and a url. +@param argOne {string} This is the first argument. +@param [argTwo=True] {bool} This is the second argument. +</api> +''' + parsed = self.parse_text(md) + r = parsed[1][1]["returns"] + self.assertEqual(r["description"], + "Array consists of two elements, a string and a url.") + + def test_bad_default_on_required_parameter(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@returns something +@param argOne=ILLEGAL {string} Mandatory parameters do not take defaults. +@param [argTwo=Chicago] {string} This is the second argument. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_missing_apitype(self): + md = '''\ +<api name="test"> +Sorry, you must have a @method or something before the description. +Putting it after the description is not good enough +@method +@returns something +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_missing_param_propname(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@param p1 {object} This is a parameter. + @prop {string} Oops, props must have a name. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_missing_param_proptype(self): + md = '''\ +<api name="test"> +@method +This is a function which does nothing in particular. +@param p1 {object} This is a parameter. + @prop name Oops, props must have a type. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_property(self): + md = '''\ +<api name="test"> +@property {foo} +An object property named test of type foo. +</api> +''' + parsed = self.parse_text(md) + self.assertEqual(parsed[1][0], 'api-json') + actual_api_json_obj = parsed[1][1] + expected_api_json_obj = { + 'line_number': 1, + 'datatype': 'foo', + 'type': 'property', + 'name': 'test', + 'description': "An object property named test of type foo." + } + self.assertEqual(actual_api_json_obj, expected_api_json_obj) + + def test_property_no_type(self): + md = '''\ +<api name="test"> +@property +This property needs to specify a type! +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + + def test_missing_api_closing_tag(self): + md = '''\ +<api name="test"> +@class +This is a class with a missing closing tag. +<api name="doStuff" +@method +This method does stuff. +</api> +''' + self.assertRaises(ParseError, self.parse_text, md) + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apirenderer.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apirenderer.py new file mode 100644 index 0000000..24a1c7c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_apirenderer.py @@ -0,0 +1,31 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest +from cuddlefish.docs.apirenderer import md_to_html + +tests_path = os.path.abspath(os.path.dirname(__file__)) +static_files_path = os.path.join(tests_path, "static-files") + +class ParserTests(unittest.TestCase): + def pathname(self, filename): + return os.path.join(static_files_path, "docs", filename) + + def render_markdown(self, pathname): + return md_to_html(pathname) + + def test_renderer(self): + test = self.render_markdown(self.pathname("APIsample.md")) + reference = open(self.pathname("APIreference.html")).read() + test_lines = test.splitlines(True) + reference_lines = reference.splitlines(True) + for x in range(len(test_lines)): + self.assertEqual(test_lines[x], reference_lines[x], + "line %d: expected '%s', got '%s'" + % (x+1, reference_lines[x], test_lines[x])) + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_generate.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_generate.py new file mode 100644 index 0000000..ce5665e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_generate.py @@ -0,0 +1,173 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import shutil +import unittest +import StringIO +import tarfile +import HTMLParser +import urlparse +import urllib + +from cuddlefish.docs import generate +from cuddlefish.tests import env_root + +INITIAL_FILESET = [ ["static-files", "base.html"], \ + ["dev-guide", "index.html"], \ + ["packages", "aardvark", "index.html"] ] + +EXTENDED_FILESET = [ ["static-files", "base.html"], \ + ["dev-guide", "extra.html"], \ + ["dev-guide", "index.html"], \ + ["packages", "aardvark", "index.html"] ] + +EXTRAFILE = ["dev-guide", "extra.html"] + +def get_test_root(): + return os.path.join(env_root, "python-lib", "cuddlefish", "tests", "static-files") + +def get_sdk_docs_root(): + return os.path.join(get_test_root(), "sdk-docs") + +def get_base_url_path(): + return os.path.join(get_sdk_docs_root(), "doc") + +def url_from_path(path): + path = path.lstrip("/") + return "file://"+"/"+"/".join(path.split(os.sep))+"/" + +def get_base_url(): + return url_from_path(get_base_url_path()) + +class Link_Checker(HTMLParser.HTMLParser): + def __init__(self, tester, filename, base_url): + HTMLParser.HTMLParser.__init__(self) + self.tester = tester + self.filename = filename + self.base_url = base_url + self.errors = [] + + def handle_starttag(self, tag, attrs): + link = self.find_link(attrs) + if link: + self.validate_link(link) + + def handle_startendtag(self, tag, attrs): + link = self.find_link(attrs) + if link: + self.validate_link(link) + + def find_link(self, attrs): + attrs = dict(attrs) + href = attrs.get('href', '') + if href: + parsed = urlparse.urlparse(href) + if not parsed.scheme: + return href + src = attrs.get('src', '') + if src: + parsed = urlparse.urlparse(src) + if not parsed.scheme: + return src + + def validate_link(self, link): + parsed = urlparse.urlparse(link) + # there should not be any file:// URLs + self.tester.assertNotEqual(parsed.scheme, "file") + # any other absolute URLs will not be checked + if parsed.scheme: + return + current_path_as_url = url_from_path(os.path.dirname(self.filename)) + # otherwise try to open the file at: baseurl + path + absolute_url = current_path_as_url + parsed.path + try: + urllib.urlopen(absolute_url) + except IOError: + self.errors.append(self.filename + "\n " + absolute_url) + +class Generate_Docs_Tests(unittest.TestCase): + + def test_generate_static_docs(self): + # make sure we start clean + if os.path.exists(get_base_url_path()): + shutil.rmtree(get_base_url_path()) + # generate a doc tarball, and extract it + base_url = get_base_url() + tar_filename = generate.generate_static_docs(env_root) + tgz = tarfile.open(tar_filename) + tgz.extractall(get_sdk_docs_root()) + broken_links = [] + # get each HTML file... + for root, subFolders, filenames in os.walk(get_sdk_docs_root()): + for filename in filenames: + if not filename.endswith(".html"): + continue + if root.endswith("static-files"): + continue + filename = os.path.join(root, filename) + # ...and feed it to the link checker + linkChecker = Link_Checker(self, filename, base_url) + linkChecker.feed(open(filename, "r").read()) + broken_links.extend(linkChecker.errors) + if broken_links: + print + print "The following links are broken:" + for broken_link in sorted(broken_links): + print " "+ broken_link + self.fail("%d links are broken" % len(broken_links)) + # clean up + shutil.rmtree(get_base_url_path()) + tgz.close() + os.remove(tar_filename) + generate.clean_generated_docs(os.path.join(env_root, "doc")) + + def test_generate_docs(self): + test_root = get_test_root() + docs_root = os.path.join(test_root, "doc") + generate.clean_generated_docs(docs_root) + new_digest = self.check_generate_regenerate_cycle(test_root, INITIAL_FILESET) + # touching an MD file under packages **does** cause a regenerate + os.utime(os.path.join(test_root, "packages", "aardvark", "doc", "main.md"), None) + new_digest = self.check_generate_regenerate_cycle(test_root, INITIAL_FILESET, new_digest) + # touching a non MD file under packages **does not** cause a regenerate + os.utime(os.path.join(test_root, "packages", "aardvark", "lib", "main.js"), None) + self.check_generate_is_skipped(test_root, INITIAL_FILESET, new_digest) + # touching a non MD file under static-files **does not** cause a regenerate + os.utime(os.path.join(docs_root, "static-files", "another.html"), None) + new_digest = self.check_generate_is_skipped(test_root, INITIAL_FILESET, new_digest) + # touching an MD file under dev-guide **does** cause a regenerate + os.utime(os.path.join(docs_root, "dev-guide-source", "index.md"), None) + new_digest = self.check_generate_regenerate_cycle(test_root, INITIAL_FILESET, new_digest) + # adding a file **does** cause a regenerate + open(os.path.join(docs_root, "dev-guide-source", "extra.md"), "w").write("some content") + new_digest = self.check_generate_regenerate_cycle(test_root, EXTENDED_FILESET, new_digest) + # deleting a file **does** cause a regenerate + os.remove(os.path.join(docs_root, "dev-guide-source", "extra.md")) + new_digest = self.check_generate_regenerate_cycle(test_root, INITIAL_FILESET, new_digest) + # remove the files + generate.clean_generated_docs(docs_root) + + def check_generate_is_skipped(self, test_root, files_to_expect, initial_digest): + generate.generate_docs(test_root, stdout=StringIO.StringIO()) + docs_root = os.path.join(test_root, "doc") + for file_to_expect in files_to_expect: + self.assertTrue(os.path.exists(os.path.join(docs_root, *file_to_expect))) + self.assertTrue(initial_digest == open(os.path.join(docs_root, "status.md5"), "r").read()) + + def check_generate_regenerate_cycle(self, test_root, files_to_expect, initial_digest = None): + # test that if we generate, files are getting generated + generate.generate_docs(test_root, stdout=StringIO.StringIO()) + docs_root = os.path.join(test_root, "doc") + for file_to_expect in files_to_expect: + self.assertTrue(os.path.exists(os.path.join(docs_root, *file_to_expect)), os.path.join(docs_root, *file_to_expect) + "not found") + if initial_digest: + self.assertTrue(initial_digest != open(os.path.join(docs_root, "status.md5"), "r").read()) + # and that if we regenerate, nothing changes... + new_digest = open(os.path.join(docs_root, "status.md5"), "r").read() + self.check_generate_is_skipped(test_root, files_to_expect, new_digest) + return new_digest + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_init.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_init.py new file mode 100644 index 0000000..33c0059 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_init.py @@ -0,0 +1,148 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os, unittest, shutil +from StringIO import StringIO +from cuddlefish import initializer +from cuddlefish.templates import MAIN_JS, TEST_MAIN_JS, PACKAGE_JSON + +tests_path = os.path.abspath(os.path.dirname(__file__)) + +class TestInit(unittest.TestCase): + + def run_init_in_subdir(self, dirname, f, *args, **kwargs): + top = os.path.abspath(os.getcwd()) + basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) + if os.path.isdir(basedir): + assert basedir.startswith(top) + shutil.rmtree(basedir) + os.makedirs(basedir) + try: + os.chdir(basedir) + return f(basedir, *args, **kwargs) + finally: + os.chdir(top) + + def do_test_init(self,basedir): + # Let's init the addon, no error admited + f = open(".ignoreme","w") + f.write("stuff") + f.close() + + out, err = StringIO(), StringIO() + init_run = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(init_run, 0) + self.assertTrue("* lib directory created" in out) + self.assertTrue("* data directory created" in out) + self.assertTrue("Have fun!" in out) + self.assertEqual(err,"") + self.assertTrue(len(os.listdir(basedir))>0) + main_js = os.path.join(basedir,"lib","main.js") + package_json = os.path.join(basedir,"package.json") + test_main_js = os.path.join(basedir,"test","test-main.js") + self.assertTrue(os.path.exists(main_js)) + self.assertTrue(os.path.exists(package_json)) + self.assertTrue(os.path.exists(test_main_js)) + self.assertEqual(open(main_js,"r").read(),MAIN_JS) + self.assertEqual(open(package_json,"r").read(), + PACKAGE_JSON % {"name":"tmp_addon_sample", + "fullName": "tmp_addon_SAMPLE" }) + self.assertEqual(open(test_main_js,"r").read(),TEST_MAIN_JS) + + # Let's check that the addon is initialized + out, err = StringIO(), StringIO() + init_run = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.failIfEqual(init_run,0) + self.assertTrue("This command must be run in an empty directory." in err) + + def test_initializer(self): + self.run_init_in_subdir("tmp_addon_SAMPLE",self.do_test_init) + + def do_test_args(self, basedir): + # check that running it with spurious arguments will fail + out,err = StringIO(), StringIO() + init_run = initializer(None, ["init", "ignored-dirname"], out, err) + out, err = out.getvalue(), err.getvalue() + self.failIfEqual(init_run, 0) + self.assertTrue("Too many arguments" in err) + + def test_args(self): + self.run_init_in_subdir("tmp_addon_sample", self.do_test_args) + + def _test_existing_files(self, basedir): + f = open("pay_attention_to_me","w") + f.write("stuff") + f.close() + out,err = StringIO(), StringIO() + rc = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc, 1) + self.failUnless("This command must be run in an empty directory" in err, + err) + self.failIf(os.path.exists("lib")) + + def test_existing_files(self): + self.run_init_in_subdir("existing_files", self._test_existing_files) + + + +class TestCfxQuits(unittest.TestCase): + + def run_cfx(self, addon_name, command): + old_cwd = os.getcwd() + addon_path = os.path.join(tests_path, + "addons", addon_name) + os.chdir(addon_path) + import sys + old_stdout = sys.stdout + old_stderr = sys.stderr + sys.stdout = out = StringIO() + sys.stderr = err = StringIO() + try: + import cuddlefish + args = list(command) + # Pass arguments given to cfx so that cfx can find firefox path + # if --binary option is given: + args.extend(sys.argv[1:]) + cuddlefish.run(arguments=args) + except SystemExit, e: + if "code" in e: + rc = e.code + elif "args" in e and len(e.args)>0: + rc = e.args[0] + else: + rc = 0 + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + os.chdir(old_cwd) + out.flush() + err.flush() + return rc, out.getvalue(), err.getvalue() + + # this method doesn't exists in python 2.5, + # implements our own + def assertIn(self, member, container): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '"%s" not found in "%s"' % (member, + container) + self.fail(standardMsg) + + def test_run(self): + rc, out, err = self.run_cfx("simplest-test", ["run"]) + self.assertEqual(rc, 0) + self.assertIn("Program terminated successfully.", err) + + def test_test(self): + rc, out, err = self.run_cfx("simplest-test", ["test"]) + self.assertEqual(rc, 0) + self.assertIn("1 of 1 tests passed.", err) + self.assertIn("Program terminated successfully.", err) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_licenses.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_licenses.py new file mode 100644 index 0000000..60b5957 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_licenses.py @@ -0,0 +1,88 @@ + +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 unittest +import os.path + +parent = os.path.dirname +test_dir = parent(os.path.abspath(__file__)) +sdk_root = parent(parent(parent(test_dir))) + +def from_sdk_top(fn): + return os.path.abspath(os.path.join(sdk_root, fn)) + +MPL2_URL = "http://mozilla.org/MPL/2.0/" + +# These files all come with their own license headers +skip = [ + "python-lib/cuddlefish/_version.py", # generated, public domain + "doc/static-files/js/jquery.js", # MIT/GPL dual + "examples/annotator/data/jquery-1.4.2.min.js", # MIT/GPL dual + "examples/reddit-panel/data/jquery-1.4.4.min.js", # MIT/GPL dual + "examples/library-detector/data/library-detector.js", # MIT + "python-lib/mozrunner/killableprocess.py", # MIT? BSDish? + "python-lib/mozrunner/winprocess.py", # MIT + "packages/api-utils/tests/test-querystring.js", # MIT + "packages/api-utils/lib/promise.js", # MIT + "packages/api-utils/tests/test-promise.js", # MIT + ] +absskip = [from_sdk_top(os.path.join(*fn.split("/"))) for fn in skip] + +class Licenses(unittest.TestCase): + def test(self): + # Examine most SDK files to check if they've got an MPL2 license + # header. We exclude some files that are known to include different + # licenses. + self.missing = [] + self.scan_file(from_sdk_top(os.path.join("python-lib", "jetpack_sdk_env.py"))) + self.scan(os.path.join("python-lib", "cuddlefish"), [".js", ".py"], + skipdirs=["sdk-docs"], # test_generate.py makes this + ) + self.scan(os.path.join("python-lib", "mozrunner"), [".py"]) + + for sdk_package in ["addon-kit", "api-utils", "test-harness"]: + self.scan(os.path.join("packages", sdk_package), + [".js", ".py", ".md"]) + self.scan("examples", [".js", ".css", ".html", ".md"]) + self.scan("bin", [".bat", ".ps1"]) + for fn in [os.path.join("bin", "activate"), + os.path.join("bin", "cfx"), + os.path.join("bin", "integration-scripts", "buildbot-run-cfx-helper"), + os.path.join("bin", "integration-scripts", "integration-check"), + ]: + self.scan_file(from_sdk_top(fn)) + self.scan("doc", [".js", ".css", ".md"], skipdirs=["syntaxhighlighter"]) + + if self.missing: + print + print "The following files are missing an MPL2 header:" + for fn in sorted(self.missing): + print " "+fn + self.fail("%d files are missing an MPL2 header" % len(self.missing)) + + def scan(self, start, extensions=[], skipdirs=[]): + # scan a whole subdirectory + start = from_sdk_top(start) + for root, dirs, files in os.walk(start): + for d in skipdirs: + if d in dirs: + dirs.remove(d) + for fn in files: + ext = os.path.splitext(fn)[1] + if extensions and ext not in extensions: + continue + absfn = os.path.join(root, fn) + if absfn in absskip: + continue + self.scan_file(absfn) + + def scan_file(self, fn): + # scan a single file + if not MPL2_URL in open(fn, "r").read(): + relfile = fn[len(sdk_root)+1:] + self.missing.append(relfile) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_linker.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_linker.py new file mode 100755 index 0000000..84f6a09 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_linker.py @@ -0,0 +1,234 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os.path +import shutil +import zipfile +from StringIO import StringIO +import simplejson as json +import unittest +import cuddlefish +from cuddlefish import packaging, manifest + +def up(path, generations=1): + for i in range(generations): + path = os.path.dirname(path) + return path + +ROOT = up(os.path.abspath(__file__), 4) +def get_linker_files_dir(name): + return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) + +class Basic(unittest.TestCase): + def get_pkg(self, name): + d = get_linker_files_dir(name) + return packaging.get_config_in_dir(d) + + def test_deps(self): + target_cfg = self.get_pkg("one") + pkg_cfg = packaging.build_config(ROOT, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, ["one"]) + self.failUnlessEqual(deps, ["one"]) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "one"]) + + def test_manifest(self): + target_cfg = self.get_pkg("one") + pkg_cfg = packaging.build_config(ROOT, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "one"]) + # target_cfg.dependencies is not provided, so we'll search through + # all known packages (everything in 'deps'). + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest() + + def assertReqIs(modname, reqname, path): + reqs = m["one/lib/%s.js" % modname]["requirements"] + self.failUnlessEqual(reqs[reqname]["path"], path) + assertReqIs("main", "panel", "addon-kit/lib/panel.js") + assertReqIs("main", "two.js", "one/lib/two.js") + assertReqIs("main", "./two", "one/lib/two.js") + assertReqIs("main", "addon-kit/tabs.js", "addon-kit/lib/tabs.js") + assertReqIs("main", "./subdir/three", "one/lib/subdir/three.js") + assertReqIs("two", "main", "one/lib/main.js") + assertReqIs("subdir/three", "../main", "one/lib/main.js") + + target_cfg.dependencies = [] + # now, because .dependencies *is* provided, we won't search 'deps', + # so we'll get a link error + self.assertRaises(manifest.ModuleNotFoundError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False) + + def test_main_in_deps(self): + target_cfg = self.get_pkg("three") + package_path = [get_linker_files_dir("three-deps")] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "three"]) + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest() + def assertReqIs(modname, reqname, path): + reqs = m["three/lib/%s.js" % modname]["requirements"] + self.failUnlessEqual(reqs[reqname]["path"], path) + assertReqIs("main", "three-a", "three-a/lib/main.js") + assertReqIs("main", "three-b", "three-b/lib/main.js") + assertReqIs("main", "three-c", "three-c/lib/main.js") + + def test_relative_main_in_top(self): + target_cfg = self.get_pkg("five") + package_path = [] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "five"]) + # all we care about is that this next call doesn't raise an exception + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest() + reqs = m["five/lib/main.js"]["requirements"] + self.failUnlessEqual(reqs, {}); + + def test_unreachable_relative_main_in_top(self): + target_cfg = self.get_pkg("six") + package_path = [] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "six"]) + self.assertRaises(manifest.UnreachablePrefixError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False) + + def test_unreachable_in_deps(self): + target_cfg = self.get_pkg("four") + package_path = [get_linker_files_dir("four-deps")] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + self.failUnlessEqual(deps, ["addon-kit", "api-utils", "four"]) + self.assertRaises(manifest.UnreachablePrefixError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False) + +class Contents(unittest.TestCase): + + def run_in_subdir(self, dirname, f, *args, **kwargs): + top = os.path.abspath(os.getcwd()) + basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) + if os.path.isdir(basedir): + assert basedir.startswith(top) + shutil.rmtree(basedir) + os.makedirs(basedir) + try: + os.chdir(basedir) + return f(basedir, *args, **kwargs) + finally: + os.chdir(top) + + def assertIn(self, what, inside_what): + self.failUnless(what in inside_what, inside_what) + + def test_jetpackID(self): + # this uses "id": "jid7", to which a @jetpack should be appended + seven = get_linker_files_dir("seven") + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + hopts = json.loads(zf.read("harness-options.json")) + self.failUnlessEqual(hopts["jetpackID"], "jid7@jetpack") + self.run_in_subdir("x", _test) + + def test_jetpackID_suffix(self): + # this uses "id": "jid1@jetpack", so no suffix should be appended + one = get_linker_files_dir("one") + def _test(basedir): + stdout = StringIO() + shutil.copytree(one, "one") + os.chdir("one") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("one.xpi", "r") + hopts = json.loads(zf.read("harness-options.json")) + self.failUnlessEqual(hopts["jetpackID"], "jid1@jetpack") + self.run_in_subdir("x", _test) + + def test_strip_default(self): + seven = get_linker_files_dir("seven") + # now run 'cfx xpi' in that directory, except put the generated .xpi + # elsewhere + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi"], # --strip-xpi is now the default + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + names = zf.namelist() + # the first problem found in bug 664840 was that cuddlefish.js + # (the loader) was stripped out on windows, due to a /-vs-\ bug + self.assertIn("resources/api-utils/lib/cuddlefish.js", names) + # the second problem found in bug 664840 was that an addon + # without an explicit tests/ directory would copy all files from + # the package into a bogus JID-PKGNAME-tests/ directory, so check + # for that + testfiles = [fn for fn in names if "seven/tests" in fn] + self.failUnlessEqual([], testfiles) + # the third problem was that data files were being stripped from + # the XPI. Note that data/ is only supposed to be included if a + # module that actually gets used does a require("self") . + self.assertIn("resources/seven/data/text.data", + names) + self.failIf("seven/lib/unused.js" + in names, names) + self.run_in_subdir("x", _test) + + def test_no_strip(self): + seven = get_linker_files_dir("seven") + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + names = zf.namelist() + self.assertIn("resources/api-utils/lib/cuddlefish.js", names) + testfiles = [fn for fn in names if "seven/tests" in fn] + self.failUnlessEqual([], testfiles) + self.assertIn("resources/seven/data/text.data", + names) + self.failUnless("resources/seven/lib/unused.js" + in names, names) + self.run_in_subdir("x", _test) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_manifest.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_manifest.py new file mode 100644 index 0000000..77ee86f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_manifest.py @@ -0,0 +1,254 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 unittest +from StringIO import StringIO +from cuddlefish.manifest import scan_module + +class Extra: + def failUnlessKeysAre(self, d, keys): + self.failUnlessEqual(sorted(d.keys()), sorted(keys)) + +class Require(unittest.TestCase, Extra): + def scan(self, text): + lines = StringIO(text).readlines() + requires, problems, locations = scan_module("fake.js", lines) + self.failUnlessEqual(problems, False) + return requires + + def scan_locations(self, text): + lines = StringIO(text).readlines() + requires, problems, locations = scan_module("fake.js", lines) + self.failUnlessEqual(problems, False) + return requires, locations + + def test_modules(self): + mod = """var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo = require(\"one\");""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo=require( 'one' ) ; """ + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo = require('o'+'ne'); // tricky, denied""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """require('one').immediately.do().stuff();""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["one"]) + self.failUnlessEqual(locations, {"one": 1}) + + # these forms are commented out, and thus ignored + + mod = """// var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """/* var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """ * var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """ ' var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """ \" var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + # multiple requires + + mod = """const foo = require('one'); + const foo = require('two');""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + self.failUnlessEqual(locations["one"], 1) + self.failUnlessEqual(locations["two"], 2) + + mod = """const foo = require('repeated'); + const bar = require('repeated'); + const baz = require('repeated');""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["repeated"]) + self.failUnlessEqual(locations["repeated"], 1) # first occurrence + + mod = """const foo = require('one'); const foo = require('two');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + + # define calls + + mod = """define('one', ['two', 'numbers/three'], function(t, th) {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["two", "numbers/three"]) + + mod = """define( + ['odd', + "numbers/four"], function() {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["odd", "numbers/four"]) + + mod = """define(function(require, exports, module) { + var a = require("some/module/a"), + b = require('b/v1'); + exports.a = a; + //This is a fakeout: require('bad'); + /* And another var bad = require('bad2'); */ + require('foo').goFoo(); + });""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["some/module/a", "b/v1", "foo"]) + + mod = """define ( + "foo", + ["bar"], function (bar) { + var me = require("me"); + } + )""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["bar", "me"]) + + mod = """define(['se' + 'ven', 'eight', nine], function () {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["eight"]) + + # async require calls + + mod = """require(['one'], function(one) {var o = require("one");});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """require([ 'one' ], function(one) {var t = require("two");});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + + mod = """require ( ['two', 'numbers/three'], function(t, th) {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["two", "numbers/three"]) + + mod = """require ( + ["bar", "fa" + 'ke' ], function (bar) { + var me = require("me"); + // require("bad").doBad(); + } + )""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["bar", "me"]) + +def scan2(text, fn="fake.js"): + stderr = StringIO() + lines = StringIO(text).readlines() + requires, problems, locations = scan_module(fn, lines, stderr) + stderr.seek(0) + return requires, problems, stderr.readlines() + +class Chrome(unittest.TestCase, Extra): + + def test_ignore_loader(self): + # we specifically ignore the loader itself + mod = """let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod, "blah/cuddlefish.js") + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + def test_chrome(self): + mod = """let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """var foo = require('foo'); + let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["foo", "chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """let c = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """var foo = require('foo'); + let c = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["foo", "chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + def test_chrome_components(self): + # Bug 663541: tolerate "Components" if you're marked with + # require("chrome"), to avoid requiring module authors to rewrite a + # lot of code. Once bug 636145 is fixed, such code will break. To fix + # it, add {Components}=require("chrome"), but that won't work until + # after 636145 is fixed. + mod = """require("chrome"); + var ios = Components.classes['@mozilla.org/network/io-service;1'];""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual((problems, err), (False, [])) + + def test_not_chrome(self): + # from bug 596595 + mod = r'soughtLines: new RegExp("^\\s*(\\[[0-9 .]*\\])?\\s*\\(\\((EE|WW)\\)|.* [Cc]hipsets?: \\)|\\s*Backtrace")' + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual((problems,err), (False, [])) + + def test_not_chrome2(self): + # from bug 655788 + mod = r"var foo = 'some stuff Cr';" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual((problems,err), (False, [])) + +class BadChrome(unittest.TestCase, Extra): + def test_bad_alias(self): + # using Components.* gets you an error, with a message that teaches + # you the correct approach. + mod = """let Cc = Components.classes; + let Cu = Components.utils; + """ + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual(problems, True) + self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") + self.failUnlessEqual(err[2], " 1: let Cc = Components.classes;\n") + self.failUnlessEqual(err[3], " 2: let Cu = Components.utils;\n") + self.failUnlessEqual(err[4], "use 'Components' to access chrome authority. To do so, you need to add a\n") + self.failUnlessEqual(err[5], "line somewhat like the following:\n") + self.failUnlessEqual(err[7], ' const {Cc,Cu} = require("chrome");\n') + self.failUnlessEqual(err[9], "Then you can use 'Components' as well as any shortcuts to its properties\n") + + def test_bad_misc(self): + # If it looks like you're using something that doesn't have an alias, + # the warning also suggests a better way. + mod = """if (Components.isSuccessCode(foo)) + """ + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual(problems, True) + self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") + self.failUnlessEqual(err[2], " 1: if (Components.isSuccessCode(foo))\n") + self.failUnlessEqual(err[3], "use 'Components' to access chrome authority. To do so, you need to add a\n") + self.failUnlessEqual(err[4], "line somewhat like the following:\n") + self.failUnlessEqual(err[6], ' const {components} = require("chrome");\n') + self.failUnlessEqual(err[8], "Then you can use 'Components' as well as any shortcuts to its properties\n") + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_packaging.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_packaging.py new file mode 100644 index 0000000..ff3d851 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_packaging.py @@ -0,0 +1,116 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest + +from cuddlefish import packaging +from cuddlefish.bunch import Bunch + +tests_path = os.path.abspath(os.path.dirname(__file__)) +static_files_path = os.path.join(tests_path, 'static-files') + +def get_configs(pkg_name, dirname='static-files'): + root_path = os.path.join(tests_path, dirname) + pkg_path = os.path.join(root_path, 'packages', pkg_name) + if not (os.path.exists(pkg_path) and os.path.isdir(pkg_path)): + raise Exception('path does not exist: %s' % pkg_path) + target_cfg = packaging.get_config_in_dir(pkg_path) + pkg_cfg = packaging.build_config(root_path, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, [pkg_name]) + build = packaging.generate_build_for_target( + pkg_cfg=pkg_cfg, + target=pkg_name, + deps=deps + ) + return Bunch(target_cfg=target_cfg, pkg_cfg=pkg_cfg, build=build) + +class PackagingTests(unittest.TestCase): + def test_bug_588661(self): + configs = get_configs('foo', 'bug-588661-files') + self.assertEqual(configs.build.loader, + 'foo/lib/foo-loader.js') + + def test_bug_614712(self): + configs = get_configs('commonjs-naming', 'bug-614712-files') + packages = configs.pkg_cfg.packages + base = os.path.join(tests_path, 'bug-614712-files', 'packages') + self.assertEqual(packages['original-naming'].tests, + [os.path.join(base, 'original-naming', 'tests')]) + self.assertEqual(packages['commonjs-naming'].tests, + [os.path.join(base, 'commonjs-naming', 'test')]) + + def test_basic(self): + configs = get_configs('aardvark') + packages = configs.pkg_cfg.packages + + self.assertTrue('api-utils' in packages) + self.assertTrue('aardvark' in packages) + self.assertTrue('api-utils' in packages.aardvark.dependencies) + self.assertEqual(packages['api-utils'].loader, 'lib/loader.js') + self.assertTrue(packages.aardvark.main == 'main') + self.assertTrue(packages.aardvark.version == "1.0") + +class PackagePath(unittest.TestCase): + def test_packagepath(self): + root_path = os.path.join(tests_path, 'static-files') + pkg_path = os.path.join(root_path, 'packages', 'minimal') + target_cfg = packaging.get_config_in_dir(pkg_path) + pkg_cfg = packaging.build_config(root_path, target_cfg) + base_packages = set(pkg_cfg.packages.keys()) + ppath = [os.path.join(tests_path, 'bug-611495-files')] + pkg_cfg2 = packaging.build_config(root_path, target_cfg, packagepath=ppath) + all_packages = set(pkg_cfg2.packages.keys()) + self.assertEqual(sorted(["jspath-one"]), + sorted(all_packages - base_packages)) + +class Directories(unittest.TestCase): + # for bug 652227 + packages_path = os.path.join(tests_path, "bug-652227-files", "packages") + def get_config(self, pkg_name): + pkg_path = os.path.join(tests_path, "bug-652227-files", "packages", + pkg_name) + return packaging.get_config_in_dir(pkg_path) + + def test_explicit_lib(self): + # package.json provides .lib + p = self.get_config('explicit-lib') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "explicit-lib", + "alt2-lib"))) + + def test_directories_lib(self): + # package.json provides .directories.lib + p = self.get_config('explicit-dir-lib') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "explicit-dir-lib", + "alt-lib"))) + + def test_lib(self): + # package.json is empty, but lib/ exists + p = self.get_config("default-lib") + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "default-lib", + "lib"))) + + def test_root(self): + # package.json is empty, no lib/, so files are in root + p = self.get_config('default-root') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "default-root"))) + + def test_locale(self): + # package.json is empty, but locale/ exists and should be used + p = self.get_config("default-locale") + self.assertEqual(os.path.abspath(p.locale), + os.path.abspath(os.path.join(self.packages_path, + "default-locale", + "locale"))) + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_preflight.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_preflight.py new file mode 100644 index 0000000..571b791 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_preflight.py @@ -0,0 +1,147 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os, shutil +import simplejson as json +import unittest +import hashlib +import base64 +from cuddlefish import preflight +from StringIO import StringIO + +class Util(unittest.TestCase): + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + def test_base62(self): + for i in range(1000): + h = hashlib.sha1(str(i)).digest() + s1 = base64.b64encode(h, "AB").strip("=") + s2 = base64.b64encode(h).strip("=").replace("+","A").replace("/","B") + self.failUnlessEqual(s1, s2) + + def write(self, config): + basedir = self.get_basedir() + fn = os.path.join(basedir, "package.json") + open(fn,"w").write(config) + def read(self): + basedir = self.get_basedir() + fn = os.path.join(basedir, "package.json") + return open(fn,"r").read() + + def get_cfg(self): + cfg = json.loads(self.read()) + if "name" not in cfg: + # the cfx parser always provides a name, even if package.json + # doesn't contain one + cfg["name"] = "pretend name" + return cfg + + def parse(self, keydata): + fields = {} + fieldnames = [] + for line in keydata.split("\n"): + if line.strip(): + k,v = line.split(":", 1) + k = k.strip() ; v = v.strip() + fields[k] = v + fieldnames.append(k) + return fields, fieldnames + + def test_preflight(self): + basedir = self.make_basedir() + fn = os.path.join(basedir, "package.json") + + # empty config is not ok: need id (name is automatically supplied) + config_orig = "{}" + self.write(config_orig) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, False) + self.failUnlessEqual(modified, True) + backup_fn = os.path.join(basedir, "package.json.backup") + config_backup = open(backup_fn,"r").read() + self.failUnlessEqual(config_backup, config_orig) + config = json.loads(self.read()) + self.failIf("name" in config) + self.failUnless("id" in config) + self.failUnless(config["id"].startswith("jid1-"), config["id"]) + self.failUnlessEqual(out.getvalue().strip(), + "No 'id' in package.json: creating a new ID for you.") + os.unlink(backup_fn) + + # just a name? we add the id + config_orig = '{"name": "my-awesome-package"}' + self.write(config_orig) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, False) + self.failUnlessEqual(modified, True) + backup_fn = os.path.join(basedir, "package.json.backup") + config_backup = open(backup_fn,"r").read() + self.failUnlessEqual(config_backup, config_orig) + config = json.loads(self.read()) + self.failUnlessEqual(config["name"], "my-awesome-package") + self.failUnless("id" in config) + self.failUnless(config["id"].startswith("jid1-"), config["id"]) + jid = str(config["id"]) + self.failUnlessEqual(out.getvalue().strip(), + "No 'id' in package.json: creating a new ID for you.") + os.unlink(backup_fn) + + # name and valid id? great! ship it! + config2 = '{"name": "my-awesome-package", "id": "%s"}' % jid + self.write(config2) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config2a = self.read() + self.failUnlessEqual(config2a, config2) + self.failUnlessEqual(out.getvalue().strip(), "") + + # name and anonymous ID? without asking to see its papers, ship it + config3 = '{"name": "my-old-skool-package", "id": "anonid0-deadbeef"}' + self.write(config3) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config3a = self.read() + self.failUnlessEqual(config3a, config3) + self.failUnlessEqual(out.getvalue().strip(), "") + + # name and old-style ID? with nostalgic trepidation, ship it + config4 = '{"name": "my-old-skool-package", "id": "foo@bar.baz"}' + self.write(config4) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config4a = self.read() + self.failUnlessEqual(config4a, config4) + self.failUnlessEqual(out.getvalue().strip(), "") + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_property_parser.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_property_parser.py new file mode 100644 index 0000000..f037f07 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_property_parser.py @@ -0,0 +1,75 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 unittest + +from cuddlefish.property_parser import parse, MalformedLocaleFileError + +class TestParser(unittest.TestCase): + + def test_parse(self): + lines = [ + # Comments are striped only if `#` is the first non-space character + "sharp=#can be in value", + "# comment", + "#key=value", + " # comment2", + + "keyWithNoValue=", + "valueWithSpaces= ", + "valueWithMultilineSpaces= \\", + " \\", + " ", + + # All spaces before/after are striped + " key = value ", + "key2=value2", + # Keys can contain '%' + "%s key=%s value", + + # Accept empty lines + "", + " ", + + # Multiline string must use backslash at end of lines + "multi=line\\", "value", + # With multiline string, left spaces are stripped ... + "some= spaces\\", " are\\ ", " stripped ", + # ... but not right spaces, except the last line! + "but=not \\", "all of \\", " them " + ] + # Ensure that lines end with a `\n` + lines = [l + "\n" for l in lines] + pairs = parse(lines) + expected = { + "sharp": "#can be in value", + + "key": "value", + "key2": "value2", + "%s key": "%s value", + + "keyWithNoValue": "", + "valueWithSpaces": "", + "valueWithMultilineSpaces": "", + + "multi": "linevalue", + "some": "spacesarestripped", + "but": "not all of them" + } + self.assertEqual(pairs, expected) + + def test_exceptions(self): + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["invalid line with no key value"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["plural[one]=plural with no generic value"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["multiline with no last empty line=\\"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["=no key"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + [" =only spaces in key"]) + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_rdf.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_rdf.py new file mode 100644 index 0000000..128289f --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_rdf.py @@ -0,0 +1,45 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 unittest +import xml.dom.minidom +import os.path + +from cuddlefish import rdf, packaging + +parent = os.path.dirname +test_dir = parent(os.path.abspath(__file__)) +template_dir = os.path.join(parent(test_dir), "app-extension") + +class RDFTests(unittest.TestCase): + def testBug567660(self): + obj = rdf.RDF() + data = u'\u2026'.encode('utf-8') + x = '<?xml version="1.0" encoding="utf-8"?><blah>%s</blah>' % data + obj.dom = xml.dom.minidom.parseString(x) + self.assertEqual(obj.dom.documentElement.firstChild.nodeValue, + u'\u2026') + self.assertEqual(str(obj).replace("\n",""), x.replace("\n","")) + + def failUnlessIn(self, substring, s, msg=""): + if substring not in s: + self.fail("(%s) substring '%s' not in string '%s'" + % (msg, substring, s)) + + def testUnpack(self): + basedir = os.path.join(test_dir, "bug-715340-files") + for n in ["pkg-1-pack", "pkg-2-unpack", "pkg-3-pack"]: + cfg = packaging.get_config_in_dir(os.path.join(basedir, n)) + m = rdf.gen_manifest(template_dir, cfg, jid="JID") + if n.endswith("-pack"): + # these ones should remain packed + self.failUnlessEqual(m.get("em:unpack"), "false") + self.failUnlessIn("<em:unpack>false</em:unpack>", str(m), n) + else: + # and these should be unpacked + self.failUnlessEqual(m.get("em:unpack"), "true") + self.failUnlessIn("<em:unpack>true</em:unpack>", str(m), n) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_runner.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_runner.py new file mode 100644 index 0000000..26583ab --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_runner.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + + +def xulrunner_app_runner_doctests(): + """ + >>> import sys + >>> from cuddlefish import runner + >>> runner.XulrunnerAppRunner(binary='foo') + Traceback (most recent call last): + ... + Exception: Binary path does not exist foo + + >>> runner.XulrunnerAppRunner(binary=sys.executable) + Traceback (most recent call last): + ... + ValueError: application.ini not found in cmdargs + + >>> runner.XulrunnerAppRunner(binary=sys.executable, + ... cmdargs=['application.ini']) + Traceback (most recent call last): + ... + ValueError: file does not exist: 'application.ini' + """ + + pass diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_util.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_util.py new file mode 100644 index 0000000..aa636a4 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_util.py @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 unittest +from cuddlefish.manifest import filter_filenames, filter_dirnames + +class Filter(unittest.TestCase): + def test_filter_filenames(self): + names = ["foo", "bar.js", "image.png", + ".hidden", "foo~", ".foo.swp", "bar.js.swp"] + self.failUnlessEqual(sorted(filter_filenames(names)), + sorted(["foo", "bar.js", "image.png"])) + + def test_filter_dirnames(self): + names = ["subdir", "data", ".git", ".hg", ".svn", "defaults"] + self.failUnlessEqual(sorted(filter_dirnames(names)), + sorted(["subdir", "data", "defaults"])) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_version.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_version.py new file mode 100644 index 0000000..814c57c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_version.py @@ -0,0 +1,28 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest +import shutil + +from cuddlefish._version import get_versions + +class Version(unittest.TestCase): + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + def test_current_version(self): + # the SDK should be able to determine its own version. We don't care + # what it is, merely that it can be computed. + version = get_versions()["version"] + self.failUnless(isinstance(version, str), (version, type(version))) + self.failUnless(len(version) > 0, version) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_webdocs.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_webdocs.py new file mode 100644 index 0000000..d9d4eab --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_webdocs.py @@ -0,0 +1,97 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest + +from cuddlefish.docs import webdocs + +class WebDocTests(unittest.TestCase): + def test_create_package_doc(self): + root = os.path.join(os.getcwd() + \ + '/python-lib/cuddlefish/tests/static-files') + web_docs = webdocs.WebDocs(root) + aarvark_package = web_docs.create_package_page('aardvark') + self._test_common_contents(aarvark_package) + self.assertTrue('<h1>aardvark</h1>'\ + in aarvark_package) + self.assertTrue(\ + '<span class="meta-header">Author</span>'\ + in aarvark_package) + self.assertTrue(\ + '<span class="author">Jon Smith</span>'\ + in aarvark_package) + self.assertTrue(\ + '<title>aardvark - Add-on SDK Documentation</title>'\ + in aarvark_package) + + def test_create_guide1_doc(self): + root = os.path.join(os.getcwd() + \ + '/python-lib/cuddlefish/tests/static-files') + web_docs = webdocs.WebDocs(root) + guide = web_docs.create_guide_page(os.path.join(\ + root + '/doc/dev-guide-source/index.blah')) + self._test_common_contents(guide) + self.assertTrue(\ + '<title>An Imposing Title - Add-on SDK Documentation</title>'\ + in guide) + self.assertTrue('<p><em>Some words!</em></p>'\ + in guide) + self.assertTrue('<div id="version">Version '\ + in guide) + + def test_create_guide2_doc(self): + root = os.path.join(os.getcwd() + \ + '/python-lib/cuddlefish/tests/static-files') + web_docs = webdocs.WebDocs(root) + guide = web_docs.create_guide_page(os.path.join(\ + root + '/doc/dev-guide-source/no_h1.blah')) + self._test_common_contents(guide) + self.assertTrue('<title>Add-on SDK Documentation</title>'\ + in guide) + self.assertTrue('<h2>A heading</h2>'\ + in guide) + + def test_create_module_doc(self): + root = os.path.join(os.getcwd() + \ + '/python-lib/cuddlefish/tests/static-files') + web_docs = webdocs.WebDocs(root) + module = web_docs.create_module_page(os.path.join(\ + root + '/packages/aardvark/doc/aardvark-feeder.blah')) + self._test_common_contents(module) + self.assertTrue(\ + '<title>aardvark-feeder - Add-on SDK Documentation</title>'\ + in module) + self.assertTrue(\ + '<h1>aardvark-feeder</h1>'\ + in module) + self.assertTrue(\ + '<div class="module_description">'\ + in module) + self.assertTrue(\ + '<p>The <code>aardvark-feeder</code> module simplifies feeding aardvarks.</p>'\ + in module) + self.assertTrue(\ + '<h2 class="api_header">API Reference</h2>'\ + in module) + self.assertTrue(\ + '<h3 class="api_header">Functions</h3>'\ + in module) + self.assertTrue(\ + '<h4 class="api_name">feed(food)</h4>'\ + in module) + self.assertTrue( + '<p>Feed the aardvark.</p>'\ + in module) + + def _test_common_contents(self, doc): + self.assertTrue(\ + '<a href="packages/aardvark/index.html"' in doc) + self.assertTrue(\ + '<a href="packages/anteater_files/index.html"' in doc) + self.assertTrue(\ + '<a href="packages/aardvark/main.html">main</a>' in doc) + +if __name__ == "__main__": + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_xpi.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_xpi.py new file mode 100644 index 0000000..7d638bb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/tests/test_xpi.py @@ -0,0 +1,412 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import unittest +import zipfile +import pprint +import shutil + +import simplejson as json +from cuddlefish import xpi, packaging, manifest, buildJID +from cuddlefish.tests import test_packaging +from test_linker import up + +xpi_template_path = os.path.join(test_packaging.static_files_path, + 'xpi-template') + +fake_manifest = '<RDF><!-- Extension metadata is here. --></RDF>' + +class PrefsTests(unittest.TestCase): + def makexpi(self, pkg_name): + self.xpiname = "%s.xpi" % pkg_name + create_xpi(self.xpiname, pkg_name, 'preferences-files') + self.xpi = zipfile.ZipFile(self.xpiname, 'r') + options = self.xpi.read('harness-options.json') + self.xpi_harness_options = json.loads(options) + + def setUp(self): + self.xpiname = None + self.xpi = None + + def tearDown(self): + if self.xpi: + self.xpi.close() + if self.xpiname and os.path.exists(self.xpiname): + os.remove(self.xpiname) + + def testPackageWithSimplePrefs(self): + self.makexpi('simple-prefs') + self.failUnless('options.xul' in self.xpi.namelist()) + optsxul = self.xpi.read('options.xul').decode("utf-8") + self.failUnless('pref="extensions.jid1-fZHqN9JfrDBa8A@jetpack.test"' + in optsxul, optsxul) + self.failUnless('type="bool"' in optsxul, optsxul) + self.failUnless(u'title="t\u00EBst"' in optsxul, repr(optsxul)) + self.failUnlessEqual(self.xpi_harness_options["jetpackID"], + "jid1-fZHqN9JfrDBa8A@jetpack") + prefsjs = self.xpi.read('defaults/preferences/prefs.js').decode("utf-8") + exp = [u'pref("extensions.jid1-fZHqN9JfrDBa8A@jetpack.test", false);', + u'pref("extensions.jid1-fZHqN9JfrDBa8A@jetpack.test2", "\u00FCnic\u00F8d\u00E9");', + ] + self.failUnlessEqual(prefsjs, "\n".join(exp)+"\n") + + def testPackageWithNoPrefs(self): + self.makexpi('no-prefs') + self.failIf('options.xul' in self.xpi.namelist()) + self.failUnlessEqual(self.xpi_harness_options["jetpackID"], + "jid1-fZHqN9JfrDBa8A@jetpack") + prefsjs = self.xpi.read('defaults/preferences/prefs.js').decode("utf-8") + self.failUnlessEqual(prefsjs, "") + + +class Bug588119Tests(unittest.TestCase): + def makexpi(self, pkg_name): + self.xpiname = "%s.xpi" % pkg_name + create_xpi(self.xpiname, pkg_name, 'bug-588119-files') + self.xpi = zipfile.ZipFile(self.xpiname, 'r') + options = self.xpi.read('harness-options.json') + self.xpi_harness_options = json.loads(options) + + def setUp(self): + self.xpiname = None + self.xpi = None + + def tearDown(self): + if self.xpi: + self.xpi.close() + if self.xpiname and os.path.exists(self.xpiname): + os.remove(self.xpiname) + + def testPackageWithImplicitIcon(self): + self.makexpi('implicit-icon') + assert 'icon.png' in self.xpi.namelist() + + def testPackageWithImplicitIcon64(self): + self.makexpi('implicit-icon') + assert 'icon64.png' in self.xpi.namelist() + + def testPackageWithExplicitIcon(self): + self.makexpi('explicit-icon') + assert 'icon.png' in self.xpi.namelist() + + def testPackageWithExplicitIcon64(self): + self.makexpi('explicit-icon') + assert 'icon64.png' in self.xpi.namelist() + + def testPackageWithNoIcon(self): + self.makexpi('no-icon') + assert 'icon.png' not in self.xpi.namelist() + + def testIconPathNotInHarnessOptions(self): + self.makexpi('implicit-icon') + assert 'icon' not in self.xpi_harness_options + + def testIcon64PathNotInHarnessOptions(self): + self.makexpi('implicit-icon') + assert 'icon64' not in self.xpi_harness_options + +class ExtraHarnessOptions(unittest.TestCase): + def setUp(self): + self.xpiname = None + self.xpi = None + + def tearDown(self): + if self.xpi: + self.xpi.close() + if self.xpiname and os.path.exists(self.xpiname): + os.remove(self.xpiname) + + def testOptions(self): + pkg_name = "extra-options" + self.xpiname = "%s.xpi" % pkg_name + create_xpi(self.xpiname, pkg_name, "bug-669274-files", + extra_harness_options={"builderVersion": "futuristic"}) + self.xpi = zipfile.ZipFile(self.xpiname, 'r') + options = self.xpi.read('harness-options.json') + hopts = json.loads(options) + self.failUnless("builderVersion" in hopts) + self.failUnlessEqual(hopts["builderVersion"], "futuristic") + + def testBadOptionName(self): + pkg_name = "extra-options" + self.xpiname = "%s.xpi" % pkg_name + self.failUnlessRaises(xpi.HarnessOptionAlreadyDefinedError, + create_xpi, + self.xpiname, pkg_name, "bug-669274-files", + extra_harness_options={"main": "already in use"}) + +class SmallXPI(unittest.TestCase): + def setUp(self): + self.root = up(os.path.abspath(__file__), 4) + def get_linker_files_dir(self, name): + return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) + def get_pkg(self, name): + d = self.get_linker_files_dir(name) + return packaging.get_config_in_dir(d) + + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + def test_contents(self): + target_cfg = self.get_pkg("three") + package_path = [self.get_linker_files_dir("three-deps")] + pkg_cfg = packaging.build_config(self.root, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + used_files = list(m.get_used_files()) + here = up(os.path.abspath(__file__)) + def absify(*parts): + fn = os.path.join(here, "linker-files", *parts) + return os.path.abspath(fn) + expected = [absify(*parts) for parts in + [("three", "lib", "main.js"), + ("three-deps", "three-a", "lib", "main.js"), + ("three-deps", "three-a", "lib", "subdir", "subfile.js"), + ("three-deps", "three-a", "data", "msg.txt"), + ("three-deps", "three-a", "data", "subdir", "submsg.txt"), + ("three-deps", "three-b", "lib", "main.js"), + ("three-deps", "three-c", "lib", "main.js"), + ("three-deps", "three-c", "lib", "sub", "foo.js"), + ]] + missing = set(expected) - set(used_files) + extra = set(used_files) - set(expected) + self.failUnlessEqual((list(missing), list(extra)), ([], [])) + used_deps = m.get_used_packages() + + build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, + used_deps, + include_tests=False) + options = {'main': target_cfg.main} + options.update(build) + basedir = self.make_basedir() + xpi_name = os.path.join(basedir, "contents.xpi") + xpi.build_xpi(template_root_dir=xpi_template_path, + manifest=fake_manifest, + xpi_path=xpi_name, + harness_options=options, + limit_to=used_files) + x = zipfile.ZipFile(xpi_name, "r") + names = x.namelist() + expected = ["components/", + "components/harness.js", + # the real template also has 'bootstrap.js', but the fake + # one in tests/static-files/xpi-template doesn't + "harness-options.json", + "install.rdf", + "defaults/preferences/prefs.js", + "resources/", + "resources/api-utils/", + "resources/api-utils/data/", + "resources/api-utils/lib/", + "resources/three/", + "resources/three/lib/", + "resources/three/lib/main.js", + "resources/three-a/", + "resources/three-a/data/", + "resources/three-a/data/msg.txt", + "resources/three-a/data/subdir/", + "resources/three-a/data/subdir/submsg.txt", + "resources/three-a/lib/", + "resources/three-a/lib/main.js", + "resources/three-a/lib/subdir/", + "resources/three-a/lib/subdir/subfile.js", + "resources/three-b/", + "resources/three-b/lib/", + "resources/three-b/lib/main.js", + "resources/three-c/", + "resources/three-c/lib/", + "resources/three-c/lib/main.js", + "resources/three-c/lib/sub/", + "resources/three-c/lib/sub/foo.js", + # notably absent: three-a/lib/unused.js + "locale/", + "locale/fr-FR.json", + "locales.json", + ] + # showing deltas makes failures easier to investigate + missing = set(expected) - set(names) + extra = set(names) - set(expected) + self.failUnlessEqual((list(missing), list(extra)), ([], [])) + self.failUnlessEqual(sorted(names), sorted(expected)) + + # check locale files + localedata = json.loads(x.read("locales.json")) + self.failUnlessEqual(sorted(localedata["locales"]), sorted(["fr-FR"])) + content = x.read("locale/fr-FR.json") + locales = json.loads(content) + # Locale files are merged into one. + # Conflicts are silently resolved by taking last package translation, + # so that we get "No" translation from three-c instead of three-b one. + self.failUnlessEqual(locales, json.loads(u''' + { + "No": "Nein", + "one": "un", + "What?": "Quoi?", + "Yes": "Oui", + "plural": { + "other": "other", + "one": "one" + }, + "uft8_value": "\u00e9" + }''')) + + def test_scantests(self): + target_cfg = self.get_pkg("three") + package_path = [self.get_linker_files_dir("three-deps")] + pkg_cfg = packaging.build_config(self.root, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True) + self.failUnlessEqual(sorted(m.get_all_test_modules()), + sorted(["test-one", "test-two"])) + # the current __init__.py code omits limit_to=used_files for 'cfx + # test', so all test files are included in the XPI. But the test + # runner will only execute the tests that m.get_all_test_modules() + # tells us about (which are put into the .allTestModules property of + # harness-options.json). + used_deps = m.get_used_packages() + + build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, + used_deps, + include_tests=True) + options = {'main': target_cfg.main} + options.update(build) + basedir = self.make_basedir() + xpi_name = os.path.join(basedir, "contents.xpi") + xpi.build_xpi(template_root_dir=xpi_template_path, + manifest=fake_manifest, + xpi_path=xpi_name, + harness_options=options, + limit_to=None) + x = zipfile.ZipFile(xpi_name, "r") + names = x.namelist() + self.failUnless("resources/api-utils/lib/unit-test.js" in names, names) + self.failUnless("resources/api-utils/lib/unit-test-finder.js" in names, names) + self.failUnless("resources/test-harness/lib/harness.js" in names, names) + self.failUnless("resources/test-harness/lib/run-tests.js" in names, names) + # all files are copied into the XPI, even the things that don't look + # like tests. + self.failUnless("resources/three/tests/test-one.js" in names, names) + self.failUnless("resources/three/tests/test-two.js" in names, names) + self.failUnless("resources/three/tests/nontest.js" in names, names) + + def test_scantests_filter(self): + target_cfg = self.get_pkg("three") + package_path = [self.get_linker_files_dir("three-deps")] + pkg_cfg = packaging.build_config(self.root, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-kit"]) + FILTER = ".*one.*" + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True, + test_filter_re=FILTER) + self.failUnlessEqual(sorted(m.get_all_test_modules()), + sorted(["test-one"])) + # the current __init__.py code omits limit_to=used_files for 'cfx + # test', so all test files are included in the XPI. But the test + # runner will only execute the tests that m.get_all_test_modules() + # tells us about (which are put into the .allTestModules property of + # harness-options.json). + used_deps = m.get_used_packages() + + build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, + used_deps, + include_tests=True) + options = {'main': target_cfg.main} + options.update(build) + basedir = self.make_basedir() + xpi_name = os.path.join(basedir, "contents.xpi") + xpi.build_xpi(template_root_dir=xpi_template_path, + manifest=fake_manifest, + xpi_path=xpi_name, + harness_options=options, + limit_to=None) + x = zipfile.ZipFile(xpi_name, "r") + names = x.namelist() + self.failUnless("resources/api-utils/lib/unit-test.js" in names, names) + self.failUnless("resources/api-utils/lib/unit-test-finder.js" in names, names) + self.failUnless("resources/test-harness/lib/harness.js" in names, names) + self.failUnless("resources/test-harness/lib/run-tests.js" in names, names) + # get_all_test_modules() respects the filter. But all files are still + # copied into the XPI. + self.failUnless("resources/three/tests/test-one.js" in names, names) + self.failUnless("resources/three/tests/test-two.js" in names, names) + self.failUnless("resources/three/tests/nontest.js" in names, names) + + +def document_dir(name): + if name in ['packages', 'xpi-template']: + dirname = os.path.join(test_packaging.static_files_path, name) + document_dir_files(dirname) + elif name == 'xpi-output': + create_xpi('test-xpi.xpi') + document_zip_file('test-xpi.xpi') + os.remove('test-xpi.xpi') + else: + raise Exception('unknown dir: %s' % name) + +def normpath(path): + """ + Make a platform-specific relative path use '/' as a separator. + """ + + return path.replace(os.path.sep, '/') + +def document_zip_file(path): + zip = zipfile.ZipFile(path, 'r') + for name in sorted(zip.namelist()): + contents = zip.read(name) + lines = contents.splitlines() + if len(lines) == 1 and name.endswith('.json') and len(lines[0]) > 75: + # Ideally we would json-decode this, but it results + # in an annoying 'u' before every string literal, + # since json decoding makes all strings unicode. + contents = eval(contents) + contents = pprint.pformat(contents) + lines = contents.splitlines() + contents = "\n ".join(lines) + print "%s:\n %s" % (normpath(name), contents) + zip.close() + +def document_dir_files(path): + filename_contents_tuples = [] + for dirpath, dirnames, filenames in os.walk(path): + relpath = dirpath[len(path)+1:] + for filename in filenames: + abspath = os.path.join(dirpath, filename) + contents = open(abspath, 'r').read() + contents = "\n ".join(contents.splitlines()) + relfilename = os.path.join(relpath, filename) + filename_contents_tuples.append((normpath(relfilename), contents)) + filename_contents_tuples.sort() + for filename, contents in filename_contents_tuples: + print "%s:" % filename + print " %s" % contents + +def create_xpi(xpiname, pkg_name='aardvark', dirname='static-files', + extra_harness_options={}): + configs = test_packaging.get_configs(pkg_name, dirname) + options = {'main': configs.target_cfg.main, + 'jetpackID': buildJID(configs.target_cfg), } + options.update(configs.build) + xpi.build_xpi(template_root_dir=xpi_template_path, + manifest=fake_manifest, + xpi_path=xpiname, + harness_options=options, + extra_harness_options=extra_harness_options) + +if __name__ == '__main__': + unittest.main() diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/util.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/util.py new file mode 100644 index 0000000..513495a --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/util.py @@ -0,0 +1,23 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + + +IGNORED_FILE_PREFIXES = ["."] +IGNORED_FILE_SUFFIXES = ["~", ".swp"] +IGNORED_DIRS = [".git", ".svn", ".hg"] + +def filter_filenames(filenames, ignored_files=[".hgignore"]): + for filename in filenames: + if filename in ignored_files: + continue + if any([filename.startswith(suffix) + for suffix in IGNORED_FILE_PREFIXES]): + continue + if any([filename.endswith(suffix) + for suffix in IGNORED_FILE_SUFFIXES]): + continue + yield filename + +def filter_dirnames(dirnames): + return [dirname for dirname in dirnames if dirname not in IGNORED_DIRS] diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/version_comparator.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/version_comparator.py new file mode 100644 index 0000000..3999e71 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/version_comparator.py @@ -0,0 +1,206 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +''' + This is a really crummy, slow Python implementation of the Mozilla + platform's nsIVersionComparator interface: + + https://developer.mozilla.org/En/NsIVersionComparator + + For more information, also see: + + http://mxr.mozilla.org/mozilla/source/xpcom/glue/nsVersionComparator.cpp +''' + +import re +import sys + +class VersionPart(object): + ''' + Examples: + + >>> VersionPart('1') + (1, None, 0, None) + + >>> VersionPart('1pre') + (1, 'pre', 0, None) + + >>> VersionPart('1pre10') + (1, 'pre', 10, None) + + >>> VersionPart('1pre10a') + (1, 'pre', 10, 'a') + + >>> VersionPart('1+') + (2, 'pre', 0, None) + + >>> VersionPart('*').numA == sys.maxint + True + + >>> VersionPart('1') < VersionPart('2') + True + + >>> VersionPart('2') > VersionPart('1') + True + + >>> VersionPart('1') == VersionPart('1') + True + + >>> VersionPart('1pre') > VersionPart('1') + False + + >>> VersionPart('1') < VersionPart('1pre') + False + + >>> VersionPart('1pre1') < VersionPart('1pre2') + True + + >>> VersionPart('1pre10b') > VersionPart('1pre10a') + True + + >>> VersionPart('1pre10b') == VersionPart('1pre10b') + True + + >>> VersionPart('1pre10a') < VersionPart('1pre10b') + True + + >>> VersionPart('1') > VersionPart('') + True + ''' + + _int_part = re.compile('[+-]?(\d*)(.*)') + _num_chars = '0123456789+-' + + def __init__(self, part): + self.numA = 0 + self.strB = None + self.numC = 0 + self.extraD = None + + if not part: + return + + if part == '*': + self.numA = sys.maxint + else: + match = self._int_part.match(part) + self.numA = int(match.group(1)) + self.strB = match.group(2) or None + if self.strB == '+': + self.strB = 'pre' + self.numA += 1 + elif self.strB: + i = 0 + num_found = -1 + for char in self.strB: + if char in self._num_chars: + num_found = i + break + i += 1 + if num_found != -1: + match = self._int_part.match(self.strB[num_found:]) + self.numC = int(match.group(1)) + self.extraD = match.group(2) or None + self.strB = self.strB[:num_found] + + def _strcmp(self, str1, str2): + # Any string is *before* no string. + if str1 is None: + if str2 is None: + return 0 + else: + return 1 + + if str2 is None: + return -1 + + return cmp(str1, str2) + + def __cmp__(self, other): + r = cmp(self.numA, other.numA) + if r: + return r + + r = self._strcmp(self.strB, other.strB) + if r: + return r + + r = cmp(self.numC, other.numC) + if r: + return r + + return self._strcmp(self.extraD, other.extraD) + + def __repr__(self): + return repr((self.numA, self.strB, self.numC, self.extraD)) + +def compare(a, b): + ''' + Examples: + + >>> compare('1', '2') + -1 + + >>> compare('1', '1') + 0 + + >>> compare('2', '1') + 1 + + >>> compare('1.0pre1', '1.0pre2') + -1 + + >>> compare('1.0pre2', '1.0') + -1 + + >>> compare('1.0', '1.0.0') + 0 + + >>> compare('1.0.0', '1.0.0.0') + 0 + + >>> compare('1.0.0.0', '1.1pre') + -1 + + >>> compare('1.1pre', '1.1pre0') + 0 + + >>> compare('1.1pre0', '1.0+') + 0 + + >>> compare('1.0+', '1.1pre1a') + -1 + + >>> compare('1.1pre1a', '1.1pre1') + -1 + + >>> compare('1.1pre1', '1.1pre10a') + -1 + + >>> compare('1.1pre10a', '1.1pre10') + -1 + + >>> compare('1.1pre10a', '1.*') + -1 + ''' + + a_parts = a.split('.') + b_parts = b.split('.') + + if len(a_parts) < len(b_parts): + a_parts.extend([''] * (len(b_parts) - len(a_parts))) + else: + b_parts.extend([''] * (len(a_parts) - len(b_parts))) + + for a_part, b_part in zip(a_parts, b_parts): + r = cmp(VersionPart(a_part), VersionPart(b_part)) + if r: + return r + + return 0 + +if __name__ == '__main__': + import doctest + + doctest.testmod(verbose=True) diff --git a/tools/addon-sdk-1.7/python-lib/cuddlefish/xpi.py b/tools/addon-sdk-1.7/python-lib/cuddlefish/xpi.py new file mode 100644 index 0000000..72b8477 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/cuddlefish/xpi.py @@ -0,0 +1,155 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import zipfile +import simplejson as json +from cuddlefish.util import filter_filenames, filter_dirnames + +class HarnessOptionAlreadyDefinedError(Exception): + """You cannot use --harness-option on keys that already exist in + harness-options.json""" + +ZIPSEP = "/" # always use "/" in zipfiles + +def make_zipfile_path(localroot, localpath): + return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep)) + +def mkzipdir(zf, path): + dirinfo = zipfile.ZipInfo(path) + dirinfo.external_attr = int("040755", 8) << 16L + zf.writestr(dirinfo, "") + +def build_xpi(template_root_dir, manifest, xpi_path, + harness_options, limit_to=None, extra_harness_options={}): + zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED) + + open('.install.rdf', 'w').write(str(manifest)) + zf.write('.install.rdf', 'install.rdf') + os.remove('.install.rdf') + + if 'icon' in harness_options: + zf.write(str(harness_options['icon']), 'icon.png') + del harness_options['icon'] + + if 'icon64' in harness_options: + zf.write(str(harness_options['icon64']), 'icon64.png') + del harness_options['icon64'] + + if 'preferences' in harness_options: + from options_xul import parse_options, validate_prefs + + validate_prefs(harness_options["preferences"]) + + opts_xul = parse_options(harness_options["preferences"], + harness_options["jetpackID"]) + open('.options.xul', 'wb').write(opts_xul.encode("utf-8")) + zf.write('.options.xul', 'options.xul') + os.remove('.options.xul') + + from options_defaults import parse_options_defaults + prefs_js = parse_options_defaults(harness_options["preferences"], + harness_options["jetpackID"]) + open('.prefs.js', 'wb').write(prefs_js.encode("utf-8")) + + else: + open('.prefs.js', 'wb').write("") + + zf.write('.prefs.js', 'defaults/preferences/prefs.js') + os.remove('.prefs.js') + + + IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf", + "application.ini", xpi_path] + + files_to_copy = {} # maps zipfile path to local-disk abspath + dirs_to_create = set() # zipfile paths, no trailing slash + + for dirpath, dirnames, filenames in os.walk(template_root_dir): + filenames = list(filter_filenames(filenames, IGNORED_FILES)) + dirnames[:] = filter_dirnames(dirnames) + for dirname in dirnames: + arcpath = make_zipfile_path(template_root_dir, + os.path.join(dirpath, dirname)) + dirs_to_create.add(arcpath) + for filename in filenames: + abspath = os.path.join(dirpath, filename) + arcpath = make_zipfile_path(template_root_dir, abspath) + files_to_copy[arcpath] = abspath + + # `packages` attribute contains a dictionnary of dictionnary + # of all packages sections directories + for packageName in harness_options['packages']: + base_arcpath = ZIPSEP.join(['resources', packageName]) + # Always write the top directory, even if it contains no files, since + # the harness will try to access it. + dirs_to_create.add(base_arcpath) + for sectionName in harness_options['packages'][packageName]: + abs_dirname = harness_options['packages'][packageName][sectionName] + base_arcpath = ZIPSEP.join(['resources', packageName, sectionName]) + # Always write the top directory, even if it contains no files, since + # the harness will try to access it. + dirs_to_create.add(base_arcpath) + # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/ + for dirpath, dirnames, filenames in os.walk(abs_dirname): + goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) + dirnames[:] = filter_dirnames(dirnames) + for filename in goodfiles: + abspath = os.path.join(dirpath, filename) + if limit_to is not None and abspath not in limit_to: + continue # strip unused files + arcpath = ZIPSEP.join( + ['resources', + packageName, + sectionName, + make_zipfile_path(abs_dirname, + os.path.join(dirpath, filename)), + ]) + files_to_copy[str(arcpath)] = str(abspath) + del harness_options['packages'] + + locales_json_data = {"locales": []} + mkzipdir(zf, "locale/") + for language in sorted(harness_options['locale']): + locales_json_data["locales"].append(language) + locale = harness_options['locale'][language] + # Be carefull about strings, we need to always ensure working with UTF-8 + jsonStr = json.dumps(locale, indent=1, sort_keys=True, ensure_ascii=False) + info = zipfile.ZipInfo('locale/' + language + '.json') + info.external_attr = 0444 << 16L + zf.writestr(info, jsonStr.encode( "utf-8" )) + del harness_options['locale'] + + jsonStr = json.dumps(locales_json_data, ensure_ascii=True) +"\n" + info = zipfile.ZipInfo('locales.json') + info.external_attr = 0444 << 16L + zf.writestr(info, jsonStr.encode("utf-8")) + + # now figure out which directories we need: all retained files parents + for arcpath in files_to_copy: + bits = arcpath.split("/") + for i in range(1,len(bits)): + parentpath = ZIPSEP.join(bits[0:i]) + dirs_to_create.add(parentpath) + + # create zipfile in alphabetical order, with each directory before its + # files + for name in sorted(dirs_to_create.union(set(files_to_copy))): + if name in dirs_to_create: + mkzipdir(zf, name+"/") + if name in files_to_copy: + zf.write(files_to_copy[name], name) + + harness_options = harness_options.copy() + for key,value in extra_harness_options.items(): + if key in harness_options: + msg = "Can't use --harness-option for existing key '%s'" % key + raise HarnessOptionAlreadyDefinedError(msg) + harness_options[key] = value + open('.options.json', 'w').write(json.dumps(harness_options, indent=1, + sort_keys=True)) + zf.write('.options.json', 'harness-options.json') + os.remove('.options.json') + + zf.close() diff --git a/tools/addon-sdk-1.7/python-lib/jetpack_sdk_env.py b/tools/addon-sdk-1.7/python-lib/jetpack_sdk_env.py new file mode 100644 index 0000000..79fdd61 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/jetpack_sdk_env.py @@ -0,0 +1,66 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 sys +import os + +def welcome(): + """ + Perform a bunch of sanity tests to make sure the Add-on SDK + environment is sane, and then display a welcome message. + """ + + try: + if sys.version_info[0] > 2: + print ("Error: You appear to be using Python %d, but " + "the Add-on SDK only supports the Python 2.x line." % + (sys.version_info[0])) + return + + import mozrunner + + if 'CUDDLEFISH_ROOT' not in os.environ: + print ("Error: CUDDLEFISH_ROOT environment variable does " + "not exist! It should point to the root of the " + "Add-on SDK repository.") + return + + env_root = os.environ['CUDDLEFISH_ROOT'] + + bin_dir = os.path.join(env_root, 'bin') + python_lib_dir = os.path.join(env_root, 'python-lib') + path = os.environ['PATH'].split(os.path.pathsep) + + if bin_dir not in path: + print ("Warning: the Add-on SDK binary directory %s " + "does not appear to be in your PATH. You may " + "not be able to run 'cfx' or other SDK tools." % + bin_dir) + + if python_lib_dir not in sys.path: + print ("Warning: the Add-on SDK python-lib directory %s " + "does not appear to be in your sys.path, which " + "is odd because I'm running from it." % python_lib_dir) + + if not mozrunner.__path__[0].startswith(env_root): + print ("Warning: your mozrunner package is installed at %s, " + "which does not seem to be located inside the Jetpack " + "SDK. This may cause problems, and you may want to " + "uninstall the other version. See bug 556562 for " + "more information." % mozrunner.__path__[0]) + except Exception: + # Apparently we can't get the actual exception object in the + # 'except' clause in a way that's syntax-compatible for both + # Python 2.x and 3.x, so we'll have to use the traceback module. + + import traceback + _, e, _ = sys.exc_info() + print ("Verification of Add-on SDK environment failed (%s)." % e) + print ("Your SDK may not work properly.") + return + + print ("Welcome to the Add-on SDK. Run 'cfx docs' for assistance.") + +if __name__ == '__main__': + welcome() diff --git a/tools/addon-sdk-1.7/python-lib/markdown/AUTHORS b/tools/addon-sdk-1.7/python-lib/markdown/AUTHORS new file mode 100644 index 0000000..cfe2b34 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/AUTHORS @@ -0,0 +1,43 @@ +Primary Authors +=============== + +Yuri Takteyev <http://freewisdom.org/>, who has written much of the current code +while procrastingating his Ph.D. + +Waylan Limberg <http://achinghead.com/>, who has written most of the available +extensions and later was asked to join Yuri, fixing nummrious bugs, adding +documentation and making general improvements to the existing codebase, +included a complete refactor of the core. + +Artem Yunusov, who as part of a 2008 GSoC project, has refactored inline +patterns, replaced the NanoDOM with ElementTree support and made various other +improvements. + +Manfed Stienstra <http://www.dwerg.net/>, who wrote the original version of +the script and is responsible for various parts of the existing codebase. + +David Wolever, who refactored the extension API and made other improvements +as he helped to integrate Markdown into Dr.Project. + +Other Contributors +================== + +The incomplete list of individuals below have provided patches or otherwise +contributed to the project in various ways. We would like to thank everyone +who has contributed to the progect in any way. + +Eric Abrahamsen +Jeff Balogh +Sergej Chodarev +Chris Clark +Tiago Cogumbreiro +Kjell Magne Fauske +G. Clark Haynes +Daniel Krech +Steward Midwinter +Jack Miller +Neale Pickett +John Szakmeister +Malcolm Tredinnick +Ben Wilson +and many others who helped by reporting bugs diff --git a/tools/addon-sdk-1.7/python-lib/markdown/LICENSE b/tools/addon-sdk-1.7/python-lib/markdown/LICENSE new file mode 100644 index 0000000..4cd8b14 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/LICENSE @@ -0,0 +1,30 @@ +Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/__init__.py b/tools/addon-sdk-1.7/python-lib/markdown/__init__.py new file mode 100644 index 0000000..0d1c504 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/__init__.py @@ -0,0 +1,603 @@ +""" +Python Markdown +=============== + +Python Markdown converts Markdown to HTML and can be used as a library or +called from the command line. + +## Basic usage as a module: + + import markdown + md = Markdown() + html = md.convert(your_text_string) + +## Basic use from the command line: + + python markdown.py source.txt > destination.html + +Run "python markdown.py --help" to see more options. + +## Extensions + +See <http://www.freewisdom.org/projects/python-markdown/> for more +information and instructions on how to extend the functionality of +Python Markdown. Read that before you try modifying this file. + +## Authors and License + +Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and +maintained by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan +Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com). + +Contact: markdown@freewisdom.org + +Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) +Copyright 200? Django Software Foundation (OrderedDict implementation) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see docs/LICENSE for details). +""" + +version = "2.0" +version_info = (2,0,0, "Final") + +import re +import codecs +import sys +import warnings +import logging +from logging import DEBUG, INFO, WARN, ERROR, CRITICAL + + +""" +CONSTANTS +============================================================================= +""" + +""" +Constants you might want to modify +----------------------------------------------------------------------------- +""" + +# default logging level for command-line use +COMMAND_LINE_LOGGING_LEVEL = CRITICAL +TAB_LENGTH = 4 # expand tabs to this many spaces +ENABLE_ATTRIBUTES = True # @id = xyz -> <... id="xyz"> +SMART_EMPHASIS = True # this_or_that does not become this<i>or</i>that +DEFAULT_OUTPUT_FORMAT = 'xhtml1' # xhtml or html4 output +HTML_REMOVED_TEXT = "[HTML_REMOVED]" # text used instead of HTML in safe mode +BLOCK_LEVEL_ELEMENTS = re.compile("p|div|h[1-6]|blockquote|pre|table|dl|ol|ul" + "|script|noscript|form|fieldset|iframe|math" + "|ins|del|hr|hr/|style|li|dt|dd|thead|tbody" + "|tr|th|td") +DOC_TAG = "div" # Element used to wrap document - later removed + +# Placeholders +STX = u'\u0002' # Use STX ("Start of text") for start-of-placeholder +ETX = u'\u0003' # Use ETX ("End of text") for end-of-placeholder +INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:" +INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX +AMP_SUBSTITUTE = STX+"amp"+ETX + + +""" +Constants you probably do not need to change +----------------------------------------------------------------------------- +""" + +RTL_BIDI_RANGES = ( (u'\u0590', u'\u07FF'), + # Hebrew (0590-05FF), Arabic (0600-06FF), + # Syriac (0700-074F), Arabic supplement (0750-077F), + # Thaana (0780-07BF), Nko (07C0-07FF). + (u'\u2D30', u'\u2D7F'), # Tifinagh + ) + + +""" +AUXILIARY GLOBAL FUNCTIONS +============================================================================= +""" + + +def message(level, text): + """ A wrapper method for logging debug messages. """ + logger = logging.getLogger('MARKDOWN') + if logger.handlers: + # The logger is configured + logger.log(level, text) + if level > WARN: + sys.exit(0) + elif level > WARN: + raise MarkdownException, text + else: + warnings.warn(text, MarkdownWarning) + + +def isBlockLevel(tag): + """Check if the tag is a block level HTML tag.""" + return BLOCK_LEVEL_ELEMENTS.match(tag) + +""" +MISC AUXILIARY CLASSES +============================================================================= +""" + +class AtomicString(unicode): + """A string which should not be further processed.""" + pass + + +class MarkdownException(Exception): + """ A Markdown Exception. """ + pass + + +class MarkdownWarning(Warning): + """ A Markdown Warning. """ + pass + + +""" +OVERALL DESIGN +============================================================================= + +Markdown processing takes place in four steps: + +1. A bunch of "preprocessors" munge the input text. +2. BlockParser() parses the high-level structural elements of the + pre-processed text into an ElementTree. +3. A bunch of "treeprocessors" are run against the ElementTree. One such + treeprocessor runs InlinePatterns against the ElementTree, detecting inline + markup. +4. Some post-processors are run against the text after the ElementTree has + been serialized into text. +5. The output is written to a string. + +Those steps are put together by the Markdown() class. + +""" + +import preprocessors +import blockprocessors +import treeprocessors +import inlinepatterns +import postprocessors +import blockparser +import etree_loader +import odict + +# Extensions should use "markdown.etree" instead of "etree" (or do `from +# markdown import etree`). Do not import it by yourself. + +etree = etree_loader.importETree() + +# Adds the ability to output html4 +import html4 + + +class Markdown: + """Convert Markdown to HTML.""" + + def __init__(self, + extensions=[], + extension_configs={}, + safe_mode = False, + output_format=DEFAULT_OUTPUT_FORMAT): + """ + Creates a new Markdown instance. + + Keyword arguments: + + * extensions: A list of extensions. + If they are of type string, the module mdx_name.py will be loaded. + If they are a subclass of markdown.Extension, they will be used + as-is. + * extension-configs: Configuration setting for extensions. + * safe_mode: Disallow raw html. One of "remove", "replace" or "escape". + * output_format: Format of output. Supported formats are: + * "xhtml1": Outputs XHTML 1.x. Default. + * "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1). + * "html4": Outputs HTML 4 + * "html": Outputs latest supported version of HTML (currently HTML 4). + Note that it is suggested that the more specific formats ("xhtml1" + and "html4") be used as "xhtml" or "html" may change in the future + if it makes sense at that time. + + """ + + self.safeMode = safe_mode + self.registeredExtensions = [] + self.docType = "" + self.stripTopLevelTags = True + + # Preprocessors + self.preprocessors = odict.OrderedDict() + self.preprocessors["html_block"] = \ + preprocessors.HtmlBlockPreprocessor(self) + self.preprocessors["reference"] = \ + preprocessors.ReferencePreprocessor(self) + # footnote preprocessor will be inserted with "<reference" + + # Block processors - ran by the parser + self.parser = blockparser.BlockParser() + self.parser.blockprocessors['empty'] = \ + blockprocessors.EmptyBlockProcessor(self.parser) + self.parser.blockprocessors['indent'] = \ + blockprocessors.ListIndentProcessor(self.parser) + self.parser.blockprocessors['code'] = \ + blockprocessors.CodeBlockProcessor(self.parser) + self.parser.blockprocessors['hashheader'] = \ + blockprocessors.HashHeaderProcessor(self.parser) + self.parser.blockprocessors['setextheader'] = \ + blockprocessors.SetextHeaderProcessor(self.parser) + self.parser.blockprocessors['hr'] = \ + blockprocessors.HRProcessor(self.parser) + self.parser.blockprocessors['olist'] = \ + blockprocessors.OListProcessor(self.parser) + self.parser.blockprocessors['ulist'] = \ + blockprocessors.UListProcessor(self.parser) + self.parser.blockprocessors['quote'] = \ + blockprocessors.BlockQuoteProcessor(self.parser) + self.parser.blockprocessors['paragraph'] = \ + blockprocessors.ParagraphProcessor(self.parser) + + + #self.prePatterns = [] + + # Inline patterns - Run on the tree + self.inlinePatterns = odict.OrderedDict() + self.inlinePatterns["backtick"] = \ + inlinepatterns.BacktickPattern(inlinepatterns.BACKTICK_RE) + self.inlinePatterns["escape"] = \ + inlinepatterns.SimpleTextPattern(inlinepatterns.ESCAPE_RE) + self.inlinePatterns["reference"] = \ + inlinepatterns.ReferencePattern(inlinepatterns.REFERENCE_RE, self) + self.inlinePatterns["link"] = \ + inlinepatterns.LinkPattern(inlinepatterns.LINK_RE, self) + self.inlinePatterns["image_link"] = \ + inlinepatterns.ImagePattern(inlinepatterns.IMAGE_LINK_RE, self) + self.inlinePatterns["image_reference"] = \ + inlinepatterns.ImageReferencePattern(inlinepatterns.IMAGE_REFERENCE_RE, self) + self.inlinePatterns["autolink"] = \ + inlinepatterns.AutolinkPattern(inlinepatterns.AUTOLINK_RE, self) + self.inlinePatterns["automail"] = \ + inlinepatterns.AutomailPattern(inlinepatterns.AUTOMAIL_RE, self) + self.inlinePatterns["linebreak2"] = \ + inlinepatterns.SubstituteTagPattern(inlinepatterns.LINE_BREAK_2_RE, 'br') + self.inlinePatterns["linebreak"] = \ + inlinepatterns.SubstituteTagPattern(inlinepatterns.LINE_BREAK_RE, 'br') + self.inlinePatterns["html"] = \ + inlinepatterns.HtmlPattern(inlinepatterns.HTML_RE, self) + self.inlinePatterns["entity"] = \ + inlinepatterns.HtmlPattern(inlinepatterns.ENTITY_RE, self) + self.inlinePatterns["not_strong"] = \ + inlinepatterns.SimpleTextPattern(inlinepatterns.NOT_STRONG_RE) + self.inlinePatterns["strong_em"] = \ + inlinepatterns.DoubleTagPattern(inlinepatterns.STRONG_EM_RE, 'strong,em') + self.inlinePatterns["strong"] = \ + inlinepatterns.SimpleTagPattern(inlinepatterns.STRONG_RE, 'strong') + self.inlinePatterns["emphasis"] = \ + inlinepatterns.SimpleTagPattern(inlinepatterns.EMPHASIS_RE, 'em') + self.inlinePatterns["emphasis2"] = \ + inlinepatterns.SimpleTagPattern(inlinepatterns.EMPHASIS_2_RE, 'em') + # The order of the handlers matters!!! + + + # Tree processors - run once we have a basic parse. + self.treeprocessors = odict.OrderedDict() + self.treeprocessors["inline"] = treeprocessors.InlineProcessor(self) + self.treeprocessors["prettify"] = \ + treeprocessors.PrettifyTreeprocessor(self) + + # Postprocessors - finishing touches. + self.postprocessors = odict.OrderedDict() + self.postprocessors["raw_html"] = \ + postprocessors.RawHtmlPostprocessor(self) + self.postprocessors["amp_substitute"] = \ + postprocessors.AndSubstitutePostprocessor() + # footnote postprocessor will be inserted with ">amp_substitute" + + # Map format keys to serializers + self.output_formats = { + 'html' : html4.to_html_string, + 'html4' : html4.to_html_string, + 'xhtml' : etree.tostring, + 'xhtml1': etree.tostring, + } + + self.references = {} + self.htmlStash = preprocessors.HtmlStash() + self.registerExtensions(extensions = extensions, + configs = extension_configs) + self.set_output_format(output_format) + self.reset() + + def registerExtensions(self, extensions, configs): + """ + Register extensions with this instance of Markdown. + + Keyword aurguments: + + * extensions: A list of extensions, which can either + be strings or objects. See the docstring on Markdown. + * configs: A dictionary mapping module names to config options. + + """ + for ext in extensions: + if isinstance(ext, basestring): + ext = load_extension(ext, configs.get(ext, [])) + try: + ext.extendMarkdown(self, globals()) + except AttributeError: + message(ERROR, "Incorrect type! Extension '%s' is " + "neither a string or an Extension." %(repr(ext))) + + + def registerExtension(self, extension): + """ This gets called by the extension """ + self.registeredExtensions.append(extension) + + def reset(self): + """ + Resets all state variables so that we can start with a new text. + """ + self.htmlStash.reset() + self.references.clear() + + for extension in self.registeredExtensions: + extension.reset() + + def set_output_format(self, format): + """ Set the output format for the class instance. """ + try: + self.serializer = self.output_formats[format.lower()] + except KeyError: + message(CRITICAL, 'Invalid Output Format: "%s". Use one of %s.' \ + % (format, self.output_formats.keys())) + + def convert(self, source): + """ + Convert markdown to serialized XHTML or HTML. + + Keyword arguments: + + * source: Source text as a Unicode string. + + """ + + # Fixup the source text + if not source.strip(): + return u"" # a blank unicode string + try: + source = unicode(source) + except UnicodeDecodeError: + message(CRITICAL, 'UnicodeDecodeError: Markdown only accepts unicode or ascii input.') + return u"" + + source = source.replace(STX, "").replace(ETX, "") + source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n" + source = re.sub(r'\n\s+\n', '\n\n', source) + source = source.expandtabs(TAB_LENGTH) + + # Split into lines and run the line preprocessors. + self.lines = source.split("\n") + for prep in self.preprocessors.values(): + self.lines = prep.run(self.lines) + + # Parse the high-level elements. + root = self.parser.parseDocument(self.lines).getroot() + + # Run the tree-processors + for treeprocessor in self.treeprocessors.values(): + newRoot = treeprocessor.run(root) + if newRoot: + root = newRoot + + # Serialize _properly_. Strip top-level tags. + output, length = codecs.utf_8_decode(self.serializer(root, encoding="utf8")) + if self.stripTopLevelTags: + start = output.index('<%s>'%DOC_TAG)+len(DOC_TAG)+2 + end = output.rindex('</%s>'%DOC_TAG) + output = output[start:end].strip() + + # Run the text post-processors + for pp in self.postprocessors.values(): + output = pp.run(output) + + return output.strip() + + def convertFile(self, input=None, output=None, encoding=None): + """Converts a markdown file and returns the HTML as a unicode string. + + Decodes the file using the provided encoding (defaults to utf-8), + passes the file content to markdown, and outputs the html to either + the provided stream or the file with provided name, using the same + encoding as the source file. + + **Note:** This is the only place that decoding and encoding of unicode + takes place in Python-Markdown. (All other code is unicode-in / + unicode-out.) + + Keyword arguments: + + * input: Name of source text file. + * output: Name of output file. Writes to stdout if `None`. + * encoding: Encoding of input and output files. Defaults to utf-8. + + """ + + encoding = encoding or "utf-8" + + # Read the source + input_file = codecs.open(input, mode="r", encoding=encoding) + text = input_file.read() + input_file.close() + text = text.lstrip(u'\ufeff') # remove the byte-order mark + + # Convert + html = self.convert(text) + + # Write to file or stdout + if isinstance(output, (str, unicode)): + output_file = codecs.open(output, "w", encoding=encoding) + output_file.write(html) + output_file.close() + else: + output.write(html.encode(encoding)) + + +""" +Extensions +----------------------------------------------------------------------------- +""" + +class Extension: + """ Base class for extensions to subclass. """ + def __init__(self, configs = {}): + """Create an instance of an Extention. + + Keyword arguments: + + * configs: A dict of configuration setting used by an Extension. + """ + self.config = configs + + def getConfig(self, key): + """ Return a setting for the given key or an empty string. """ + if key in self.config: + return self.config[key][0] + else: + return "" + + def getConfigInfo(self): + """ Return all config settings as a list of tuples. """ + return [(key, self.config[key][1]) for key in self.config.keys()] + + def setConfig(self, key, value): + """ Set a config setting for `key` with the given `value`. """ + self.config[key][0] = value + + def extendMarkdown(self, md, md_globals): + """ + Add the various proccesors and patterns to the Markdown Instance. + + This method must be overriden by every extension. + + Keyword arguments: + + * md: The Markdown instance. + + * md_globals: Global variables in the markdown module namespace. + + """ + pass + + +def load_extension(ext_name, configs = []): + """Load extension by name, then return the module. + + The extension name may contain arguments as part of the string in the + following format: "extname(key1=value1,key2=value2)" + + """ + + # Parse extensions config params (ignore the order) + configs = dict(configs) + pos = ext_name.find("(") # find the first "(" + if pos > 0: + ext_args = ext_name[pos+1:-1] + ext_name = ext_name[:pos] + pairs = [x.split("=") for x in ext_args.split(",")] + configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) + + # Setup the module names + ext_module = 'markdown.extensions' + module_name_new_style = '.'.join([ext_module, ext_name]) + module_name_old_style = '_'.join(['mdx', ext_name]) + + # Try loading the extention first from one place, then another + try: # New style (markdown.extensons.<extension>) + module = __import__(module_name_new_style, {}, {}, [ext_module]) + except ImportError: + try: # Old style (mdx.<extension>) + module = __import__(module_name_old_style) + except ImportError: + message(WARN, "Failed loading extension '%s' from '%s' or '%s'" + % (ext_name, module_name_new_style, module_name_old_style)) + # Return None so we don't try to initiate none-existant extension + return None + + # If the module is loaded successfully, we expect it to define a + # function called makeExtension() + try: + return module.makeExtension(configs.items()) + except AttributeError: + message(CRITICAL, "Failed to initiate extension '%s'" % ext_name) + + +def load_extensions(ext_names): + """Loads multiple extensions""" + extensions = [] + for ext_name in ext_names: + extension = load_extension(ext_name) + if extension: + extensions.append(extension) + return extensions + + +""" +EXPORTED FUNCTIONS +============================================================================= + +Those are the two functions we really mean to export: markdown() and +markdownFromFile(). +""" + +def markdown(text, + extensions = [], + safe_mode = False, + output_format = DEFAULT_OUTPUT_FORMAT): + """Convert a markdown string to HTML and return HTML as a unicode string. + + This is a shortcut function for `Markdown` class to cover the most + basic use case. It initializes an instance of Markdown, loads the + necessary extensions and runs the parser on the given text. + + Keyword arguments: + + * text: Markdown formatted text as Unicode or ASCII string. + * extensions: A list of extensions or extension names (may contain config args). + * safe_mode: Disallow raw html. One of "remove", "replace" or "escape". + * output_format: Format of output. Supported formats are: + * "xhtml1": Outputs XHTML 1.x. Default. + * "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1). + * "html4": Outputs HTML 4 + * "html": Outputs latest supported version of HTML (currently HTML 4). + Note that it is suggested that the more specific formats ("xhtml1" + and "html4") be used as "xhtml" or "html" may change in the future + if it makes sense at that time. + + Returns: An HTML document as a string. + + """ + md = Markdown(extensions=load_extensions(extensions), + safe_mode=safe_mode, + output_format=output_format) + return md.convert(text) + + +def markdownFromFile(input = None, + output = None, + extensions = [], + encoding = None, + safe_mode = False, + output_format = DEFAULT_OUTPUT_FORMAT): + """Read markdown code from a file and write it to a file or a stream.""" + md = Markdown(extensions=load_extensions(extensions), + safe_mode=safe_mode, + output_format=output_format) + md.convertFile(input, output, encoding) + + + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/blockparser.py b/tools/addon-sdk-1.7/python-lib/markdown/blockparser.py new file mode 100644 index 0000000..e18b338 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/blockparser.py @@ -0,0 +1,95 @@ + +import markdown + +class State(list): + """ Track the current and nested state of the parser. + + This utility class is used to track the state of the BlockParser and + support multiple levels if nesting. It's just a simple API wrapped around + a list. Each time a state is set, that state is appended to the end of the + list. Each time a state is reset, that state is removed from the end of + the list. + + Therefore, each time a state is set for a nested block, that state must be + reset when we back out of that level of nesting or the state could be + corrupted. + + While all the methods of a list object are available, only the three + defined below need be used. + + """ + + def set(self, state): + """ Set a new state. """ + self.append(state) + + def reset(self): + """ Step back one step in nested state. """ + self.pop() + + def isstate(self, state): + """ Test that top (current) level is of given state. """ + if len(self): + return self[-1] == state + else: + return False + +class BlockParser: + """ Parse Markdown blocks into an ElementTree object. + + A wrapper class that stitches the various BlockProcessors together, + looping through them and creating an ElementTree object. + """ + + def __init__(self): + self.blockprocessors = markdown.odict.OrderedDict() + self.state = State() + + def parseDocument(self, lines): + """ Parse a markdown document into an ElementTree. + + Given a list of lines, an ElementTree object (not just a parent Element) + is created and the root element is passed to the parser as the parent. + The ElementTree object is returned. + + This should only be called on an entire document, not pieces. + + """ + # Create a ElementTree from the lines + self.root = markdown.etree.Element(markdown.DOC_TAG) + self.parseChunk(self.root, '\n'.join(lines)) + return markdown.etree.ElementTree(self.root) + + def parseChunk(self, parent, text): + """ Parse a chunk of markdown text and attach to given etree node. + + While the ``text`` argument is generally assumed to contain multiple + blocks which will be split on blank lines, it could contain only one + block. Generally, this method would be called by extensions when + block parsing is required. + + The ``parent`` etree Element passed in is altered in place. + Nothing is returned. + + """ + self.parseBlocks(parent, text.split('\n\n')) + + def parseBlocks(self, parent, blocks): + """ Process blocks of markdown text and attach to given etree node. + + Given a list of ``blocks``, each blockprocessor is stepped through + until there are no blocks left. While an extension could potentially + call this method directly, it's generally expected to be used internally. + + This is a public method as an extension may need to add/alter additional + BlockProcessors which call this method to recursively parse a nested + block. + + """ + while blocks: + for processor in self.blockprocessors.values(): + if processor.test(parent, blocks[0]): + processor.run(parent, blocks) + break + + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/blockprocessors.py b/tools/addon-sdk-1.7/python-lib/markdown/blockprocessors.py new file mode 100644 index 0000000..79f4db9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/blockprocessors.py @@ -0,0 +1,460 @@ +""" +CORE MARKDOWN BLOCKPARSER +============================================================================= + +This parser handles basic parsing of Markdown blocks. It doesn't concern itself +with inline elements such as **bold** or *italics*, but rather just catches +blocks, lists, quotes, etc. + +The BlockParser is made up of a bunch of BlockProssors, each handling a +different type of block. Extensions may add/replace/remove BlockProcessors +as they need to alter how markdown blocks are parsed. + +""" + +import re +import markdown + +class BlockProcessor: + """ Base class for block processors. + + Each subclass will provide the methods below to work with the source and + tree. Each processor will need to define it's own ``test`` and ``run`` + methods. The ``test`` method should return True or False, to indicate + whether the current block should be processed by this processor. If the + test passes, the parser will call the processors ``run`` method. + + """ + + def __init__(self, parser=None): + self.parser = parser + + def lastChild(self, parent): + """ Return the last child of an etree element. """ + if len(parent): + return parent[-1] + else: + return None + + def detab(self, text): + """ Remove a tab from the front of each line of the given text. """ + newtext = [] + lines = text.split('\n') + for line in lines: + if line.startswith(' '*markdown.TAB_LENGTH): + newtext.append(line[markdown.TAB_LENGTH:]) + elif not line.strip(): + newtext.append('') + else: + break + return '\n'.join(newtext), '\n'.join(lines[len(newtext):]) + + def looseDetab(self, text, level=1): + """ Remove a tab from front of lines but allowing dedented lines. """ + lines = text.split('\n') + for i in range(len(lines)): + if lines[i].startswith(' '*markdown.TAB_LENGTH*level): + lines[i] = lines[i][markdown.TAB_LENGTH*level:] + return '\n'.join(lines) + + def test(self, parent, block): + """ Test for block type. Must be overridden by subclasses. + + As the parser loops through processors, it will call the ``test`` method + on each to determine if the given block of text is of that type. This + method must return a boolean ``True`` or ``False``. The actual method of + testing is left to the needs of that particular block type. It could + be as simple as ``block.startswith(some_string)`` or a complex regular + expression. As the block type may be different depending on the parent + of the block (i.e. inside a list), the parent etree element is also + provided and may be used as part of the test. + + Keywords: + + * ``parent``: A etree element which will be the parent of the block. + * ``block``: A block of text from the source which has been split at + blank lines. + """ + pass + + def run(self, parent, blocks): + """ Run processor. Must be overridden by subclasses. + + When the parser determines the appropriate type of a block, the parser + will call the corresponding processor's ``run`` method. This method + should parse the individual lines of the block and append them to + the etree. + + Note that both the ``parent`` and ``etree`` keywords are pointers + to instances of the objects which should be edited in place. Each + processor must make changes to the existing objects as there is no + mechanism to return new/different objects to replace them. + + This means that this method should be adding SubElements or adding text + to the parent, and should remove (``pop``) or add (``insert``) items to + the list of blocks. + + Keywords: + + * ``parent``: A etree element which is the parent of the current block. + * ``blocks``: A list of all remaining blocks of the document. + """ + pass + + +class ListIndentProcessor(BlockProcessor): + """ Process children of list items. + + Example: + * a list item + process this part + + or this part + + """ + + INDENT_RE = re.compile(r'^(([ ]{%s})+)'% markdown.TAB_LENGTH) + ITEM_TYPES = ['li'] + LIST_TYPES = ['ul', 'ol'] + + def test(self, parent, block): + return block.startswith(' '*markdown.TAB_LENGTH) and \ + not self.parser.state.isstate('detabbed') and \ + (parent.tag in self.ITEM_TYPES or \ + (len(parent) and parent[-1] and \ + (parent[-1].tag in self.LIST_TYPES) + ) + ) + + def run(self, parent, blocks): + block = blocks.pop(0) + level, sibling = self.get_level(parent, block) + block = self.looseDetab(block, level) + + self.parser.state.set('detabbed') + if parent.tag in self.ITEM_TYPES: + # The parent is already a li. Just parse the child block. + self.parser.parseBlocks(parent, [block]) + elif sibling.tag in self.ITEM_TYPES: + # The sibling is a li. Use it as parent. + self.parser.parseBlocks(sibling, [block]) + elif len(sibling) and sibling[-1].tag in self.ITEM_TYPES: + # The parent is a list (``ol`` or ``ul``) which has children. + # Assume the last child li is the parent of this block. + if sibling[-1].text: + # If the parent li has text, that text needs to be moved to a p + block = '%s\n\n%s' % (sibling[-1].text, block) + sibling[-1].text = '' + self.parser.parseChunk(sibling[-1], block) + else: + self.create_item(sibling, block) + self.parser.state.reset() + + def create_item(self, parent, block): + """ Create a new li and parse the block with it as the parent. """ + li = markdown.etree.SubElement(parent, 'li') + self.parser.parseBlocks(li, [block]) + + def get_level(self, parent, block): + """ Get level of indent based on list level. """ + # Get indent level + m = self.INDENT_RE.match(block) + if m: + indent_level = len(m.group(1))/markdown.TAB_LENGTH + else: + indent_level = 0 + if self.parser.state.isstate('list'): + # We're in a tightlist - so we already are at correct parent. + level = 1 + else: + # We're in a looselist - so we need to find parent. + level = 0 + # Step through children of tree to find matching indent level. + while indent_level > level: + child = self.lastChild(parent) + if child and (child.tag in self.LIST_TYPES or child.tag in self.ITEM_TYPES): + if child.tag in self.LIST_TYPES: + level += 1 + parent = child + else: + # No more child levels. If we're short of indent_level, + # we have a code block. So we stop here. + break + return level, parent + + +class CodeBlockProcessor(BlockProcessor): + """ Process code blocks. """ + + def test(self, parent, block): + return block.startswith(' '*markdown.TAB_LENGTH) + + def run(self, parent, blocks): + sibling = self.lastChild(parent) + block = blocks.pop(0) + theRest = '' + if sibling and sibling.tag == "pre" and len(sibling) \ + and sibling[0].tag == "code": + # The previous block was a code block. As blank lines do not start + # new code blocks, append this block to the previous, adding back + # linebreaks removed from the split into a list. + code = sibling[0] + block, theRest = self.detab(block) + code.text = markdown.AtomicString('%s\n%s\n' % (code.text, block.rstrip())) + else: + # This is a new codeblock. Create the elements and insert text. + pre = markdown.etree.SubElement(parent, 'pre') + code = markdown.etree.SubElement(pre, 'code') + block, theRest = self.detab(block) + code.text = markdown.AtomicString('%s\n' % block.rstrip()) + if theRest: + # This block contained unindented line(s) after the first indented + # line. Insert these lines as the first block of the master blocks + # list for future processing. + blocks.insert(0, theRest) + + +class BlockQuoteProcessor(BlockProcessor): + + RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)') + + def test(self, parent, block): + return bool(self.RE.search(block)) + + def run(self, parent, blocks): + block = blocks.pop(0) + m = self.RE.search(block) + if m: + before = block[:m.start()] # Lines before blockquote + # Pass lines before blockquote in recursively for parsing forst. + self.parser.parseBlocks(parent, [before]) + # Remove ``> `` from begining of each line. + block = '\n'.join([self.clean(line) for line in + block[m.start():].split('\n')]) + sibling = self.lastChild(parent) + if sibling and sibling.tag == "blockquote": + # Previous block was a blockquote so set that as this blocks parent + quote = sibling + else: + # This is a new blockquote. Create a new parent element. + quote = markdown.etree.SubElement(parent, 'blockquote') + # Recursively parse block with blockquote as parent. + self.parser.parseChunk(quote, block) + + def clean(self, line): + """ Remove ``>`` from beginning of a line. """ + m = self.RE.match(line) + if line.strip() == ">": + return "" + elif m: + return m.group(2) + else: + return line + +class OListProcessor(BlockProcessor): + """ Process ordered list blocks. """ + + TAG = 'ol' + # Detect an item (``1. item``). ``group(1)`` contains contents of item. + RE = re.compile(r'^[ ]{0,3}\d+\.[ ](.*)') + # Detect items on secondary lines. they can be of either list type. + CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.)|[*+-])[ ](.*)') + # Detect indented (nested) items of either type + INDENT_RE = re.compile(r'^[ ]{4,7}((\d+\.)|[*+-])[ ].*') + + def test(self, parent, block): + return bool(self.RE.match(block)) + + def run(self, parent, blocks): + # Check fr multiple items in one block. + items = self.get_items(blocks.pop(0)) + sibling = self.lastChild(parent) + if sibling and sibling.tag in ['ol', 'ul']: + # Previous block was a list item, so set that as parent + lst = sibling + # make sure previous item is in a p. + if len(lst) and lst[-1].text and not len(lst[-1]): + p = markdown.etree.SubElement(lst[-1], 'p') + p.text = lst[-1].text + lst[-1].text = '' + # parse first block differently as it gets wrapped in a p. + li = markdown.etree.SubElement(lst, 'li') + self.parser.state.set('looselist') + firstitem = items.pop(0) + self.parser.parseBlocks(li, [firstitem]) + self.parser.state.reset() + else: + # This is a new list so create parent with appropriate tag. + lst = markdown.etree.SubElement(parent, self.TAG) + self.parser.state.set('list') + # Loop through items in block, recursively parsing each with the + # appropriate parent. + for item in items: + if item.startswith(' '*markdown.TAB_LENGTH): + # Item is indented. Parse with last item as parent + self.parser.parseBlocks(lst[-1], [item]) + else: + # New item. Create li and parse with it as parent + li = markdown.etree.SubElement(lst, 'li') + self.parser.parseBlocks(li, [item]) + self.parser.state.reset() + + def get_items(self, block): + """ Break a block into list items. """ + items = [] + for line in block.split('\n'): + m = self.CHILD_RE.match(line) + if m: + # This is a new item. Append + items.append(m.group(3)) + elif self.INDENT_RE.match(line): + # This is an indented (possibly nested) item. + if items[-1].startswith(' '*markdown.TAB_LENGTH): + # Previous item was indented. Append to that item. + items[-1] = '%s\n%s' % (items[-1], line) + else: + items.append(line) + else: + # This is another line of previous item. Append to that item. + items[-1] = '%s\n%s' % (items[-1], line) + return items + + +class UListProcessor(OListProcessor): + """ Process unordered list blocks. """ + + TAG = 'ul' + RE = re.compile(r'^[ ]{0,3}[*+-][ ](.*)') + + +class HashHeaderProcessor(BlockProcessor): + """ Process Hash Headers. """ + + # Detect a header at start of any line in block + RE = re.compile(r'(^|\n)(?P<level>#{1,6})(?P<header>.*?)#*(\n|$)') + + def test(self, parent, block): + return bool(self.RE.search(block)) + + def run(self, parent, blocks): + block = blocks.pop(0) + m = self.RE.search(block) + if m: + before = block[:m.start()] # All lines before header + after = block[m.end():] # All lines after header + if before: + # As the header was not the first line of the block and the + # lines before the header must be parsed first, + # recursively parse this lines as a block. + self.parser.parseBlocks(parent, [before]) + # Create header using named groups from RE + h = markdown.etree.SubElement(parent, 'h%d' % len(m.group('level'))) + h.text = m.group('header').strip() + if after: + # Insert remaining lines as first block for future parsing. + blocks.insert(0, after) + else: + # This should never happen, but just in case... + message(CRITICAL, "We've got a problem header!") + + +class SetextHeaderProcessor(BlockProcessor): + """ Process Setext-style Headers. """ + + # Detect Setext-style header. Must be first 2 lines of block. + RE = re.compile(r'^.*?\n[=-]{3,}', re.MULTILINE) + + def test(self, parent, block): + return bool(self.RE.match(block)) + + def run(self, parent, blocks): + lines = blocks.pop(0).split('\n') + # Determine level. ``=`` is 1 and ``-`` is 2. + if lines[1].startswith('='): + level = 1 + else: + level = 2 + h = markdown.etree.SubElement(parent, 'h%d' % level) + h.text = lines[0].strip() + if len(lines) > 2: + # Block contains additional lines. Add to master blocks for later. + blocks.insert(0, '\n'.join(lines[2:])) + + +class HRProcessor(BlockProcessor): + """ Process Horizontal Rules. """ + + RE = r'[ ]{0,3}(?P<ch>[*_-])[ ]?((?P=ch)[ ]?){2,}[ ]*' + # Detect hr on any line of a block. + SEARCH_RE = re.compile(r'(^|\n)%s(\n|$)' % RE) + # Match a hr on a single line of text. + MATCH_RE = re.compile(r'^%s$' % RE) + + def test(self, parent, block): + return bool(self.SEARCH_RE.search(block)) + + def run(self, parent, blocks): + lines = blocks.pop(0).split('\n') + prelines = [] + # Check for lines in block before hr. + for line in lines: + m = self.MATCH_RE.match(line) + if m: + break + else: + prelines.append(line) + if len(prelines): + # Recursively parse lines before hr so they get parsed first. + self.parser.parseBlocks(parent, ['\n'.join(prelines)]) + # create hr + hr = markdown.etree.SubElement(parent, 'hr') + # check for lines in block after hr. + lines = lines[len(prelines)+1:] + if len(lines): + # Add lines after hr to master blocks for later parsing. + blocks.insert(0, '\n'.join(lines)) + + +class EmptyBlockProcessor(BlockProcessor): + """ Process blocks and start with an empty line. """ + + # Detect a block that only contains whitespace + # or only whitespace on the first line. + RE = re.compile(r'^\s*\n') + + def test(self, parent, block): + return bool(self.RE.match(block)) + + def run(self, parent, blocks): + block = blocks.pop(0) + m = self.RE.match(block) + if m: + # Add remaining line to master blocks for later. + blocks.insert(0, block[m.end():]) + sibling = self.lastChild(parent) + if sibling and sibling.tag == 'pre' and sibling[0] and \ + sibling[0].tag == 'code': + # Last block is a codeblock. Append to preserve whitespace. + sibling[0].text = markdown.AtomicString('%s/n/n/n' % sibling[0].text ) + + +class ParagraphProcessor(BlockProcessor): + """ Process Paragraph blocks. """ + + def test(self, parent, block): + return True + + def run(self, parent, blocks): + block = blocks.pop(0) + if block.strip(): + # Not a blank block. Add to parent, otherwise throw it away. + if self.parser.state.isstate('list'): + # The parent is a tight-list. Append to parent.text + if parent.text: + parent.text = '%s\n%s' % (parent.text, block) + else: + parent.text = block.lstrip() + else: + # Create a regular paragraph + p = markdown.etree.SubElement(parent, 'p') + p.text = block.lstrip() diff --git a/tools/addon-sdk-1.7/python-lib/markdown/commandline.py b/tools/addon-sdk-1.7/python-lib/markdown/commandline.py new file mode 100644 index 0000000..1eedc6d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/commandline.py @@ -0,0 +1,96 @@ +""" +COMMAND-LINE SPECIFIC STUFF +============================================================================= + +The rest of the code is specifically for handling the case where Python +Markdown is called from the command line. +""" + +import markdown +import sys +import logging +from logging import DEBUG, INFO, WARN, ERROR, CRITICAL + +EXECUTABLE_NAME_FOR_USAGE = "python markdown.py" +""" The name used in the usage statement displayed for python versions < 2.3. +(With python 2.3 and higher the usage statement is generated by optparse +and uses the actual name of the executable called.) """ + +OPTPARSE_WARNING = """ +Python 2.3 or higher required for advanced command line options. +For lower versions of Python use: + + %s INPUT_FILE > OUTPUT_FILE + +""" % EXECUTABLE_NAME_FOR_USAGE + +def parse_options(): + """ + Define and parse `optparse` options for command-line usage. + """ + + try: + optparse = __import__("optparse") + except: + if len(sys.argv) == 2: + return {'input': sys.argv[1], + 'output': None, + 'safe': False, + 'extensions': [], + 'encoding': None }, CRITICAL + else: + print OPTPARSE_WARNING + return None, None + + parser = optparse.OptionParser(usage="%prog INPUTFILE [options]") + parser.add_option("-f", "--file", dest="filename", default=sys.stdout, + help="write output to OUTPUT_FILE", + metavar="OUTPUT_FILE") + parser.add_option("-e", "--encoding", dest="encoding", + help="encoding for input and output files",) + parser.add_option("-q", "--quiet", default = CRITICAL, + action="store_const", const=CRITICAL+10, dest="verbose", + help="suppress all messages") + parser.add_option("-v", "--verbose", + action="store_const", const=INFO, dest="verbose", + help="print info messages") + parser.add_option("-s", "--safe", dest="safe", default=False, + metavar="SAFE_MODE", + help="safe mode ('replace', 'remove' or 'escape' user's HTML tag)") + parser.add_option("-o", "--output_format", dest="output_format", + default='xhtml1', metavar="OUTPUT_FORMAT", + help="Format of output. One of 'xhtml1' (default) or 'html4'.") + parser.add_option("--noisy", + action="store_const", const=DEBUG, dest="verbose", + help="print debug messages") + parser.add_option("-x", "--extension", action="append", dest="extensions", + help = "load extension EXTENSION", metavar="EXTENSION") + + (options, args) = parser.parse_args() + + if not len(args) == 1: + parser.print_help() + return None, None + else: + input_file = args[0] + + if not options.extensions: + options.extensions = [] + + return {'input': input_file, + 'output': options.filename, + 'safe_mode': options.safe, + 'extensions': options.extensions, + 'encoding': options.encoding, + 'output_format': options.output_format}, options.verbose + +def run(): + """Run Markdown from the command line.""" + + # Parse options and adjust logging level if necessary + options, logging_level = parse_options() + if not options: sys.exit(0) + if logging_level: logging.getLogger('MARKDOWN').setLevel(logging_level) + + # Run + markdown.markdownFromFile(**options) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/etree_loader.py b/tools/addon-sdk-1.7/python-lib/markdown/etree_loader.py new file mode 100644 index 0000000..e2599b2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/etree_loader.py @@ -0,0 +1,33 @@ + +from markdown import message, CRITICAL +import sys + +## Import +def importETree(): + """Import the best implementation of ElementTree, return a module object.""" + etree_in_c = None + try: # Is it Python 2.5+ with C implemenation of ElementTree installed? + import xml.etree.cElementTree as etree_in_c + except ImportError: + try: # Is it Python 2.5+ with Python implementation of ElementTree? + import xml.etree.ElementTree as etree + except ImportError: + try: # An earlier version of Python with cElementTree installed? + import cElementTree as etree_in_c + except ImportError: + try: # An earlier version of Python with Python ElementTree? + import elementtree.ElementTree as etree + except ImportError: + message(CRITICAL, "Failed to import ElementTree") + sys.exit(1) + if etree_in_c and etree_in_c.VERSION < "1.0": + message(CRITICAL, "For cElementTree version 1.0 or higher is required.") + sys.exit(1) + elif etree_in_c : + return etree_in_c + elif etree.VERSION < "1.1": + message(CRITICAL, "For ElementTree version 1.1 or higher is required") + sys.exit(1) + else : + return etree + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/__init__.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/__init__.py diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/abbr.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/abbr.py new file mode 100644 index 0000000..783220e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/abbr.py @@ -0,0 +1,95 @@ +''' +Abbreviation Extension for Python-Markdown +========================================== + +This extension adds abbreviation handling to Python-Markdown. + +Simple Usage: + + >>> import markdown + >>> text = """ + ... Some text with an ABBR and a REF. Ignore REFERENCE and ref. + ... + ... *[ABBR]: Abbreviation + ... *[REF]: Abbreviation Reference + ... """ + >>> markdown.markdown(text, ['abbr']) + u'<p>Some text with an <abbr title="Abbreviation">ABBR</abbr> and a <abbr title="Abbreviation Reference">REF</abbr>. Ignore REFERENCE and ref.</p>' + +Copyright 2007-2008 +* [Waylan Limberg](http://achinghead.com/) +* [Seemant Kulleen](http://www.kulleen.org/) + + +''' + +import markdown, re +from markdown import etree + +# Global Vars +ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)') + +class AbbrExtension(markdown.Extension): + """ Abbreviation Extension for Python-Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Insert AbbrPreprocessor before ReferencePreprocessor. """ + md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference') + + +class AbbrPreprocessor(markdown.preprocessors.Preprocessor): + """ Abbreviation Preprocessor - parse text for abbr references. """ + + def run(self, lines): + ''' + Find and remove all Abbreviation references from the text. + Each reference is set as a new AbbrPattern in the markdown instance. + + ''' + new_text = [] + for line in lines: + m = ABBR_REF_RE.match(line) + if m: + abbr = m.group('abbr').strip() + title = m.group('title').strip() + self.markdown.inlinePatterns['abbr-%s'%abbr] = \ + AbbrPattern(self._generate_pattern(abbr), title) + else: + new_text.append(line) + return new_text + + def _generate_pattern(self, text): + ''' + Given a string, returns an regex pattern to match that string. + + 'HTML' -> r'(?P<abbr>[H][T][M][L])' + + Note: we force each char as a literal match (in brackets) as we don't + know what they will be beforehand. + + ''' + chars = list(text) + for i in range(len(chars)): + chars[i] = r'[%s]' % chars[i] + return r'(?P<abbr>\b%s\b)' % (r''.join(chars)) + + +class AbbrPattern(markdown.inlinepatterns.Pattern): + """ Abbreviation inline pattern. """ + + def __init__(self, pattern, title): + markdown.inlinepatterns.Pattern.__init__(self, pattern) + self.title = title + + def handleMatch(self, m): + abbr = etree.Element('abbr') + abbr.text = m.group('abbr') + abbr.set('title', self.title) + return abbr + +def makeExtension(configs=None): + return AbbrExtension(configs=configs) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/codehilite.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/codehilite.py new file mode 100644 index 0000000..c5d496b --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/codehilite.py @@ -0,0 +1,224 @@ +#!/usr/bin/python + +""" +CodeHilite Extension for Python-Markdown +======================================== + +Adds code/syntax highlighting to standard Python-Markdown code blocks. + +Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). + +Project website: <http://www.freewisdom.org/project/python-markdown/CodeHilite> +Contact: markdown@freewisdom.org + +License: BSD (see ../docs/LICENSE for details) + +Dependencies: +* [Python 2.3+](http://python.org/) +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) +* [Pygments](http://pygments.org/) + +""" + +import markdown + +# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY ----------------- + +try: + TAB_LENGTH = markdown.TAB_LENGTH +except AttributeError: + TAB_LENGTH = 4 + + +# ------------------ The Main CodeHilite Class ---------------------- +class CodeHilite: + """ + Determine language of source code, and pass it into the pygments hilighter. + + Basic Usage: + >>> code = CodeHilite(src = 'some text') + >>> html = code.hilite() + + * src: Source string or any object with a .readline attribute. + + * linenos: (Boolen) Turn line numbering 'on' or 'off' (off by default). + + * css_class: Set class name of wrapper div ('codehilite' by default). + + Low Level Usage: + >>> code = CodeHilite() + >>> code.src = 'some text' # String or anything with a .readline attr. + >>> code.linenos = True # True or False; Turns line numbering on or of. + >>> html = code.hilite() + + """ + + def __init__(self, src=None, linenos=False, css_class="codehilite"): + self.src = src + self.lang = None + self.linenos = linenos + self.css_class = css_class + + def hilite(self): + """ + Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with + optional line numbers. The output should then be styled with css to + your liking. No styles are applied by default - only styling hooks + (i.e.: <span class="k">). + + returns : A string of html. + + """ + + self.src = self.src.strip('\n') + + self._getLang() + + try: + from pygments import highlight + from pygments.lexers import get_lexer_by_name, guess_lexer, \ + TextLexer + from pygments.formatters import HtmlFormatter + except ImportError: + # just escape and pass through + txt = self._escape(self.src) + if self.linenos: + txt = self._number(txt) + else : + txt = '<div class="%s"><pre>%s</pre></div>\n'% \ + (self.css_class, txt) + return txt + else: + try: + lexer = get_lexer_by_name(self.lang) + except ValueError: + try: + lexer = guess_lexer(self.src) + except ValueError: + lexer = TextLexer() + formatter = HtmlFormatter(linenos=self.linenos, + cssclass=self.css_class) + return highlight(self.src, lexer, formatter) + + def _escape(self, txt): + """ basic html escaping """ + txt = txt.replace('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + + def _number(self, txt): + """ Use <ol> for line numbering """ + # Fix Whitespace + txt = txt.replace('\t', ' '*TAB_LENGTH) + txt = txt.replace(" "*4, " ") + txt = txt.replace(" "*3, " ") + txt = txt.replace(" "*2, " ") + + # Add line numbers + lines = txt.splitlines() + txt = '<div class="codehilite"><pre><ol>\n' + for line in lines: + txt += '\t<li>%s</li>\n'% line + txt += '</ol></pre></div>\n' + return txt + + + def _getLang(self): + """ + Determines language of a code block from shebang lines and whether said + line should be removed or left in place. If the sheband line contains a + path (even a single /) then it is assumed to be a real shebang lines and + left alone. However, if no path is given (e.i.: #!python or :::python) + then it is assumed to be a mock shebang for language identifitation of a + code fragment and removed from the code block prior to processing for + code highlighting. When a mock shebang (e.i: #!python) is found, line + numbering is turned on. When colons are found in place of a shebang + (e.i.: :::python), line numbering is left in the current state - off + by default. + + """ + + import re + + #split text into lines + lines = self.src.split("\n") + #pull first line to examine + fl = lines.pop(0) + + c = re.compile(r''' + (?:(?:::+)|(?P<shebang>[#]!)) # Shebang or 2 or more colons. + (?P<path>(?:/\w+)*[/ ])? # Zero or 1 path + (?P<lang>[\w+-]*) # The language + ''', re.VERBOSE) + # search first line for shebang + m = c.search(fl) + if m: + # we have a match + try: + self.lang = m.group('lang').lower() + except IndexError: + self.lang = None + if m.group('path'): + # path exists - restore first line + lines.insert(0, fl) + if m.group('shebang'): + # shebang exists - use line numbers + self.linenos = True + else: + # No match + lines.insert(0, fl) + + self.src = "\n".join(lines).strip("\n") + + + +# ------------------ The Markdown Extension ------------------------------- +class HiliteTreeprocessor(markdown.treeprocessors.Treeprocessor): + """ Hilight source code in code blocks. """ + + def run(self, root): + """ Find code blocks and store in htmlStash. """ + blocks = root.getiterator('pre') + for block in blocks: + children = block.getchildren() + if len(children) == 1 and children[0].tag == 'code': + code = CodeHilite(children[0].text, + linenos=self.config['force_linenos'][0], + css_class=self.config['css_class'][0]) + placeholder = self.markdown.htmlStash.store(code.hilite(), + safe=True) + # Clear codeblock in etree instance + block.clear() + # Change to p element which will later + # be removed when inserting raw html + block.tag = 'p' + block.text = placeholder + + +class CodeHiliteExtension(markdown.Extension): + """ Add source code hilighting to markdown codeblocks. """ + + def __init__(self, configs): + # define default configs + self.config = { + 'force_linenos' : [False, "Force line numbers - Default: False"], + 'css_class' : ["codehilite", + "Set class name for wrapper <div> - Default: codehilite"], + } + + # Override defaults with user settings + for key, value in configs: + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + """ Add HilitePostprocessor to Markdown instance. """ + hiliter = HiliteTreeprocessor(md) + hiliter.config = self.config + md.treeprocessors.add("hilite", hiliter, "_begin") + + +def makeExtension(configs={}): + return CodeHiliteExtension(configs=configs) + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/def_list.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/def_list.py new file mode 100644 index 0000000..73a1c85 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/def_list.py @@ -0,0 +1,104 @@ +#!/usr/bin/env Python +""" +Definition List Extension for Python-Markdown +============================================= + +Added parsing of Definition Lists to Python-Markdown. + +A simple example: + + Apple + : Pomaceous fruit of plants of the genus Malus in + the family Rosaceae. + : An american computer company. + + Orange + : The fruit of an evergreen tree of the genus Citrus. + +Copyright 2008 - [Waylan Limberg](http://achinghead.com) + +""" + +import markdown, re +from markdown import etree + + +class DefListProcessor(markdown.blockprocessors.BlockProcessor): + """ Process Definition Lists. """ + + RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)') + + def test(self, parent, block): + return bool(self.RE.search(block)) + + def run(self, parent, blocks): + block = blocks.pop(0) + m = self.RE.search(block) + terms = [l.strip() for l in block[:m.start()].split('\n') if l.strip()] + d, theRest = self.detab(block[m.end():]) + if d: + d = '%s\n%s' % (m.group(2), d) + else: + d = m.group(2) + #import ipdb; ipdb.set_trace() + sibling = self.lastChild(parent) + if not terms and sibling.tag == 'p': + # The previous paragraph contains the terms + state = 'looselist' + terms = sibling.text.split('\n') + parent.remove(sibling) + # Aquire new sibling + sibling = self.lastChild(parent) + else: + state = 'list' + + if sibling and sibling.tag == 'dl': + # This is another item on an existing list + dl = sibling + if len(dl) and dl[-1].tag == 'dd' and len(dl[-1]): + state = 'looselist' + else: + # This is a new list + dl = etree.SubElement(parent, 'dl') + # Add terms + for term in terms: + dt = etree.SubElement(dl, 'dt') + dt.text = term + # Add definition + self.parser.state.set(state) + dd = etree.SubElement(dl, 'dd') + self.parser.parseBlocks(dd, [d]) + self.parser.state.reset() + + if theRest: + blocks.insert(0, theRest) + +class DefListIndentProcessor(markdown.blockprocessors.ListIndentProcessor): + """ Process indented children of definition list items. """ + + ITEM_TYPES = ['dd'] + LIST_TYPES = ['dl'] + + def create_item(parent, block): + """ Create a new dd and parse the block with it as the parent. """ + dd = markdown.etree.SubElement(parent, 'dd') + self.parser.parseBlocks(dd, [block]) + + + +class DefListExtension(markdown.Extension): + """ Add definition lists to Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Add an instance of DefListProcessor to BlockParser. """ + md.parser.blockprocessors.add('defindent', + DefListIndentProcessor(md.parser), + '>indent') + md.parser.blockprocessors.add('deflist', + DefListProcessor(md.parser), + '>ulist') + + +def makeExtension(configs={}): + return DefListExtension(configs=configs) + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/extra.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/extra.py new file mode 100644 index 0000000..4a2ffbf --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/extra.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +""" +Python-Markdown Extra Extension +=============================== + +A compilation of various Python-Markdown extensions that imitates +[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/). + +Note that each of the individual extensions still need to be available +on your PYTHONPATH. This extension simply wraps them all up as a +convenience so that only one extension needs to be listed when +initiating Markdown. See the documentation for each individual +extension for specifics about that extension. + +In the event that one or more of the supported extensions are not +available for import, Markdown will issue a warning and simply continue +without that extension. + +There may be additional extensions that are distributed with +Python-Markdown that are not included here in Extra. Those extensions +are not part of PHP Markdown Extra, and therefore, not part of +Python-Markdown Extra. If you really would like Extra to include +additional extensions, we suggest creating your own clone of Extra +under a differant name. You could also edit the `extensions` global +variable defined below, but be aware that such changes may be lost +when you upgrade to any future version of Python-Markdown. + +""" + +import markdown + +extensions = ['fenced_code', + 'footnotes', + 'headerid', + 'def_list', + 'tables', + 'abbr', + ] + + +class ExtraExtension(markdown.Extension): + """ Add various extensions to Markdown class.""" + + def extendMarkdown(self, md, md_globals): + """ Register extension instances. """ + md.registerExtensions(extensions, self.config) + +def makeExtension(configs={}): + return ExtraExtension(configs=dict(configs)) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/fenced_code.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/fenced_code.py new file mode 100644 index 0000000..307b1dc --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/fenced_code.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +""" +Fenced Code Extension for Python Markdown +========================================= + +This extension adds Fenced Code Blocks to Python-Markdown. + + >>> import markdown + >>> text = ''' + ... A paragraph before a fenced code block: + ... + ... ~~~ + ... Fenced code block + ... ~~~ + ... ''' + >>> html = markdown.markdown(text, extensions=['fenced_code']) + >>> html + u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>' + +Works with safe_mode also (we check this because we are using the HtmlStash): + + >>> markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace') + u'<p>A paragraph before a fenced code block:</p>\\n<pre><code>Fenced code block\\n</code></pre>' + +Include tilde's in a code block and wrap with blank lines: + + >>> text = ''' + ... ~~~~~~~~ + ... + ... ~~~~ + ... + ... ~~~~~~~~''' + >>> markdown.markdown(text, extensions=['fenced_code']) + u'<pre><code>\\n~~~~\\n\\n</code></pre>' + +Multiple blocks and language tags: + + >>> text = ''' + ... ~~~~{.python} + ... block one + ... ~~~~ + ... + ... ~~~~.html + ... <p>block two</p> + ... ~~~~''' + >>> markdown.markdown(text, extensions=['fenced_code']) + u'<pre><code class="python">block one\\n</code></pre>\\n\\n<pre><code class="html"><p>block two</p>\\n</code></pre>' + +Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/). + +Project website: <http://www.freewisdom.org/project/python-markdown/Fenced__Code__Blocks> +Contact: markdown@freewisdom.org + +License: BSD (see ../docs/LICENSE for details) + +Dependencies: +* [Python 2.3+](http://python.org) +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) + +""" + +import markdown, re + +# Global vars +FENCED_BLOCK_RE = re.compile( \ + r'(?P<fence>^~{3,})[ ]*(\{?\.(?P<lang>[a-zA-Z0-9_-]*)\}?)?[ ]*\n(?P<code>.*?)(?P=fence)[ ]*$', + re.MULTILINE|re.DOTALL + ) +CODE_WRAP = '<pre><code%s>%s</code></pre>' +LANG_TAG = ' class="%s"' + + +class FencedCodeExtension(markdown.Extension): + + def extendMarkdown(self, md, md_globals): + """ Add FencedBlockPreprocessor to the Markdown instance. """ + + md.preprocessors.add('fenced_code_block', + FencedBlockPreprocessor(md), + "_begin") + + +class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor): + + def run(self, lines): + """ Match and store Fenced Code Blocks in the HtmlStash. """ + text = "\n".join(lines) + while 1: + m = FENCED_BLOCK_RE.search(text) + if m: + lang = '' + if m.group('lang'): + lang = LANG_TAG % m.group('lang') + code = CODE_WRAP % (lang, self._escape(m.group('code'))) + placeholder = self.markdown.htmlStash.store(code, safe=True) + text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():]) + else: + break + return text.split("\n") + + def _escape(self, txt): + """ basic html escaping """ + txt = txt.replace('&', '&') + txt = txt.replace('<', '<') + txt = txt.replace('>', '>') + txt = txt.replace('"', '"') + return txt + + +def makeExtension(configs=None): + return FencedCodeExtension() + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/footnotes.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/footnotes.py new file mode 100644 index 0000000..6dacab7 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/footnotes.py @@ -0,0 +1,293 @@ +""" +========================= FOOTNOTES ================================= + +This section adds footnote handling to markdown. It can be used as +an example for extending python-markdown with relatively complex +functionality. While in this case the extension is included inside +the module itself, it could just as easily be added from outside the +module. Not that all markdown classes above are ignorant about +footnotes. All footnote functionality is provided separately and +then added to the markdown instance at the run time. + +Footnote functionality is attached by calling extendMarkdown() +method of FootnoteExtension. The method also registers the +extension to allow it's state to be reset by a call to reset() +method. + +Example: + Footnotes[^1] have a label[^label] and a definition[^!DEF]. + + [^1]: This is a footnote + [^label]: A footnote on "label" + [^!DEF]: The footnote for definition + +""" + +import re, markdown +from markdown import etree + +FN_BACKLINK_TEXT = "zz1337820767766393qq" +NBSP_PLACEHOLDER = "qq3936677670287331zz" +DEF_RE = re.compile(r'(\ ?\ ?\ ?)\[\^([^\]]*)\]:\s*(.*)') +TABBED_RE = re.compile(r'((\t)|( ))(.*)') + +class FootnoteExtension(markdown.Extension): + """ Footnote Extension. """ + + def __init__ (self, configs): + """ Setup configs. """ + self.config = {'PLACE_MARKER': + ["///Footnotes Go Here///", + "The text string that marks where the footnotes go"]} + + for key, value in configs: + self.config[key][0] = value + + self.reset() + + def extendMarkdown(self, md, md_globals): + """ Add pieces to Markdown. """ + md.registerExtension(self) + self.parser = md.parser + # Insert a preprocessor before ReferencePreprocessor + md.preprocessors.add("footnote", FootnotePreprocessor(self), + "<reference") + # Insert an inline pattern before ImageReferencePattern + FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah + md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self), + "<reference") + # Insert a tree-processor that would actually add the footnote div + # This must be before the inline treeprocessor so inline patterns + # run on the contents of the div. + md.treeprocessors.add("footnote", FootnoteTreeprocessor(self), + "<inline") + # Insert a postprocessor after amp_substitute oricessor + md.postprocessors.add("footnote", FootnotePostprocessor(self), + ">amp_substitute") + + def reset(self): + """ Clear the footnotes on reset. """ + self.footnotes = markdown.odict.OrderedDict() + + def findFootnotesPlaceholder(self, root): + """ Return ElementTree Element that contains Footnote placeholder. """ + def finder(element): + for child in element: + if child.text: + if child.text.find(self.getConfig("PLACE_MARKER")) > -1: + return child, True + if child.tail: + if child.tail.find(self.getConfig("PLACE_MARKER")) > -1: + return (child, element), False + finder(child) + return None + + res = finder(root) + return res + + def setFootnote(self, id, text): + """ Store a footnote for later retrieval. """ + self.footnotes[id] = text + + def makeFootnoteId(self, id): + """ Return footnote link id. """ + return 'fn:%s' % id + + def makeFootnoteRefId(self, id): + """ Return footnote back-link id. """ + return 'fnref:%s' % id + + def makeFootnotesDiv(self, root): + """ Return div of footnotes as et Element. """ + + if not self.footnotes.keys(): + return None + + div = etree.Element("div") + div.set('class', 'footnote') + hr = etree.SubElement(div, "hr") + ol = etree.SubElement(div, "ol") + + for id in self.footnotes.keys(): + li = etree.SubElement(ol, "li") + li.set("id", self.makeFootnoteId(id)) + self.parser.parseChunk(li, self.footnotes[id]) + backlink = etree.Element("a") + backlink.set("href", "#" + self.makeFootnoteRefId(id)) + backlink.set("rev", "footnote") + backlink.set("title", "Jump back to footnote %d in the text" % \ + (self.footnotes.index(id)+1)) + backlink.text = FN_BACKLINK_TEXT + + if li.getchildren(): + node = li[-1] + if node.tag == "p": + node.text = node.text + NBSP_PLACEHOLDER + node.append(backlink) + else: + p = etree.SubElement(li, "p") + p.append(backlink) + return div + + +class FootnotePreprocessor(markdown.preprocessors.Preprocessor): + """ Find all footnote references and store for later use. """ + + def __init__ (self, footnotes): + self.footnotes = footnotes + + def run(self, lines): + lines = self._handleFootnoteDefinitions(lines) + text = "\n".join(lines) + return text.split("\n") + + def _handleFootnoteDefinitions(self, lines): + """ + Recursively find all footnote definitions in lines. + + Keywords: + + * lines: A list of lines of text + + Return: A list of lines with footnote definitions removed. + + """ + i, id, footnote = self._findFootnoteDefinition(lines) + + if id : + plain = lines[:i] + detabbed, theRest = self.detectTabbed(lines[i+1:]) + self.footnotes.setFootnote(id, + footnote + "\n" + + "\n".join(detabbed)) + more_plain = self._handleFootnoteDefinitions(theRest) + return plain + [""] + more_plain + else : + return lines + + def _findFootnoteDefinition(self, lines): + """ + Find the parts of a footnote definition. + + Keywords: + + * lines: A list of lines of text. + + Return: A three item tuple containing the index of the first line of a + footnote definition, the id of the definition and the body of the + definition. + + """ + counter = 0 + for line in lines: + m = DEF_RE.match(line) + if m: + return counter, m.group(2), m.group(3) + counter += 1 + return counter, None, None + + def detectTabbed(self, lines): + """ Find indented text and remove indent before further proccesing. + + Keyword arguments: + + * lines: an array of strings + + Returns: a list of post processed items and the unused + remainder of the original list + + """ + items = [] + item = -1 + i = 0 # to keep track of where we are + + def detab(line): + match = TABBED_RE.match(line) + if match: + return match.group(4) + + for line in lines: + if line.strip(): # Non-blank line + line = detab(line) + if line: + items.append(line) + i += 1 + continue + else: + return items, lines[i:] + + else: # Blank line: _maybe_ we are done. + i += 1 # advance + + # Find the next non-blank line + for j in range(i, len(lines)): + if lines[j].strip(): + next_line = lines[j]; break + else: + break # There is no more text; we are done. + + # Check if the next non-blank line is tabbed + if detab(next_line): # Yes, more work to do. + items.append("") + continue + else: + break # No, we are done. + else: + i += 1 + + return items, lines[i:] + + +class FootnotePattern(markdown.inlinepatterns.Pattern): + """ InlinePattern for footnote markers in a document's body text. """ + + def __init__(self, pattern, footnotes): + markdown.inlinepatterns.Pattern.__init__(self, pattern) + self.footnotes = footnotes + + def handleMatch(self, m): + sup = etree.Element("sup") + a = etree.SubElement(sup, "a") + id = m.group(2) + sup.set('id', self.footnotes.makeFootnoteRefId(id)) + a.set('href', '#' + self.footnotes.makeFootnoteId(id)) + a.set('rel', 'footnote') + a.text = str(self.footnotes.footnotes.index(id) + 1) + return sup + + +class FootnoteTreeprocessor(markdown.treeprocessors.Treeprocessor): + """ Build and append footnote div to end of document. """ + + def __init__ (self, footnotes): + self.footnotes = footnotes + + def run(self, root): + footnotesDiv = self.footnotes.makeFootnotesDiv(root) + if footnotesDiv: + result = self.footnotes.findFootnotesPlaceholder(root) + if result: + node, isText = result + if isText: + node.text = None + node.getchildren().insert(0, footnotesDiv) + else: + child, element = node + ind = element.getchildren().find(child) + element.getchildren().insert(ind + 1, footnotesDiv) + child.tail = None + fnPlaceholder.parent.replaceChild(fnPlaceholder, footnotesDiv) + else: + root.append(footnotesDiv) + +class FootnotePostprocessor(markdown.postprocessors.Postprocessor): + """ Replace placeholders with html entities. """ + + def run(self, text): + text = text.replace(FN_BACKLINK_TEXT, "↩") + return text.replace(NBSP_PLACEHOLDER, " ") + +def makeExtension(configs=[]): + """ Return an instance of the FootnoteExtension """ + return FootnoteExtension(configs=configs) + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/headerid.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/headerid.py new file mode 100644 index 0000000..f70a7a9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/headerid.py @@ -0,0 +1,195 @@ +#!/usr/bin/python + +""" +HeaderID Extension for Python-Markdown +====================================== + +Adds ability to set HTML IDs for headers. + +Basic usage: + + >>> import markdown + >>> text = "# Some Header # {#some_id}" + >>> md = markdown.markdown(text, ['headerid']) + >>> md + u'<h1 id="some_id">Some Header</h1>' + +All header IDs are unique: + + >>> text = ''' + ... #Header + ... #Another Header {#header} + ... #Third Header {#header}''' + >>> md = markdown.markdown(text, ['headerid']) + >>> md + u'<h1 id="header">Header</h1>\\n<h1 id="header_1">Another Header</h1>\\n<h1 id="header_2">Third Header</h1>' + +To fit within a html template's hierarchy, set the header base level: + + >>> text = ''' + ... #Some Header + ... ## Next Level''' + >>> md = markdown.markdown(text, ['headerid(level=3)']) + >>> md + u'<h3 id="some_header">Some Header</h3>\\n<h4 id="next_level">Next Level</h4>' + +Turn off auto generated IDs: + + >>> text = ''' + ... # Some Header + ... # Header with ID # { #foo }''' + >>> md = markdown.markdown(text, ['headerid(forceid=False)']) + >>> md + u'<h1>Some Header</h1>\\n<h1 id="foo">Header with ID</h1>' + +Use with MetaData extension: + + >>> text = '''header_level: 2 + ... header_forceid: Off + ... + ... # A Header''' + >>> md = markdown.markdown(text, ['headerid', 'meta']) + >>> md + u'<h2>A Header</h2>' + +Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/). + +Project website: <http://www.freewisdom.org/project/python-markdown/HeaderId> +Contact: markdown@freewisdom.org + +License: BSD (see ../docs/LICENSE for details) + +Dependencies: +* [Python 2.3+](http://python.org) +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) + +""" + +import markdown +from markdown import etree +import re +from string import ascii_lowercase, digits, punctuation + +ID_CHARS = ascii_lowercase + digits + '-_' +IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$') + + +class HeaderIdProcessor(markdown.blockprocessors.BlockProcessor): + """ Replacement BlockProcessor for Header IDs. """ + + # Detect a header at start of any line in block + RE = re.compile(r"""(^|\n) + (?P<level>\#{1,6}) # group('level') = string of hashes + (?P<header>.*?) # group('header') = Header text + \#* # optional closing hashes + (?:[ \t]*\{[ \t]*\#(?P<id>[-_:a-zA-Z0-9]+)[ \t]*\})? + (\n|$) # ^^ group('id') = id attribute + """, + re.VERBOSE) + + IDs = [] + + def test(self, parent, block): + return bool(self.RE.search(block)) + + def run(self, parent, blocks): + block = blocks.pop(0) + m = self.RE.search(block) + if m: + before = block[:m.start()] # All lines before header + after = block[m.end():] # All lines after header + if before: + # As the header was not the first line of the block and the + # lines before the header must be parsed first, + # recursively parse this lines as a block. + self.parser.parseBlocks(parent, [before]) + # Create header using named groups from RE + start_level, force_id = self._get_meta() + level = len(m.group('level')) + start_level + if level > 6: + level = 6 + h = markdown.etree.SubElement(parent, 'h%d' % level) + h.text = m.group('header').strip() + if m.group('id'): + h.set('id', self._unique_id(m.group('id'))) + elif force_id: + h.set('id', self._create_id(m.group('header').strip())) + if after: + # Insert remaining lines as first block for future parsing. + blocks.insert(0, after) + else: + # This should never happen, but just in case... + message(CRITICAL, "We've got a problem header!") + + def _get_meta(self): + """ Return meta data suported by this ext as a tuple """ + level = int(self.config['level'][0]) - 1 + force = self._str2bool(self.config['forceid'][0]) + if hasattr(self.md, 'Meta'): + if self.md.Meta.has_key('header_level'): + level = int(self.md.Meta['header_level'][0]) - 1 + if self.md.Meta.has_key('header_forceid'): + force = self._str2bool(self.md.Meta['header_forceid'][0]) + return level, force + + def _str2bool(self, s, default=False): + """ Convert a string to a booleen value. """ + s = str(s) + if s.lower() in ['0', 'f', 'false', 'off', 'no', 'n']: + return False + elif s.lower() in ['1', 't', 'true', 'on', 'yes', 'y']: + return True + return default + + def _unique_id(self, id): + """ Ensure ID is unique. Append '_1', '_2'... if not """ + while id in self.IDs: + m = IDCOUNT_RE.match(id) + if m: + id = '%s_%d'% (m.group(1), int(m.group(2))+1) + else: + id = '%s_%d'% (id, 1) + self.IDs.append(id) + return id + + def _create_id(self, header): + """ Return ID from Header text. """ + h = '' + for c in header.lower().replace(' ', '_'): + if c in ID_CHARS: + h += c + elif c not in punctuation: + h += '+' + return self._unique_id(h) + + +class HeaderIdExtension (markdown.Extension): + def __init__(self, configs): + # set defaults + self.config = { + 'level' : ['1', 'Base level for headers.'], + 'forceid' : ['True', 'Force all headers to have an id.'] + } + + for key, value in configs: + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + md.registerExtension(self) + self.processor = HeaderIdProcessor(md.parser) + self.processor.md = md + self.processor.config = self.config + # Replace existing hasheader in place. + md.parser.blockprocessors['hashheader'] = self.processor + + def reset(self): + self.processor.IDs = [] + + +def makeExtension(configs=None): + return HeaderIdExtension(configs=configs) + +if __name__ == "__main__": + import doctest + doctest.testmod() + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/html_tidy.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/html_tidy.py new file mode 100644 index 0000000..5105e33 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/html_tidy.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +""" +HTML Tidy Extension for Python-Markdown +======================================= + +Runs [HTML Tidy][] on the output of Python-Markdown using the [uTidylib][] +Python wrapper. Both libtidy and uTidylib must be installed on your system. + +Note than any Tidy [options][] can be passed in as extension configs. So, +for example, to output HTML rather than XHTML, set ``output_xhtml=0``. To +indent the output, set ``indent=auto`` and to have Tidy wrap the output in +``<html>`` and ``<body>`` tags, set ``show_body_only=0``. + +[HTML Tidy]: http://tidy.sourceforge.net/ +[uTidylib]: http://utidylib.berlios.de/ +[options]: http://tidy.sourceforge.net/docs/quickref.html + +Copyright (c)2008 [Waylan Limberg](http://achinghead.com) + +License: [BSD](http://www.opensource.org/licenses/bsd-license.php) + +Dependencies: +* [Python2.3+](http://python.org) +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) +* [HTML Tidy](http://utidylib.berlios.de/) +* [uTidylib](http://utidylib.berlios.de/) + +""" + +import markdown +import tidy + +class TidyExtension(markdown.Extension): + + def __init__(self, configs): + # Set defaults to match typical markdown behavior. + self.config = dict(output_xhtml=1, + show_body_only=1, + ) + # Merge in user defined configs overriding any present if nessecary. + for c in configs: + self.config[c[0]] = c[1] + + def extendMarkdown(self, md, md_globals): + # Save options to markdown instance + md.tidy_options = self.config + # Add TidyProcessor to postprocessors + md.postprocessors['tidy'] = TidyProcessor(md) + + +class TidyProcessor(markdown.postprocessors.Postprocessor): + + def run(self, text): + # Pass text to Tidy. As Tidy does not accept unicode we need to encode + # it and decode its return value. + return unicode(tidy.parseString(text.encode('utf-8'), + **self.markdown.tidy_options)) + + +def makeExtension(configs=None): + return TidyExtension(configs=configs) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/imagelinks.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/imagelinks.py new file mode 100644 index 0000000..ee0b708 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/imagelinks.py @@ -0,0 +1,119 @@ +""" +========================= IMAGE LINKS ================================= + + +Turns paragraphs like + +<~~~~~~~~~~~~~~~~~~~~~~~~ +dir/subdir +dir/subdir +dir/subdir +~~~~~~~~~~~~~~ +dir/subdir +dir/subdir +dir/subdir +~~~~~~~~~~~~~~~~~~~> + +Into mini-photo galleries. + +""" + +import re, markdown +import url_manager + + +IMAGE_LINK = """<a href="%s"><img src="%s" title="%s"/></a>""" +SLIDESHOW_LINK = """<a href="%s" target="_blank">[slideshow]</a>""" +ALBUM_LINK = """ <a href="%s">[%s]</a>""" + + +class ImageLinksExtension(markdown.Extension): + + def extendMarkdown(self, md, md_globals): + + md.preprocessors.add("imagelink", ImageLinkPreprocessor(md), "_begin") + + +class ImageLinkPreprocessor(markdown.preprocessors.Preprocessor): + + def run(self, lines): + + url = url_manager.BlogEntryUrl(url_manager.BlogUrl("all"), + "2006/08/29/the_rest_of_our") + + + all_images = [] + blocks = [] + in_image_block = False + + new_lines = [] + + for line in lines: + + if line.startswith("<~~~~~~~"): + albums = [] + rows = [] + in_image_block = True + + if not in_image_block: + + new_lines.append(line) + + else: + + line = line.strip() + + if line.endswith("~~~~~~>") or not line: + in_image_block = False + new_block = "<div><br/><center><span class='image-links'>\n" + + album_url_hash = {} + + for row in rows: + for photo_url, title in row: + new_block += " " + new_block += IMAGE_LINK % (photo_url, + photo_url.get_thumbnail(), + title) + + album_url_hash[str(photo_url.get_album())] = 1 + + new_block += "<br/>" + + new_block += "</span>" + new_block += SLIDESHOW_LINK % url.get_slideshow() + + album_urls = album_url_hash.keys() + album_urls.sort() + + if len(album_urls) == 1: + new_block += ALBUM_LINK % (album_urls[0], "complete album") + else : + for i in range(len(album_urls)) : + new_block += ALBUM_LINK % (album_urls[i], + "album %d" % (i + 1) ) + + new_lines.append(new_block + "</center><br/></div>") + + elif line[1:6] == "~~~~~" : + rows.append([]) # start a new row + else : + parts = line.split() + line = parts[0] + title = " ".join(parts[1:]) + + album, photo = line.split("/") + photo_url = url.get_photo(album, photo, + len(all_images)+1) + all_images.append(photo_url) + rows[-1].append((photo_url, title)) + + if not album in albums : + albums.append(album) + + return new_lines + + +def makeExtension(configs): + return ImageLinksExtension(configs) + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/meta.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/meta.py new file mode 100644 index 0000000..1b555b2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/meta.py @@ -0,0 +1,90 @@ +#!usr/bin/python + +""" +Meta Data Extension for Python-Markdown +======================================= + +This extension adds Meta Data handling to markdown. + +Basic Usage: + + >>> import markdown + >>> text = '''Title: A Test Doc. + ... Author: Waylan Limberg + ... John Doe + ... Blank_Data: + ... + ... The body. This is paragraph one. + ... ''' + >>> md = markdown.Markdown(['meta']) + >>> md.convert(text) + u'<p>The body. This is paragraph one.</p>' + >>> md.Meta + {u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']} + +Make sure text without Meta Data still works (markdown < 1.6b returns a <p>). + + >>> text = ' Some Code - not extra lines of meta data.' + >>> md = markdown.Markdown(['meta']) + >>> md.convert(text) + u'<pre><code>Some Code - not extra lines of meta data.\\n</code></pre>' + >>> md.Meta + {} + +Copyright 2007-2008 [Waylan Limberg](http://achinghead.com). + +Project website: <http://www.freewisdom.org/project/python-markdown/Meta-Data> +Contact: markdown@freewisdom.org + +License: BSD (see ../docs/LICENSE for details) + +""" + +import markdown, re + +# Global Vars +META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)') +META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)') + +class MetaExtension (markdown.Extension): + """ Meta-Data extension for Python-Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Add MetaPreprocessor to Markdown instance. """ + + md.preprocessors.add("meta", MetaPreprocessor(md), "_begin") + + +class MetaPreprocessor(markdown.preprocessors.Preprocessor): + """ Get Meta-Data. """ + + def run(self, lines): + """ Parse Meta-Data and store in Markdown.Meta. """ + meta = {} + key = None + while 1: + line = lines.pop(0) + if line.strip() == '': + break # blank line - done + m1 = META_RE.match(line) + if m1: + key = m1.group('key').lower().strip() + meta[key] = [m1.group('value').strip()] + else: + m2 = META_MORE_RE.match(line) + if m2 and key: + # Add another line to existing key + meta[key].append(m2.group('value').strip()) + else: + lines.insert(0, line) + break # no meta data - done + self.markdown.Meta = meta + return lines + + +def makeExtension(configs={}): + return MetaExtension(configs=configs) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/rss.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/rss.py new file mode 100644 index 0000000..1274da2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/rss.py @@ -0,0 +1,114 @@ +import markdown +from markdown import etree + +DEFAULT_URL = "http://www.freewisdom.org/projects/python-markdown/" +DEFAULT_CREATOR = "Yuri Takhteyev" +DEFAULT_TITLE = "Markdown in Python" +GENERATOR = "http://www.freewisdom.org/projects/python-markdown/markdown2rss" + +month_map = { "Jan" : "01", + "Feb" : "02", + "March" : "03", + "April" : "04", + "May" : "05", + "June" : "06", + "July" : "07", + "August" : "08", + "September" : "09", + "October" : "10", + "November" : "11", + "December" : "12" } + +def get_time(heading): + + heading = heading.split("-")[0] + heading = heading.strip().replace(",", " ").replace(".", " ") + + month, date, year = heading.split() + month = month_map[month] + + return rdftime(" ".join((month, date, year, "12:00:00 AM"))) + +def rdftime(time): + + time = time.replace(":", " ") + time = time.replace("/", " ") + time = time.split() + return "%s-%s-%sT%s:%s:%s-08:00" % (time[0], time[1], time[2], + time[3], time[4], time[5]) + + +def get_date(text): + return "date" + +class RssExtension (markdown.Extension): + + def extendMarkdown(self, md, md_globals): + + self.config = { 'URL' : [DEFAULT_URL, "Main URL"], + 'CREATOR' : [DEFAULT_CREATOR, "Feed creator's name"], + 'TITLE' : [DEFAULT_TITLE, "Feed title"] } + + md.xml_mode = True + + # Insert a tree-processor that would actually add the title tag + treeprocessor = RssTreeProcessor(md) + treeprocessor.ext = self + md.treeprocessors['rss'] = treeprocessor + md.stripTopLevelTags = 0 + md.docType = '<?xml version="1.0" encoding="utf-8"?>\n' + +class RssTreeProcessor(markdown.treeprocessors.Treeprocessor): + + def run (self, root): + + rss = etree.Element("rss") + rss.set("version", "2.0") + + channel = etree.SubElement(rss, "channel") + + for tag, text in (("title", self.ext.getConfig("TITLE")), + ("link", self.ext.getConfig("URL")), + ("description", None)): + + element = etree.SubElement(channel, tag) + element.text = text + + for child in root: + + if child.tag in ["h1", "h2", "h3", "h4", "h5"]: + + heading = child.text.strip() + item = etree.SubElement(channel, "item") + link = etree.SubElement(item, "link") + link.text = self.ext.getConfig("URL") + title = etree.SubElement(item, "title") + title.text = heading + + guid = ''.join([x for x in heading if x.isalnum()]) + guidElem = etree.SubElement(item, "guid") + guidElem.text = guid + guidElem.set("isPermaLink", "false") + + elif child.tag in ["p"]: + try: + description = etree.SubElement(item, "description") + except UnboundLocalError: + # Item not defined - moving on + pass + else: + if len(child): + content = "\n".join([etree.tostring(node) + for node in child]) + else: + content = child.text + pholder = self.markdown.htmlStash.store( + "<![CDATA[ %s]]>" % content) + description.text = pholder + + return rss + + +def makeExtension(configs): + + return RssExtension(configs) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/tables.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/tables.py new file mode 100644 index 0000000..1d3c920 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/tables.py @@ -0,0 +1,97 @@ +#!/usr/bin/env Python +""" +Tables Extension for Python-Markdown +==================================== + +Added parsing of tables to Python-Markdown. + +A simple example: + + First Header | Second Header + ------------- | ------------- + Content Cell | Content Cell + Content Cell | Content Cell + +Copyright 2009 - [Waylan Limberg](http://achinghead.com) +""" +import markdown +from markdown import etree + + +class TableProcessor(markdown.blockprocessors.BlockProcessor): + """ Process Tables. """ + + def test(self, parent, block): + rows = block.split('\n') + return (len(rows) > 2 and '|' in rows[0] and + '|' in rows[1] and '-' in rows[1] and + rows[1][0] in ['|', ':', '-']) + + def run(self, parent, blocks): + """ Parse a table block and build table. """ + block = blocks.pop(0).split('\n') + header = block[:2] + rows = block[2:] + # Get format type (bordered by pipes or not) + border = False + if header[0].startswith('|'): + border = True + # Get alignment of columns + align = [] + for c in self._split_row(header[1], border): + if c.startswith(':') and c.endswith(':'): + align.append('center') + elif c.startswith(':'): + align.append('left') + elif c.endswith(':'): + align.append('right') + else: + align.append(None) + # Build table + table = etree.SubElement(parent, 'table') + thead = etree.SubElement(table, 'thead') + self._build_row(header[0], thead, align, border) + tbody = etree.SubElement(table, 'tbody') + for row in rows: + self._build_row(row, tbody, align, border) + + def _build_row(self, row, parent, align, border): + """ Given a row of text, build table cells. """ + tr = etree.SubElement(parent, 'tr') + tag = 'td' + if parent.tag == 'thead': + tag = 'th' + cells = self._split_row(row, border) + # We use align here rather than cells to ensure every row + # contains the same number of columns. + for i, a in enumerate(align): + c = etree.SubElement(tr, tag) + try: + c.text = cells[i].strip() + except IndexError: + c.text = "" + if a: + c.set('align', a) + + def _split_row(self, row, border): + """ split a row of text into list of cells. """ + if border: + if row.startswith('|'): + row = row[1:] + if row.endswith('|'): + row = row[:-1] + return row.split('|') + + +class TableExtension(markdown.Extension): + """ Add tables to Markdown. """ + + def extendMarkdown(self, md, md_globals): + """ Add an instance of TableProcessor to BlockParser. """ + md.parser.blockprocessors.add('table', + TableProcessor(md.parser), + '<hashheader') + + +def makeExtension(configs={}): + return TableExtension(configs=configs) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/toc.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/toc.py new file mode 100644 index 0000000..1624ccf --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/toc.py @@ -0,0 +1,140 @@ +""" +Table of Contents Extension for Python-Markdown +* * * + +(c) 2008 [Jack Miller](http://codezen.org) + +Dependencies: +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) + +""" +import markdown +from markdown import etree +import re + +class TocTreeprocessor(markdown.treeprocessors.Treeprocessor): + # Iterator wrapper to get parent and child all at once + def iterparent(self, root): + for parent in root.getiterator(): + for child in parent: + yield parent, child + + def run(self, doc): + div = etree.Element("div") + div.attrib["class"] = "toc" + last_li = None + + # Add title to the div + if self.config["title"][0]: + header = etree.SubElement(div, "span") + header.attrib["class"] = "toctitle" + header.text = self.config["title"][0] + + level = 0 + list_stack=[div] + header_rgx = re.compile("[Hh][123456]") + + # Get a list of id attributes + used_ids = [] + for c in doc.getiterator(): + if "id" in c.attrib: + used_ids.append(c.attrib["id"]) + + for (p, c) in self.iterparent(doc): + if not c.text: + continue + + # To keep the output from screwing up the + # validation by putting a <div> inside of a <p> + # we actually replace the <p> in its entirety. + # We do not allow the marker inside a header as that + # would causes an enless loop of placing a new TOC + # inside previously generated TOC. + + if c.text.find(self.config["marker"][0]) > -1 and not header_rgx.match(c.tag): + for i in range(len(p)): + if p[i] == c: + p[i] = div + break + + if header_rgx.match(c.tag): + tag_level = int(c.tag[-1]) + + # Regardless of how many levels we jumped + # only one list should be created, since + # empty lists containing lists are illegal. + + if tag_level < level: + list_stack.pop() + level = tag_level + + if tag_level > level: + newlist = etree.Element("ul") + if last_li: + last_li.append(newlist) + else: + list_stack[-1].append(newlist) + list_stack.append(newlist) + level = tag_level + + # Do not override pre-existing ids + if not "id" in c.attrib: + id = self.config["slugify"][0](c.text) + if id in used_ids: + ctr = 1 + while "%s_%d" % (id, ctr) in used_ids: + ctr += 1 + id = "%s_%d" % (id, ctr) + used_ids.append(id) + c.attrib["id"] = id + else: + id = c.attrib["id"] + + # List item link, to be inserted into the toc div + last_li = etree.Element("li") + link = etree.SubElement(last_li, "a") + link.text = c.text + link.attrib["href"] = '#' + id + + if int(self.config["anchorlink"][0]): + anchor = etree.SubElement(c, "a") + anchor.text = c.text + anchor.attrib["href"] = "#" + id + anchor.attrib["class"] = "toclink" + c.text = "" + + list_stack[-1].append(last_li) + +class TocExtension(markdown.Extension): + def __init__(self, configs): + self.config = { "marker" : ["[TOC]", + "Text to find and replace with Table of Contents -" + "Defaults to \"[TOC]\""], + "slugify" : [self.slugify, + "Function to generate anchors based on header text-" + "Defaults to a built in slugify function."], + "title" : [None, + "Title to insert into TOC <div> - " + "Defaults to None"], + "anchorlink" : [0, + "1 if header should be a self link" + "Defaults to 0"]} + + for key, value in configs: + self.setConfig(key, value) + + # This is exactly the same as Django's slugify + def slugify(self, value): + """ Slugify a string, to make it URL friendly. """ + import unicodedata + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') + value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) + return re.sub('[-\s]+','-',value) + + def extendMarkdown(self, md, md_globals): + tocext = TocTreeprocessor(md) + tocext.config = self.config + md.treeprocessors.add("toc", tocext, "_begin") + +def makeExtension(configs={}): + return TocExtension(configs=configs) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/extensions/wikilinks.py b/tools/addon-sdk-1.7/python-lib/markdown/extensions/wikilinks.py new file mode 100644 index 0000000..df44e1c --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/extensions/wikilinks.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python + +''' +WikiLinks Extension for Python-Markdown +====================================== + +Converts [[WikiLinks]] to relative links. Requires Python-Markdown 2.0+ + +Basic usage: + + >>> import markdown + >>> text = "Some text with a [[WikiLink]]." + >>> html = markdown.markdown(text, ['wikilinks']) + >>> html + u'<p>Some text with a <a class="wikilink" href="/WikiLink/">WikiLink</a>.</p>' + +Whitespace behavior: + + >>> markdown.markdown('[[ foo bar_baz ]]', ['wikilinks']) + u'<p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p>' + >>> markdown.markdown('foo [[ ]] bar', ['wikilinks']) + u'<p>foo bar</p>' + +To define custom settings the simple way: + + >>> markdown.markdown(text, + ... ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)'] + ... ) + u'<p>Some text with a <a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>' + +Custom settings the complex way: + + >>> md = markdown.Markdown( + ... extensions = ['wikilinks'], + ... extension_configs = {'wikilinks': [ + ... ('base_url', 'http://example.com/'), + ... ('end_url', '.html'), + ... ('html_class', '') ]}, + ... safe_mode = True) + >>> md.convert(text) + u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>' + +Use MetaData with mdx_meta.py (Note the blank html_class in MetaData): + + >>> text = """wiki_base_url: http://example.com/ + ... wiki_end_url: .html + ... wiki_html_class: + ... + ... Some text with a [[WikiLink]].""" + >>> md = markdown.Markdown(extensions=['meta', 'wikilinks']) + >>> md.convert(text) + u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>' + +MetaData should not carry over to next document: + + >>> md.convert("No [[MetaData]] here.") + u'<p>No <a class="wikilink" href="/MetaData/">MetaData</a> here.</p>' + +Define a custom URL builder: + + >>> def my_url_builder(label, base, end): + ... return '/bar/' + >>> md = markdown.Markdown(extensions=['wikilinks'], + ... extension_configs={'wikilinks' : [('build_url', my_url_builder)]}) + >>> md.convert('[[foo]]') + u'<p><a class="wikilink" href="/bar/">foo</a></p>' + +From the command line: + + python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt + +By [Waylan Limberg](http://achinghead.com/). + +License: [BSD](http://www.opensource.org/licenses/bsd-license.php) + +Dependencies: +* [Python 2.3+](http://python.org) +* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/) +''' + +import markdown +import re + +def build_url(label, base, end): + """ Build a url from the label, a base, and an end. """ + clean_label = re.sub(r'([ ]+_)|(_[ ]+)|([ ]+)', '_', label) + return '%s%s%s'% (base, clean_label, end) + + +class WikiLinkExtension(markdown.Extension): + def __init__(self, configs): + # set extension defaults + self.config = { + 'base_url' : ['/', 'String to append to beginning or URL.'], + 'end_url' : ['/', 'String to append to end of URL.'], + 'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'], + 'build_url' : [build_url, 'Callable formats URL from label.'], + } + + # Override defaults with user settings + for key, value in configs : + self.setConfig(key, value) + + def extendMarkdown(self, md, md_globals): + self.md = md + + # append to end of inline patterns + WIKILINK_RE = r'\[\[([A-Za-z0-9_ -]+)\]\]' + wikilinkPattern = WikiLinks(WIKILINK_RE, self.config) + wikilinkPattern.md = md + md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong") + + +class WikiLinks(markdown.inlinepatterns.Pattern): + def __init__(self, pattern, config): + markdown.inlinepatterns.Pattern.__init__(self, pattern) + self.config = config + + def handleMatch(self, m): + if m.group(2).strip(): + base_url, end_url, html_class = self._getMeta() + label = m.group(2).strip() + url = self.config['build_url'][0](label, base_url, end_url) + a = markdown.etree.Element('a') + a.text = label + a.set('href', url) + if html_class: + a.set('class', html_class) + else: + a = '' + return a + + def _getMeta(self): + """ Return meta data or config data. """ + base_url = self.config['base_url'][0] + end_url = self.config['end_url'][0] + html_class = self.config['html_class'][0] + if hasattr(self.md, 'Meta'): + if self.md.Meta.has_key('wiki_base_url'): + base_url = self.md.Meta['wiki_base_url'][0] + if self.md.Meta.has_key('wiki_end_url'): + end_url = self.md.Meta['wiki_end_url'][0] + if self.md.Meta.has_key('wiki_html_class'): + html_class = self.md.Meta['wiki_html_class'][0] + return base_url, end_url, html_class + + +def makeExtension(configs=None) : + return WikiLinkExtension(configs=configs) + + +if __name__ == "__main__": + import doctest + doctest.testmod() + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/html4.py b/tools/addon-sdk-1.7/python-lib/markdown/html4.py new file mode 100644 index 0000000..08f241d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/html4.py @@ -0,0 +1,274 @@ +# markdown/html4.py +# +# Add html4 serialization to older versions of Elementree +# Taken from ElementTree 1.3 preview with slight modifications +# +# Copyright (c) 1999-2007 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2007 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + + +import markdown +ElementTree = markdown.etree.ElementTree +QName = markdown.etree.QName +Comment = markdown.etree.Comment +PI = markdown.etree.PI +ProcessingInstruction = markdown.etree.ProcessingInstruction + +HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr", + "img", "input", "isindex", "link", "meta" "param") + +try: + HTML_EMPTY = set(HTML_EMPTY) +except NameError: + pass + +_namespace_map = { + # "well-known" namespace prefixes + "http://www.w3.org/XML/1998/namespace": "xml", + "http://www.w3.org/1999/xhtml": "html", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", + "http://schemas.xmlsoap.org/wsdl/": "wsdl", + # xml schema + "http://www.w3.org/2001/XMLSchema": "xs", + "http://www.w3.org/2001/XMLSchema-instance": "xsi", + # dublic core + "http://purl.org/dc/elements/1.1/": "dc", +} + + +def _raise_serialization_error(text): + raise TypeError( + "cannot serialize %r (type %s)" % (text, type(text).__name__) + ) + +def _encode(text, encoding): + try: + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_cdata(text, encoding): + # escape character data + try: + # it's worth avoiding do-nothing calls for strings that are + # shorter than 500 character, or so. assume that's, by far, + # the most common case in most applications. + if "&" in text: + text = text.replace("&", "&") + if "<" in text: + text = text.replace("<", "<") + if ">" in text: + text = text.replace(">", ">") + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + + +def _escape_attrib(text, encoding): + # escape attribute value + try: + if "&" in text: + text = text.replace("&", "&") + if "<" in text: + text = text.replace("<", "<") + if ">" in text: + text = text.replace(">", ">") + if "\"" in text: + text = text.replace("\"", """) + if "\n" in text: + text = text.replace("\n", " ") + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + +def _escape_attrib_html(text, encoding): + # escape attribute value + try: + if "&" in text: + text = text.replace("&", "&") + if ">" in text: + text = text.replace(">", ">") + if "\"" in text: + text = text.replace("\"", """) + return text.encode(encoding, "xmlcharrefreplace") + except (TypeError, AttributeError): + _raise_serialization_error(text) + + +def _serialize_html(write, elem, encoding, qnames, namespaces): + tag = elem.tag + text = elem.text + if tag is Comment: + write("<!--%s-->" % _escape_cdata(text, encoding)) + elif tag is ProcessingInstruction: + write("<?%s?>" % _escape_cdata(text, encoding)) + else: + tag = qnames[tag] + if tag is None: + if text: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + else: + write("<" + tag) + items = elem.items() + if items or namespaces: + items.sort() # lexical order + for k, v in items: + if isinstance(k, QName): + k = k.text + if isinstance(v, QName): + v = qnames[v.text] + else: + v = _escape_attrib_html(v, encoding) + # FIXME: handle boolean attributes + write(" %s=\"%s\"" % (qnames[k], v)) + if namespaces: + items = namespaces.items() + items.sort(key=lambda x: x[1]) # sort on prefix + for v, k in items: + if k: + k = ":" + k + write(" xmlns%s=\"%s\"" % ( + k.encode(encoding), + _escape_attrib(v, encoding) + )) + write(">") + tag = tag.lower() + if text: + if tag == "script" or tag == "style": + write(_encode(text, encoding)) + else: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + if tag not in HTML_EMPTY: + write("</" + tag + ">") + if elem.tail: + write(_escape_cdata(elem.tail, encoding)) + +def write_html(root, f, + # keyword arguments + encoding="us-ascii", + default_namespace=None): + assert root is not None + if not hasattr(f, "write"): + f = open(f, "wb") + write = f.write + if not encoding: + encoding = "us-ascii" + qnames, namespaces = _namespaces( + root, encoding, default_namespace + ) + _serialize_html( + write, root, encoding, qnames, namespaces + ) + +# -------------------------------------------------------------------- +# serialization support + +def _namespaces(elem, encoding, default_namespace=None): + # identify namespaces used in this tree + + # maps qnames to *encoded* prefix:local names + qnames = {None: None} + + # maps uri:s to prefixes + namespaces = {} + if default_namespace: + namespaces[default_namespace] = "" + + def encode(text): + return text.encode(encoding) + + def add_qname(qname): + # calculate serialized qname representation + try: + if qname[:1] == "{": + uri, tag = qname[1:].split("}", 1) + prefix = namespaces.get(uri) + if prefix is None: + prefix = _namespace_map.get(uri) + if prefix is None: + prefix = "ns%d" % len(namespaces) + if prefix != "xml": + namespaces[uri] = prefix + if prefix: + qnames[qname] = encode("%s:%s" % (prefix, tag)) + else: + qnames[qname] = encode(tag) # default element + else: + if default_namespace: + # FIXME: can this be handled in XML 1.0? + raise ValueError( + "cannot use non-qualified names with " + "default_namespace option" + ) + qnames[qname] = encode(qname) + except TypeError: + _raise_serialization_error(qname) + + # populate qname and namespaces table + try: + iterate = elem.iter + except AttributeError: + iterate = elem.getiterator # cET compatibility + for elem in iterate(): + tag = elem.tag + if isinstance(tag, QName) and tag.text not in qnames: + add_qname(tag.text) + elif isinstance(tag, basestring): + if tag not in qnames: + add_qname(tag) + elif tag is not None and tag is not Comment and tag is not PI: + _raise_serialization_error(tag) + for key, value in elem.items(): + if isinstance(key, QName): + key = key.text + if key not in qnames: + add_qname(key) + if isinstance(value, QName) and value.text not in qnames: + add_qname(value.text) + text = elem.text + if isinstance(text, QName) and text.text not in qnames: + add_qname(text.text) + return qnames, namespaces + +def to_html_string(element, encoding=None): + class dummy: + pass + data = [] + file = dummy() + file.write = data.append + write_html(ElementTree(element).getroot(),file,encoding) + return "".join(data) diff --git a/tools/addon-sdk-1.7/python-lib/markdown/inlinepatterns.py b/tools/addon-sdk-1.7/python-lib/markdown/inlinepatterns.py new file mode 100644 index 0000000..89fa3b2 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/inlinepatterns.py @@ -0,0 +1,371 @@ +""" +INLINE PATTERNS +============================================================================= + +Inline patterns such as *emphasis* are handled by means of auxiliary +objects, one per pattern. Pattern objects must be instances of classes +that extend markdown.Pattern. Each pattern object uses a single regular +expression and needs support the following methods: + + pattern.getCompiledRegExp() # returns a regular expression + + pattern.handleMatch(m) # takes a match object and returns + # an ElementTree element or just plain text + +All of python markdown's built-in patterns subclass from Pattern, +but you can add additional patterns that don't. + +Also note that all the regular expressions used by inline must +capture the whole block. For this reason, they all start with +'^(.*)' and end with '(.*)!'. In case with built-in expression +Pattern takes care of adding the "^(.*)" and "(.*)!". + +Finally, the order in which regular expressions are applied is very +important - e.g. if we first replace http://.../ links with <a> tags +and _then_ try to replace inline html, we would end up with a mess. +So, we apply the expressions in the following order: + +* escape and backticks have to go before everything else, so + that we can preempt any markdown patterns by escaping them. + +* then we handle auto-links (must be done before inline html) + +* then we handle inline HTML. At this point we will simply + replace all inline HTML strings with a placeholder and add + the actual HTML to a hash. + +* then inline images (must be done before links) + +* then bracketed links, first regular then reference-style + +* finally we apply strong and emphasis +""" + +import markdown +import re +from urlparse import urlparse, urlunparse +import sys +if sys.version >= "3.0": + from html import entities as htmlentitydefs +else: + import htmlentitydefs + +""" +The actual regular expressions for patterns +----------------------------------------------------------------------------- +""" + +NOBRACKET = r'[^\]\[]*' +BRK = ( r'\[(' + + (NOBRACKET + r'(\[')*6 + + (NOBRACKET+ r'\])*')*6 + + NOBRACKET + r')\]' ) +NOIMG = r'(?<!\!)' + +BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")`` +ESCAPE_RE = r'\\(.)' # \< +EMPHASIS_RE = r'(\*)([^\*]*)\2' # *emphasis* +STRONG_RE = r'(\*{2}|_{2})(.*?)\2' # **strong** +STRONG_EM_RE = r'(\*{3}|_{3})(.*?)\2' # ***strong*** + +if markdown.SMART_EMPHASIS: + EMPHASIS_2_RE = r'(?<!\S)(_)(\S.*?)\2' # _emphasis_ +else: + EMPHASIS_2_RE = r'(_)(.*?)\2' # _emphasis_ + +LINK_RE = NOIMG + BRK + \ +r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*)\12)?\)''' +# [text](url) or [text](<url>) + +IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^\)]*))\)' +# ![alttxt](http://x.com/) or ![alttxt](<http://x.com/>) +REFERENCE_RE = NOIMG + BRK+ r'\s*\[([^\]]*)\]' # [Google][3] +IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2] +NOT_STRONG_RE = r'( \* )' # stand-alone * or _ +AUTOLINK_RE = r'<((?:f|ht)tps?://[^>]*)>' # <http://www.123.com> +AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com> + +HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...> +ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # & +LINE_BREAK_RE = r' \n' # two spaces at end of line +LINE_BREAK_2_RE = r' $' # two spaces at end of text + + +def dequote(string): + """Remove quotes from around a string.""" + if ( ( string.startswith('"') and string.endswith('"')) + or (string.startswith("'") and string.endswith("'")) ): + return string[1:-1] + else: + return string + +ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123} + +def handleAttributes(text, parent): + """Set values of an element based on attribute definitions ({@id=123}).""" + def attributeCallback(match): + parent.set(match.group(1), match.group(2).replace('\n', ' ')) + return ATTR_RE.sub(attributeCallback, text) + + +""" +The pattern classes +----------------------------------------------------------------------------- +""" + +class Pattern: + """Base class that inline patterns subclass. """ + + def __init__ (self, pattern, markdown_instance=None): + """ + Create an instant of an inline pattern. + + Keyword arguments: + + * pattern: A regular expression that matches a pattern + + """ + self.pattern = pattern + self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, re.DOTALL) + + # Api for Markdown to pass safe_mode into instance + self.safe_mode = False + if markdown_instance: + self.markdown = markdown_instance + + def getCompiledRegExp (self): + """ Return a compiled regular expression. """ + return self.compiled_re + + def handleMatch(self, m): + """Return a ElementTree element from the given match. + + Subclasses should override this method. + + Keyword arguments: + + * m: A re match object containing a match of the pattern. + + """ + pass + + def type(self): + """ Return class name, to define pattern type """ + return self.__class__.__name__ + +BasePattern = Pattern # for backward compatibility + +class SimpleTextPattern (Pattern): + """ Return a simple text of group(2) of a Pattern. """ + def handleMatch(self, m): + text = m.group(2) + if text == markdown.INLINE_PLACEHOLDER_PREFIX: + return None + return text + +class SimpleTagPattern (Pattern): + """ + Return element of type `tag` with a text attribute of group(3) + of a Pattern. + + """ + def __init__ (self, pattern, tag): + Pattern.__init__(self, pattern) + self.tag = tag + + def handleMatch(self, m): + el = markdown.etree.Element(self.tag) + el.text = m.group(3) + return el + + +class SubstituteTagPattern (SimpleTagPattern): + """ Return a eLement of type `tag` with no children. """ + def handleMatch (self, m): + return markdown.etree.Element(self.tag) + + +class BacktickPattern (Pattern): + """ Return a `<code>` element containing the matching text. """ + def __init__ (self, pattern): + Pattern.__init__(self, pattern) + self.tag = "code" + + def handleMatch(self, m): + el = markdown.etree.Element(self.tag) + el.text = markdown.AtomicString(m.group(3).strip()) + return el + + +class DoubleTagPattern (SimpleTagPattern): + """Return a ElementTree element nested in tag2 nested in tag1. + + Useful for strong emphasis etc. + + """ + def handleMatch(self, m): + tag1, tag2 = self.tag.split(",") + el1 = markdown.etree.Element(tag1) + el2 = markdown.etree.SubElement(el1, tag2) + el2.text = m.group(3) + return el1 + + +class HtmlPattern (Pattern): + """ Store raw inline html and return a placeholder. """ + def handleMatch (self, m): + rawhtml = m.group(2) + inline = True + place_holder = self.markdown.htmlStash.store(rawhtml) + return place_holder + + +class LinkPattern (Pattern): + """ Return a link element from the given match. """ + def handleMatch(self, m): + el = markdown.etree.Element("a") + el.text = m.group(2) + title = m.group(11) + href = m.group(9) + + if href: + if href[0] == "<": + href = href[1:-1] + el.set("href", self.sanitize_url(href.strip())) + else: + el.set("href", "") + + if title: + title = dequote(title) #.replace('"', """) + el.set("title", title) + return el + + def sanitize_url(self, url): + """ + Sanitize a url against xss attacks in "safe_mode". + + Rather than specifically blacklisting `javascript:alert("XSS")` and all + its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known + safe url formats. Most urls contain a network location, however some + are known not to (i.e.: mailto links). Script urls do not contain a + location. Additionally, for `javascript:...`, the scheme would be + "javascript" but some aliases will appear to `urlparse()` to have no + scheme. On top of that relative links (i.e.: "foo/bar.html") have no + scheme. Therefore we must check "path", "parameters", "query" and + "fragment" for any literal colons. We don't check "scheme" for colons + because it *should* never have any and "netloc" must allow the form: + `username:password@host:port`. + + """ + locless_schemes = ['', 'mailto', 'news'] + scheme, netloc, path, params, query, fragment = url = urlparse(url) + safe_url = False + if netloc != '' or scheme in locless_schemes: + safe_url = True + + for part in url[2:]: + if ":" in part: + safe_url = False + + if self.markdown.safeMode and not safe_url: + return '' + else: + return urlunparse(url) + +class ImagePattern(LinkPattern): + """ Return a img element from the given match. """ + def handleMatch(self, m): + el = markdown.etree.Element("img") + src_parts = m.group(9).split() + if src_parts: + src = src_parts[0] + if src[0] == "<" and src[-1] == ">": + src = src[1:-1] + el.set('src', self.sanitize_url(src)) + else: + el.set('src', "") + if len(src_parts) > 1: + el.set('title', dequote(" ".join(src_parts[1:]))) + + if markdown.ENABLE_ATTRIBUTES: + truealt = handleAttributes(m.group(2), el) + else: + truealt = m.group(2) + + el.set('alt', truealt) + return el + +class ReferencePattern(LinkPattern): + """ Match to a stored reference and return link element. """ + def handleMatch(self, m): + if m.group(9): + id = m.group(9).lower() + else: + # if we got something like "[Google][]" + # we'll use "google" as the id + id = m.group(2).lower() + + if not id in self.markdown.references: # ignore undefined refs + return None + href, title = self.markdown.references[id] + + text = m.group(2) + return self.makeTag(href, title, text) + + def makeTag(self, href, title, text): + el = markdown.etree.Element('a') + + el.set('href', self.sanitize_url(href)) + if title: + el.set('title', title) + + el.text = text + return el + + +class ImageReferencePattern (ReferencePattern): + """ Match to a stored reference and return img element. """ + def makeTag(self, href, title, text): + el = markdown.etree.Element("img") + el.set("src", self.sanitize_url(href)) + if title: + el.set("title", title) + el.set("alt", text) + return el + + +class AutolinkPattern (Pattern): + """ Return a link Element given an autolink (`<http://example/com>`). """ + def handleMatch(self, m): + el = markdown.etree.Element("a") + el.set('href', m.group(2)) + el.text = markdown.AtomicString(m.group(2)) + return el + +class AutomailPattern (Pattern): + """ + Return a mailto link Element given an automail link (`<foo@example.com>`). + """ + def handleMatch(self, m): + el = markdown.etree.Element('a') + email = m.group(2) + if email.startswith("mailto:"): + email = email[len("mailto:"):] + + def codepoint2name(code): + """Return entity definition by code, or the code if not defined.""" + entity = htmlentitydefs.codepoint2name.get(code) + if entity: + return "%s%s;" % (markdown.AMP_SUBSTITUTE, entity) + else: + return "%s#%d;" % (markdown.AMP_SUBSTITUTE, code) + + letters = [codepoint2name(ord(letter)) for letter in email] + el.text = markdown.AtomicString(''.join(letters)) + + mailto = "mailto:" + email + mailto = "".join([markdown.AMP_SUBSTITUTE + '#%d;' % + ord(letter) for letter in mailto]) + el.set('href', mailto) + return el + diff --git a/tools/addon-sdk-1.7/python-lib/markdown/odict.py b/tools/addon-sdk-1.7/python-lib/markdown/odict.py new file mode 100644 index 0000000..bf3ef07 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/odict.py @@ -0,0 +1,162 @@ +class OrderedDict(dict): + """ + A dictionary that keeps its keys in the order in which they're inserted. + + Copied from Django's SortedDict with some modifications. + + """ + def __new__(cls, *args, **kwargs): + instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs) + instance.keyOrder = [] + return instance + + def __init__(self, data=None): + if data is None: + data = {} + super(OrderedDict, self).__init__(data) + if isinstance(data, dict): + self.keyOrder = data.keys() + else: + self.keyOrder = [] + for key, value in data: + if key not in self.keyOrder: + self.keyOrder.append(key) + + def __deepcopy__(self, memo): + from copy import deepcopy + return self.__class__([(key, deepcopy(value, memo)) + for key, value in self.iteritems()]) + + def __setitem__(self, key, value): + super(OrderedDict, self).__setitem__(key, value) + if key not in self.keyOrder: + self.keyOrder.append(key) + + def __delitem__(self, key): + super(OrderedDict, self).__delitem__(key) + self.keyOrder.remove(key) + + def __iter__(self): + for k in self.keyOrder: + yield k + + def pop(self, k, *args): + result = super(OrderedDict, self).pop(k, *args) + try: + self.keyOrder.remove(k) + except ValueError: + # Key wasn't in the dictionary in the first place. No problem. + pass + return result + + def popitem(self): + result = super(OrderedDict, self).popitem() + self.keyOrder.remove(result[0]) + return result + + def items(self): + return zip(self.keyOrder, self.values()) + + def iteritems(self): + for key in self.keyOrder: + yield key, super(OrderedDict, self).__getitem__(key) + + def keys(self): + return self.keyOrder[:] + + def iterkeys(self): + return iter(self.keyOrder) + + def values(self): + return [super(OrderedDict, self).__getitem__(k) for k in self.keyOrder] + + def itervalues(self): + for key in self.keyOrder: + yield super(OrderedDict, self).__getitem__(key) + + def update(self, dict_): + for k, v in dict_.items(): + self.__setitem__(k, v) + + def setdefault(self, key, default): + if key not in self.keyOrder: + self.keyOrder.append(key) + return super(OrderedDict, self).setdefault(key, default) + + def value_for_index(self, index): + """Return the value of the item at the given zero-based index.""" + return self[self.keyOrder[index]] + + def insert(self, index, key, value): + """Insert the key, value pair before the item with the given index.""" + if key in self.keyOrder: + n = self.keyOrder.index(key) + del self.keyOrder[n] + if n < index: + index -= 1 + self.keyOrder.insert(index, key) + super(OrderedDict, self).__setitem__(key, value) + + def copy(self): + """Return a copy of this object.""" + # This way of initializing the copy means it works for subclasses, too. + obj = self.__class__(self) + obj.keyOrder = self.keyOrder[:] + return obj + + def __repr__(self): + """ + Replace the normal dict.__repr__ with a version that returns the keys + in their sorted order. + """ + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + + def clear(self): + super(OrderedDict, self).clear() + self.keyOrder = [] + + def index(self, key): + """ Return the index of a given key. """ + return self.keyOrder.index(key) + + def index_for_location(self, location): + """ Return index or None for a given location. """ + if location == '_begin': + i = 0 + elif location == '_end': + i = None + elif location.startswith('<') or location.startswith('>'): + i = self.index(location[1:]) + if location.startswith('>'): + if i >= len(self): + # last item + i = None + else: + i += 1 + else: + raise ValueError('Not a valid location: "%s". Location key ' + 'must start with a ">" or "<".' % location) + return i + + def add(self, key, value, location): + """ Insert by key location. """ + i = self.index_for_location(location) + if i is not None: + self.insert(i, key, value) + else: + self.__setitem__(key, value) + + def link(self, key, location): + """ Change location of an existing item. """ + n = self.keyOrder.index(key) + del self.keyOrder[n] + i = self.index_for_location(location) + try: + if i is not None: + self.keyOrder.insert(i, key) + else: + self.keyOrder.append(key) + except Error: + # restore to prevent data loss and reraise + self.keyOrder.insert(n, key) + raise Error diff --git a/tools/addon-sdk-1.7/python-lib/markdown/postprocessors.py b/tools/addon-sdk-1.7/python-lib/markdown/postprocessors.py new file mode 100644 index 0000000..80227bb --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/postprocessors.py @@ -0,0 +1,77 @@ +""" +POST-PROCESSORS +============================================================================= + +Markdown also allows post-processors, which are similar to preprocessors in +that they need to implement a "run" method. However, they are run after core +processing. + +""" + + +import markdown + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Postprocessor(Processor): + """ + Postprocessors are run after the ElementTree it converted back into text. + + Each Postprocessor implements a "run" method that takes a pointer to a + text string, modifies it as necessary and returns a text string. + + Postprocessors must extend markdown.Postprocessor. + + """ + + def run(self, text): + """ + Subclasses of Postprocessor should implement a `run` method, which + takes the html document as a single text string and returns a + (possibly modified) string. + + """ + pass + + +class RawHtmlPostprocessor(Postprocessor): + """ Restore raw html to the document. """ + + def run(self, text): + """ Iterate over html stash and restore "safe" html. """ + for i in range(self.markdown.htmlStash.html_counter): + html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] + if self.markdown.safeMode and not safe: + if str(self.markdown.safeMode).lower() == 'escape': + html = self.escape(html) + elif str(self.markdown.safeMode).lower() == 'remove': + html = '' + else: + html = markdown.HTML_REMOVED_TEXT + if safe or not self.markdown.safeMode: + text = text.replace("<p>%s</p>" % + (markdown.preprocessors.HTML_PLACEHOLDER % i), + html + "\n") + text = text.replace(markdown.preprocessors.HTML_PLACEHOLDER % i, + html) + return text + + def escape(self, html): + """ Basic html escaping """ + html = html.replace('&', '&') + html = html.replace('<', '<') + html = html.replace('>', '>') + return html.replace('"', '"') + + +class AndSubstitutePostprocessor(Postprocessor): + """ Restore valid entities """ + def __init__(self): + pass + + def run(self, text): + text = text.replace(markdown.AMP_SUBSTITUTE, "&") + return text diff --git a/tools/addon-sdk-1.7/python-lib/markdown/preprocessors.py b/tools/addon-sdk-1.7/python-lib/markdown/preprocessors.py new file mode 100644 index 0000000..712a1e8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/preprocessors.py @@ -0,0 +1,214 @@ + +""" +PRE-PROCESSORS +============================================================================= + +Preprocessors work on source text before we start doing anything too +complicated. +""" + +import re +import markdown + +HTML_PLACEHOLDER_PREFIX = markdown.STX+"wzxhzdk:" +HTML_PLACEHOLDER = HTML_PLACEHOLDER_PREFIX + "%d" + markdown.ETX + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Preprocessor (Processor): + """ + Preprocessors are run after the text is broken into lines. + + Each preprocessor implements a "run" method that takes a pointer to a + list of lines of the document, modifies it as necessary and returns + either the same pointer or a pointer to a new list. + + Preprocessors must extend markdown.Preprocessor. + + """ + def run(self, lines): + """ + Each subclass of Preprocessor should override the `run` method, which + takes the document as a list of strings split by newlines and returns + the (possibly modified) list of lines. + + """ + pass + +class HtmlStash: + """ + This class is used for stashing HTML objects that we extract + in the beginning and replace with place-holders. + """ + + def __init__ (self): + """ Create a HtmlStash. """ + self.html_counter = 0 # for counting inline html segments + self.rawHtmlBlocks=[] + + def store(self, html, safe=False): + """ + Saves an HTML segment for later reinsertion. Returns a + placeholder string that needs to be inserted into the + document. + + Keyword arguments: + + * html: an html segment + * safe: label an html segment as safe for safemode + + Returns : a placeholder string + + """ + self.rawHtmlBlocks.append((html, safe)) + placeholder = HTML_PLACEHOLDER % self.html_counter + self.html_counter += 1 + return placeholder + + def reset(self): + self.html_counter = 0 + self.rawHtmlBlocks = [] + + +class HtmlBlockPreprocessor(Preprocessor): + """Remove html blocks from the text and store them for later retrieval.""" + + right_tag_patterns = ["</%s>", "%s>"] + + def _get_left_tag(self, block): + return block[1:].replace(">", " ", 1).split()[0].lower() + + def _get_right_tag(self, left_tag, block): + for p in self.right_tag_patterns: + tag = p % left_tag + i = block.rfind(tag) + if i > 2: + return tag.lstrip("<").rstrip(">"), i + len(p)-2 + len(left_tag) + return block.rstrip()[-len(left_tag)-2:-1].lower(), len(block) + + def _equal_tags(self, left_tag, right_tag): + if left_tag == 'div' or left_tag[0] in ['?', '@', '%']: # handle PHP, etc. + return True + if ("/" + left_tag) == right_tag: + return True + if (right_tag == "--" and left_tag == "--"): + return True + elif left_tag == right_tag[1:] \ + and right_tag[0] != "<": + return True + else: + return False + + def _is_oneliner(self, tag): + return (tag in ['hr', 'hr/']) + + def run(self, lines): + text = "\n".join(lines) + new_blocks = [] + text = text.split("\n\n") + items = [] + left_tag = '' + right_tag = '' + in_tag = False # flag + + while text: + block = text[0] + if block.startswith("\n"): + block = block[1:] + text = text[1:] + + if block.startswith("\n"): + block = block[1:] + + if not in_tag: + if block.startswith("<"): + left_tag = self._get_left_tag(block) + right_tag, data_index = self._get_right_tag(left_tag, block) + + if data_index < len(block): + text.insert(0, block[data_index:]) + block = block[:data_index] + + if not (markdown.isBlockLevel(left_tag) \ + or block[1] in ["!", "?", "@", "%"]): + new_blocks.append(block) + continue + + if self._is_oneliner(left_tag): + new_blocks.append(block.strip()) + continue + + if block[1] == "!": + # is a comment block + left_tag = "--" + right_tag, data_index = self._get_right_tag(left_tag, block) + # keep checking conditions below and maybe just append + + if block.rstrip().endswith(">") \ + and self._equal_tags(left_tag, right_tag): + new_blocks.append( + self.markdown.htmlStash.store(block.strip())) + continue + else: #if not block[1] == "!": + # if is block level tag and is not complete + + if markdown.isBlockLevel(left_tag) or left_tag == "--" \ + and not block.rstrip().endswith(">"): + items.append(block.strip()) + in_tag = True + else: + new_blocks.append( + self.markdown.htmlStash.store(block.strip())) + + continue + + new_blocks.append(block) + + else: + items.append(block.strip()) + + right_tag, data_index = self._get_right_tag(left_tag, block) + + if self._equal_tags(left_tag, right_tag): + # if find closing tag + in_tag = False + new_blocks.append( + self.markdown.htmlStash.store('\n\n'.join(items))) + items = [] + + if items: + new_blocks.append(self.markdown.htmlStash.store('\n\n'.join(items))) + new_blocks.append('\n') + + new_text = "\n\n".join(new_blocks) + return new_text.split("\n") + + +class ReferencePreprocessor(Preprocessor): + """ Remove reference definitions from text and store for later use. """ + + RE = re.compile(r'^(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)$', re.DOTALL) + + def run (self, lines): + new_text = []; + for line in lines: + m = self.RE.match(line) + if m: + id = m.group(2).strip().lower() + t = m.group(4).strip() # potential title + if not t: + self.markdown.references[id] = (m.group(3), t) + elif (len(t) >= 2 + and (t[0] == t[-1] == "\"" + or t[0] == t[-1] == "\'" + or (t[0] == "(" and t[-1] == ")") ) ): + self.markdown.references[id] = (m.group(3), t[1:-1]) + else: + new_text.append(line) + else: + new_text.append(line) + + return new_text #+ "\n" diff --git a/tools/addon-sdk-1.7/python-lib/markdown/treeprocessors.py b/tools/addon-sdk-1.7/python-lib/markdown/treeprocessors.py new file mode 100644 index 0000000..1dc612a --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/markdown/treeprocessors.py @@ -0,0 +1,329 @@ +import markdown +import re + +def isString(s): + """ Check if it's string """ + return isinstance(s, unicode) or isinstance(s, str) + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Treeprocessor(Processor): + """ + Treeprocessors are run on the ElementTree object before serialization. + + Each Treeprocessor implements a "run" method that takes a pointer to an + ElementTree, modifies it as necessary and returns an ElementTree + object. + + Treeprocessors must extend markdown.Treeprocessor. + + """ + def run(self, root): + """ + Subclasses of Treeprocessor should implement a `run` method, which + takes a root ElementTree. This method can return another ElementTree + object, and the existing root ElementTree will be replaced, or it can + modify the current tree and return None. + """ + pass + + +class InlineProcessor(Treeprocessor): + """ + A Treeprocessor that traverses a tree, applying inline patterns. + """ + + def __init__ (self, md): + self.__placeholder_prefix = markdown.INLINE_PLACEHOLDER_PREFIX + self.__placeholder_suffix = markdown.ETX + self.__placeholder_length = 4 + len(self.__placeholder_prefix) \ + + len(self.__placeholder_suffix) + self.__placeholder_re = re.compile(markdown.INLINE_PLACEHOLDER % r'([0-9]{4})') + self.markdown = md + + def __makePlaceholder(self, type): + """ Generate a placeholder """ + id = "%04d" % len(self.stashed_nodes) + hash = markdown.INLINE_PLACEHOLDER % id + return hash, id + + def __findPlaceholder(self, data, index): + """ + Extract id from data string, start from index + + Keyword arguments: + + * data: string + * index: index, from which we start search + + Returns: placeholder id and string index, after the found placeholder. + """ + + m = self.__placeholder_re.search(data, index) + if m: + return m.group(1), m.end() + else: + return None, index + 1 + + def __stashNode(self, node, type): + """ Add node to stash """ + placeholder, id = self.__makePlaceholder(type) + self.stashed_nodes[id] = node + return placeholder + + def __handleInline(self, data, patternIndex=0): + """ + Process string with inline patterns and replace it + with placeholders + + Keyword arguments: + + * data: A line of Markdown text + * patternIndex: The index of the inlinePattern to start with + + Returns: String with placeholders. + + """ + if not isinstance(data, markdown.AtomicString): + startIndex = 0 + while patternIndex < len(self.markdown.inlinePatterns): + data, matched, startIndex = self.__applyPattern( + self.markdown.inlinePatterns.value_for_index(patternIndex), + data, patternIndex, startIndex) + if not matched: + patternIndex += 1 + return data + + def __processElementText(self, node, subnode, isText=True): + """ + Process placeholders in Element.text or Element.tail + of Elements popped from self.stashed_nodes. + + Keywords arguments: + + * node: parent node + * subnode: processing node + * isText: bool variable, True - it's text, False - it's tail + + Returns: None + + """ + if isText: + text = subnode.text + subnode.text = None + else: + text = subnode.tail + subnode.tail = None + + childResult = self.__processPlaceholders(text, subnode) + + if not isText and node is not subnode: + pos = node.getchildren().index(subnode) + node.remove(subnode) + else: + pos = 0 + + childResult.reverse() + for newChild in childResult: + node.insert(pos, newChild) + + def __processPlaceholders(self, data, parent): + """ + Process string with placeholders and generate ElementTree tree. + + Keyword arguments: + + * data: string with placeholders instead of ElementTree elements. + * parent: Element, which contains processing inline data + + Returns: list with ElementTree elements with applied inline patterns. + """ + def linkText(text): + if text: + if result: + if result[-1].tail: + result[-1].tail += text + else: + result[-1].tail = text + else: + if parent.text: + parent.text += text + else: + parent.text = text + + result = [] + strartIndex = 0 + while data: + index = data.find(self.__placeholder_prefix, strartIndex) + if index != -1: + id, phEndIndex = self.__findPlaceholder(data, index) + + if id in self.stashed_nodes: + node = self.stashed_nodes.get(id) + + if index > 0: + text = data[strartIndex:index] + linkText(text) + + if not isString(node): # it's Element + for child in [node] + node.getchildren(): + if child.tail: + if child.tail.strip(): + self.__processElementText(node, child, False) + if child.text: + if child.text.strip(): + self.__processElementText(child, child) + else: # it's just a string + linkText(node) + strartIndex = phEndIndex + continue + + strartIndex = phEndIndex + result.append(node) + + else: # wrong placeholder + end = index + len(prefix) + linkText(data[strartIndex:end]) + strartIndex = end + else: + text = data[strartIndex:] + linkText(text) + data = "" + + return result + + def __applyPattern(self, pattern, data, patternIndex, startIndex=0): + """ + Check if the line fits the pattern, create the necessary + elements, add it to stashed_nodes. + + Keyword arguments: + + * data: the text to be processed + * pattern: the pattern to be checked + * patternIndex: index of current pattern + * startIndex: string index, from which we starting search + + Returns: String with placeholders instead of ElementTree elements. + + """ + match = pattern.getCompiledRegExp().match(data[startIndex:]) + leftData = data[:startIndex] + + if not match: + return data, False, 0 + + node = pattern.handleMatch(match) + + if node is None: + return data, True, len(leftData) + match.span(len(match.groups()))[0] + + if not isString(node): + if not isinstance(node.text, markdown.AtomicString): + # We need to process current node too + for child in [node] + node.getchildren(): + if not isString(node): + if child.text: + child.text = self.__handleInline(child.text, + patternIndex + 1) + if child.tail: + child.tail = self.__handleInline(child.tail, + patternIndex) + + placeholder = self.__stashNode(node, pattern.type()) + + return "%s%s%s%s" % (leftData, + match.group(1), + placeholder, match.groups()[-1]), True, 0 + + def run(self, tree): + """Apply inline patterns to a parsed Markdown tree. + + Iterate over ElementTree, find elements with inline tag, apply inline + patterns and append newly created Elements to tree. If you don't + want process your data with inline paterns, instead of normal string, + use subclass AtomicString: + + node.text = markdown.AtomicString("data won't be processed with inline patterns") + + Arguments: + + * markdownTree: ElementTree object, representing Markdown tree. + + Returns: ElementTree object with applied inline patterns. + + """ + self.stashed_nodes = {} + + stack = [tree] + + while stack: + currElement = stack.pop() + insertQueue = [] + for child in currElement.getchildren(): + if child.text and not isinstance(child.text, markdown.AtomicString): + text = child.text + child.text = None + lst = self.__processPlaceholders(self.__handleInline( + text), child) + stack += lst + insertQueue.append((child, lst)) + + if child.getchildren(): + stack.append(child) + + for element, lst in insertQueue: + if element.text: + element.text = \ + markdown.inlinepatterns.handleAttributes(element.text, + element) + i = 0 + for newChild in lst: + # Processing attributes + if newChild.tail: + newChild.tail = \ + markdown.inlinepatterns.handleAttributes(newChild.tail, + element) + if newChild.text: + newChild.text = \ + markdown.inlinepatterns.handleAttributes(newChild.text, + newChild) + element.insert(i, newChild) + i += 1 + return tree + + +class PrettifyTreeprocessor(Treeprocessor): + """ Add linebreaks to the html document. """ + + def _prettifyETree(self, elem): + """ Recursively add linebreaks to ElementTree children. """ + + i = "\n" + if markdown.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']: + if (not elem.text or not elem.text.strip()) \ + and len(elem) and markdown.isBlockLevel(elem[0].tag): + elem.text = i + for e in elem: + if markdown.isBlockLevel(e.tag): + self._prettifyETree(e) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + if not elem.tail or not elem.tail.strip(): + elem.tail = i + + def run(self, root): + """ Add linebreaks to ElementTree root object. """ + + self._prettifyETree(root) + # Do <br />'s seperately as they are often in the middle of + # inline content and missed by _prettifyETree. + brs = root.getiterator('br') + for br in brs: + if not br.tail or not br.tail.strip(): + br.tail = '\n' + else: + br.tail = '\n%s' % br.tail diff --git a/tools/addon-sdk-1.7/python-lib/mozrunner/__init__.py b/tools/addon-sdk-1.7/python-lib/mozrunner/__init__.py new file mode 100644 index 0000000..b88c734 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/mozrunner/__init__.py @@ -0,0 +1,690 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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 os +import sys +import copy +import tempfile +import signal +import commands +import zipfile +import optparse +import killableprocess +import subprocess +import platform +import shutil +from StringIO import StringIO +from xml.dom import minidom + +from distutils import dir_util +from time import sleep + +# conditional (version-dependent) imports +try: + import simplejson +except ImportError: + import json as simplejson + +import logging +logger = logging.getLogger(__name__) + +# Use dir_util for copy/rm operations because shutil is all kinds of broken +copytree = dir_util.copy_tree +rmtree = dir_util.remove_tree + +def findInPath(fileName, path=os.environ['PATH']): + dirs = path.split(os.pathsep) + for dir in dirs: + if os.path.isfile(os.path.join(dir, fileName)): + return os.path.join(dir, fileName) + if os.name == 'nt' or sys.platform == 'cygwin': + if os.path.isfile(os.path.join(dir, fileName + ".exe")): + return os.path.join(dir, fileName + ".exe") + return None + +stdout = sys.stdout +stderr = sys.stderr +stdin = sys.stdin + +def run_command(cmd, env=None, **kwargs): + """Run the given command in killable process.""" + killable_kwargs = {'stdout':stdout ,'stderr':stderr, 'stdin':stdin} + killable_kwargs.update(kwargs) + + if sys.platform != "win32": + return killableprocess.Popen(cmd, preexec_fn=lambda : os.setpgid(0, 0), + env=env, **killable_kwargs) + else: + return killableprocess.Popen(cmd, env=env, **killable_kwargs) + +def getoutput(l): + tmp = tempfile.mktemp() + x = open(tmp, 'w') + subprocess.call(l, stdout=x, stderr=x) + x.close(); x = open(tmp, 'r') + r = x.read() ; x.close() + os.remove(tmp) + return r + +def get_pids(name, minimun_pid=0): + """Get all the pids matching name, exclude any pids below minimum_pid.""" + if os.name == 'nt' or sys.platform == 'cygwin': + import wpk + + pids = wpk.get_pids(name) + + else: + data = getoutput(['ps', 'ax']).splitlines() + pids = [int(line.split()[0]) for line in data if line.find(name) is not -1] + + matching_pids = [m for m in pids if m > minimun_pid] + return matching_pids + +def makedirs(name): + + head, tail = os.path.split(name) + if not tail: + head, tail = os.path.split(head) + if head and tail and not os.path.exists(head): + try: + makedirs(head) + except OSError, e: + pass + if tail == os.curdir: # xxx/newdir/. exists if xxx/newdir exists + return + try: + os.mkdir(name) + except: + pass + +# addon_details() copied from mozprofile +def addon_details(install_rdf_fh): + """ + returns a dictionary of details about the addon + - addon_path : path to the addon directory + Returns: + {'id': u'rainbow@colors.org', # id of the addon + 'version': u'1.4', # version of the addon + 'name': u'Rainbow', # name of the addon + 'unpack': # whether to unpack the addon + """ + + details = { + 'id': None, + 'unpack': False, + 'name': None, + 'version': None + } + + def get_namespace_id(doc, url): + attributes = doc.documentElement.attributes + namespace = "" + for i in range(attributes.length): + if attributes.item(i).value == url: + if ":" in attributes.item(i).name: + # If the namespace is not the default one remove 'xlmns:' + namespace = attributes.item(i).name.split(':')[1] + ":" + break + return namespace + + def get_text(element): + """Retrieve the text value of a given node""" + rc = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc).strip() + + doc = minidom.parse(install_rdf_fh) + + # Get the namespaces abbreviations + em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#") + rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + + description = doc.getElementsByTagName(rdf + "Description").item(0) + for node in description.childNodes: + # Remove the namespace prefix from the tag for comparison + entry = node.nodeName.replace(em, "") + if entry in details.keys(): + details.update({ entry: get_text(node) }) + + # turn unpack into a true/false value + if isinstance(details['unpack'], basestring): + details['unpack'] = details['unpack'].lower() == 'true' + + return details + +class Profile(object): + """Handles all operations regarding profile. Created new profiles, installs extensions, + sets preferences and handles cleanup.""" + + def __init__(self, binary=None, profile=None, addons=None, + preferences=None): + + self.binary = binary + + self.create_new = not(bool(profile)) + if profile: + self.profile = profile + else: + self.profile = self.create_new_profile(self.binary) + + self.addons_installed = [] + self.addons = addons or [] + + ### set preferences from class preferences + preferences = preferences or {} + if hasattr(self.__class__, 'preferences'): + self.preferences = self.__class__.preferences.copy() + else: + self.preferences = {} + self.preferences.update(preferences) + + for addon in self.addons: + self.install_addon(addon) + + self.set_preferences(self.preferences) + + def create_new_profile(self, binary): + """Create a new clean profile in tmp which is a simple empty folder""" + profile = tempfile.mkdtemp(suffix='.mozrunner') + return profile + + def unpack_addon(self, xpi_zipfile, addon_path): + for name in xpi_zipfile.namelist(): + if name.endswith('/'): + makedirs(os.path.join(addon_path, name)) + else: + if not os.path.isdir(os.path.dirname(os.path.join(addon_path, name))): + makedirs(os.path.dirname(os.path.join(addon_path, name))) + data = xpi_zipfile.read(name) + f = open(os.path.join(addon_path, name), 'wb') + f.write(data) ; f.close() + zi = xpi_zipfile.getinfo(name) + os.chmod(os.path.join(addon_path,name), (zi.external_attr>>16)) + + def install_addon(self, path): + """Installs the given addon or directory of addons in the profile.""" + + extensions_path = os.path.join(self.profile, 'extensions') + if not os.path.exists(extensions_path): + os.makedirs(extensions_path) + + addons = [path] + if not path.endswith('.xpi') and not os.path.exists(os.path.join(path, 'install.rdf')): + addons = [os.path.join(path, x) for x in os.listdir(path)] + + for addon in addons: + if addon.endswith('.xpi'): + xpi_zipfile = zipfile.ZipFile(addon, "r") + details = addon_details(StringIO(xpi_zipfile.read('install.rdf'))) + addon_path = os.path.join(extensions_path, details["id"]) + if details.get("unpack", True): + self.unpack_addon(xpi_zipfile, addon_path) + self.addons_installed.append(addon_path) + else: + shutil.copy(addon, addon_path + '.xpi') + else: + # it's already unpacked, but we need to extract the id so we + # can copy it + details = addon_details(open(os.path.join(addon, "install.rdf"), "rb")) + addon_path = os.path.join(extensions_path, details["id"]) + shutil.copytree(addon, addon_path, symlinks=True) + + def set_preferences(self, preferences): + """Adds preferences dict to profile preferences""" + prefs_file = os.path.join(self.profile, 'user.js') + # Ensure that the file exists first otherwise create an empty file + if os.path.isfile(prefs_file): + f = open(prefs_file, 'a+') + else: + f = open(prefs_file, 'w') + + f.write('\n#MozRunner Prefs Start\n') + + pref_lines = ['user_pref(%s, %s);' % + (simplejson.dumps(k), simplejson.dumps(v) ) for k, v in + preferences.items()] + for line in pref_lines: + f.write(line+'\n') + f.write('#MozRunner Prefs End\n') + f.flush() ; f.close() + + def pop_preferences(self): + """ + pop the last set of preferences added + returns True if popped + """ + + # our magic markers + delimeters = ('#MozRunner Prefs Start', '#MozRunner Prefs End') + + lines = file(os.path.join(self.profile, 'user.js')).read().splitlines() + def last_index(_list, value): + """ + returns the last index of an item; + this should actually be part of python code but it isn't + """ + for index in reversed(range(len(_list))): + if _list[index] == value: + return index + s = last_index(lines, delimeters[0]) + e = last_index(lines, delimeters[1]) + + # ensure both markers are found + if s is None: + assert e is None, '%s found without %s' % (delimeters[1], delimeters[0]) + return False # no preferences found + elif e is None: + assert e is None, '%s found without %s' % (delimeters[0], delimeters[1]) + + # ensure the markers are in the proper order + assert e > s, '%s found at %s, while %s found at %s' (delimeter[1], e, delimeter[0], s) + + # write the prefs + cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) + f = file(os.path.join(self.profile, 'user.js'), 'w') + f.write(cleaned_prefs) + f.close() + return True + + def clean_preferences(self): + """Removed preferences added by mozrunner.""" + while True: + if not self.pop_preferences(): + break + + def clean_addons(self): + """Cleans up addons in the profile.""" + for addon in self.addons_installed: + if os.path.isdir(addon): + rmtree(addon) + + def cleanup(self): + """Cleanup operations on the profile.""" + def oncleanup_error(function, path, excinfo): + #TODO: How should we handle this? + print "Error Cleaning up: " + str(excinfo[1]) + if self.create_new: + shutil.rmtree(self.profile, False, oncleanup_error) + else: + self.clean_preferences() + self.clean_addons() + +class FirefoxProfile(Profile): + """Specialized Profile subclass for Firefox""" + preferences = {# Don't automatically update the application + 'app.update.enabled' : False, + # Don't restore the last open set of tabs if the browser has crashed + 'browser.sessionstore.resume_from_crash': False, + # Don't check for the default web browser + 'browser.shell.checkDefaultBrowser' : False, + # Don't warn on exit when multiple tabs are open + 'browser.tabs.warnOnClose' : False, + # Don't warn when exiting the browser + 'browser.warnOnQuit': False, + # Only install add-ons from the profile and the app folder + 'extensions.enabledScopes' : 5, + # Don't automatically update add-ons + 'extensions.update.enabled' : False, + # Don't open a dialog to show available add-on updates + 'extensions.update.notifyUser' : False, + } + + # The possible names of application bundles on Mac OS X, in order of + # preference from most to least preferred. + # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly, + # but it will still be present if users update an older nightly build + # via the app update service. + bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly'] + + # The possible names of binaries, in order of preference from most to least + # preferred. + @property + def names(self): + if sys.platform == 'darwin': + return ['firefox', 'nightly', 'shiretoko'] + if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + return ['firefox', 'mozilla-firefox', 'iceweasel'] + if os.name == 'nt' or sys.platform == 'cygwin': + return ['firefox'] + +class ThunderbirdProfile(Profile): + preferences = {'extensions.update.enabled' : False, + 'extensions.update.notifyUser' : False, + 'browser.shell.checkDefaultBrowser' : False, + 'browser.tabs.warnOnClose' : False, + 'browser.warnOnQuit': False, + 'browser.sessionstore.resume_from_crash': False, + } + + # The possible names of application bundles on Mac OS X, in order of + # preference from most to least preferred. + bundle_names = ["Thunderbird", "Shredder"] + + # The possible names of binaries, in order of preference from most to least + # preferred. + names = ["thunderbird", "shredder"] + + +class Runner(object): + """Handles all running operations. Finds bins, runs and kills the process.""" + + def __init__(self, binary=None, profile=None, cmdargs=[], env=None, + kp_kwargs={}): + if binary is None: + self.binary = self.find_binary() + elif sys.platform == 'darwin' and binary.find('Contents/MacOS/') == -1: + self.binary = os.path.join(binary, 'Contents/MacOS/%s-bin' % self.names[0]) + else: + self.binary = binary + + if not os.path.exists(self.binary): + raise Exception("Binary path does not exist "+self.binary) + + if sys.platform == 'linux2' and self.binary.endswith('-bin'): + dirname = os.path.dirname(self.binary) + if os.environ.get('LD_LIBRARY_PATH', None): + os.environ['LD_LIBRARY_PATH'] = '%s:%s' % (os.environ['LD_LIBRARY_PATH'], dirname) + else: + os.environ['LD_LIBRARY_PATH'] = dirname + + # Disable the crash reporter by default + os.environ['MOZ_CRASHREPORTER_NO_REPORT'] = '1' + + self.profile = profile + + self.cmdargs = cmdargs + if env is None: + self.env = copy.copy(os.environ) + self.env.update({'MOZ_NO_REMOTE':"1",}) + else: + self.env = env + self.kp_kwargs = kp_kwargs or {} + + def find_binary(self): + """Finds the binary for self.names if one was not provided.""" + binary = None + if sys.platform in ('linux2', 'sunos5', 'solaris'): + for name in reversed(self.names): + binary = findInPath(name) + elif os.name == 'nt' or sys.platform == 'cygwin': + + # find the default executable from the windows registry + try: + import _winreg + except ImportError: + pass + else: + sam_flags = [0] + # KEY_WOW64_32KEY etc only appeared in 2.6+, but that's OK as + # only 2.6+ has functioning 64bit builds. + if hasattr(_winreg, "KEY_WOW64_32KEY"): + if "64 bit" in sys.version: + # a 64bit Python should also look in the 32bit registry + sam_flags.append(_winreg.KEY_WOW64_32KEY) + else: + # possibly a 32bit Python on 64bit Windows, so look in + # the 64bit registry incase there is a 64bit app. + sam_flags.append(_winreg.KEY_WOW64_64KEY) + for sam_flag in sam_flags: + try: + # assumes self.app_name is defined, as it should be for + # implementors + keyname = r"Software\Mozilla\Mozilla %s" % self.app_name + sam = _winreg.KEY_READ | sam_flag + app_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, keyname, 0, sam) + version, _type = _winreg.QueryValueEx(app_key, "CurrentVersion") + version_key = _winreg.OpenKey(app_key, version + r"\Main") + path, _ = _winreg.QueryValueEx(version_key, "PathToExe") + return path + except _winreg.error: + pass + + # search for the binary in the path + for name in reversed(self.names): + binary = findInPath(name) + if sys.platform == 'cygwin': + program_files = os.environ['PROGRAMFILES'] + else: + program_files = os.environ['ProgramFiles'] + + if binary is None: + for bin in [(program_files, 'Mozilla Firefox', 'firefox.exe'), + (os.environ.get("ProgramFiles(x86)"),'Mozilla Firefox', 'firefox.exe'), + (program_files,'Nightly', 'firefox.exe'), + (os.environ.get("ProgramFiles(x86)"),'Nightly', 'firefox.exe') + ]: + path = os.path.join(*bin) + if os.path.isfile(path): + binary = path + break + elif sys.platform == 'darwin': + for bundle_name in self.bundle_names: + # Look for the application bundle in the user's home directory + # or the system-wide /Applications directory. If we don't find + # it in one of those locations, we move on to the next possible + # bundle name. + appdir = os.path.join("~/Applications/%s.app" % bundle_name) + if not os.path.isdir(appdir): + appdir = "/Applications/%s.app" % bundle_name + if not os.path.isdir(appdir): + continue + + # Look for a binary with any of the possible binary names + # inside the application bundle. + for binname in self.names: + binpath = os.path.join(appdir, + "Contents/MacOS/%s-bin" % binname) + if (os.path.isfile(binpath)): + binary = binpath + break + + if binary: + break + + if binary is None: + raise Exception('Mozrunner could not locate your binary, you will need to set it.') + return binary + + @property + def command(self): + """Returns the command list to run.""" + cmd = [self.binary, '-profile', self.profile.profile] + # On i386 OS X machines, i386+x86_64 universal binaries need to be told + # to run as i386 binaries. If we're not running a i386+x86_64 universal + # binary, then this command modification is harmless. + if sys.platform == 'darwin': + if hasattr(platform, 'architecture') and platform.architecture()[0] == '32bit': + cmd = ['arch', '-i386'] + cmd + return cmd + + def get_repositoryInfo(self): + """Read repository information from application.ini and platform.ini.""" + import ConfigParser + + config = ConfigParser.RawConfigParser() + dirname = os.path.dirname(self.binary) + repository = { } + + for entry in [['application', 'App'], ['platform', 'Build']]: + (file, section) = entry + config.read(os.path.join(dirname, '%s.ini' % file)) + + for entry in [['SourceRepository', 'repository'], ['SourceStamp', 'changeset']]: + (key, id) = entry + + try: + repository['%s_%s' % (file, id)] = config.get(section, key); + except: + repository['%s_%s' % (file, id)] = None + + return repository + + def start(self): + """Run self.command in the proper environment.""" + if self.profile is None: + self.profile = self.profile_class() + self.process_handler = run_command(self.command+self.cmdargs, self.env, **self.kp_kwargs) + + def wait(self, timeout=None): + """Wait for the browser to exit.""" + self.process_handler.wait(timeout=timeout) + + if sys.platform != 'win32': + for name in self.names: + for pid in get_pids(name, self.process_handler.pid): + self.process_handler.pid = pid + self.process_handler.wait(timeout=timeout) + + def kill(self, kill_signal=signal.SIGTERM): + """Kill the browser""" + if sys.platform != 'win32': + self.process_handler.kill() + for name in self.names: + for pid in get_pids(name, self.process_handler.pid): + self.process_handler.pid = pid + self.process_handler.kill() + else: + try: + self.process_handler.kill(group=True) + # On windows, it sometimes behooves one to wait for dust to settle + # after killing processes. Let's try that. + # TODO: Bug 640047 is invesitgating the correct way to handle this case + self.process_handler.wait(timeout=10) + except Exception, e: + logger.error('Cannot kill process, '+type(e).__name__+' '+e.message) + + def stop(self): + self.kill() + +class FirefoxRunner(Runner): + """Specialized Runner subclass for running Firefox.""" + + app_name = 'Firefox' + profile_class = FirefoxProfile + + # The possible names of application bundles on Mac OS X, in order of + # preference from most to least preferred. + # Note: Nightly is obsolete, as it has been renamed to FirefoxNightly, + # but it will still be present if users update an older nightly build + # only via the app update service. + bundle_names = ['Firefox', 'FirefoxNightly', 'Nightly'] + + @property + def names(self): + if sys.platform == 'darwin': + return ['firefox', 'nightly', 'shiretoko'] + if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + return ['firefox', 'mozilla-firefox', 'iceweasel'] + if os.name == 'nt' or sys.platform == 'cygwin': + return ['firefox'] + +class ThunderbirdRunner(Runner): + """Specialized Runner subclass for running Thunderbird""" + + app_name = 'Thunderbird' + profile_class = ThunderbirdProfile + + # The possible names of application bundles on Mac OS X, in order of + # preference from most to least preferred. + bundle_names = ["Thunderbird", "Shredder"] + + # The possible names of binaries, in order of preference from most to least + # preferred. + names = ["thunderbird", "shredder"] + +class CLI(object): + """Command line interface.""" + + runner_class = FirefoxRunner + profile_class = FirefoxProfile + module = "mozrunner" + + parser_options = {("-b", "--binary",): dict(dest="binary", help="Binary path.", + metavar=None, default=None), + ('-p', "--profile",): dict(dest="profile", help="Profile path.", + metavar=None, default=None), + ('-a', "--addons",): dict(dest="addons", + help="Addons paths to install.", + metavar=None, default=None), + ("--info",): dict(dest="info", default=False, + action="store_true", + help="Print module information") + } + + def __init__(self): + """ Setup command line parser and parse arguments """ + self.metadata = self.get_metadata_from_egg() + self.parser = optparse.OptionParser(version="%prog " + self.metadata["Version"]) + for names, opts in self.parser_options.items(): + self.parser.add_option(*names, **opts) + (self.options, self.args) = self.parser.parse_args() + + if self.options.info: + self.print_metadata() + sys.exit(0) + + # XXX should use action='append' instead of rolling our own + try: + self.addons = self.options.addons.split(',') + except: + self.addons = [] + + def get_metadata_from_egg(self): + import pkg_resources + ret = {} + dist = pkg_resources.get_distribution(self.module) + if dist.has_metadata("PKG-INFO"): + for line in dist.get_metadata_lines("PKG-INFO"): + key, value = line.split(':', 1) + ret[key] = value + if dist.has_metadata("requires.txt"): + ret["Dependencies"] = "\n" + dist.get_metadata("requires.txt") + return ret + + def print_metadata(self, data=("Name", "Version", "Summary", "Home-page", + "Author", "Author-email", "License", "Platform", "Dependencies")): + for key in data: + if key in self.metadata: + print key + ": " + self.metadata[key] + + def create_runner(self): + """ Get the runner object """ + runner = self.get_runner(binary=self.options.binary) + profile = self.get_profile(binary=runner.binary, + profile=self.options.profile, + addons=self.addons) + runner.profile = profile + return runner + + def get_runner(self, binary=None, profile=None): + """Returns the runner instance for the given command line binary argument + the profile instance returned from self.get_profile().""" + return self.runner_class(binary, profile) + + def get_profile(self, binary=None, profile=None, addons=None, preferences=None): + """Returns the profile instance for the given command line arguments.""" + addons = addons or [] + preferences = preferences or {} + return self.profile_class(binary, profile, addons, preferences) + + def run(self): + runner = self.create_runner() + self.start(runner) + runner.profile.cleanup() + + def start(self, runner): + """Starts the runner and waits for Firefox to exitor Keyboard Interrupt. + Shoule be overwritten to provide custom running of the runner instance.""" + runner.start() + print 'Started:', ' '.join(runner.command) + try: + runner.wait() + except KeyboardInterrupt: + runner.stop() + + +def cli(): + CLI().run() diff --git a/tools/addon-sdk-1.7/python-lib/mozrunner/killableprocess.py b/tools/addon-sdk-1.7/python-lib/mozrunner/killableprocess.py new file mode 100644 index 0000000..892ed87 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/mozrunner/killableprocess.py @@ -0,0 +1,316 @@ +# killableprocess - subprocesses which can be reliably killed +# +# Parts of this module are copied from the subprocess.py file contained +# in the Python distribution. +# +# Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se> +# +# Additions and modifications written by Benjamin Smedberg +# <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation +# <http://www.mozilla.org/> +# +# More Modifications +# Copyright (c) 2006-2007 by Mike Taylor <bear@code-bear.com> +# Copyright (c) 2007-2008 by Mikeal Rogers <mikeal@mozilla.com> +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of the +# author not be used in advertising or publicity pertaining to +# distribution of the software without specific, written prior +# permission. +# +# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +"""killableprocess - Subprocesses which can be reliably killed + +This module is a subclass of the builtin "subprocess" module. It allows +processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method. + +It also adds a timeout argument to Wait() for a limited period of time before +forcefully killing the process. + +Note: On Windows, this module requires Windows 2000 or higher (no support for +Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with +Python 2.5+ or available from http://python.net/crew/theller/ctypes/ +""" + +import subprocess +import sys +import os +import time +import datetime +import types +import exceptions + +try: + from subprocess import CalledProcessError +except ImportError: + # Python 2.4 doesn't implement CalledProcessError + class CalledProcessError(Exception): + """This exception is raised when a process run by check_call() returns + a non-zero exit status. The exit status will be stored in the + returncode attribute.""" + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + +mswindows = (sys.platform == "win32") + +if mswindows: + import winprocess +else: + import signal + +# This is normally defined in win32con, but we don't want +# to incur the huge tree of dependencies (pywin32 and friends) +# just to get one constant. So here's our hack +STILL_ACTIVE = 259 + +def call(*args, **kwargs): + waitargs = {} + if "timeout" in kwargs: + waitargs["timeout"] = kwargs.pop("timeout") + + return Popen(*args, **kwargs).wait(**waitargs) + +def check_call(*args, **kwargs): + """Call a program with an optional timeout. If the program has a non-zero + exit status, raises a CalledProcessError.""" + + retcode = call(*args, **kwargs) + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = args[0] + raise CalledProcessError(retcode, cmd) + +if not mswindows: + def DoNothing(*args): + pass + +class Popen(subprocess.Popen): + kill_called = False + if mswindows: + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + if not isinstance(args, types.StringTypes): + args = subprocess.list2cmdline(args) + + # Always or in the create new process group + creationflags |= winprocess.CREATE_NEW_PROCESS_GROUP + + if startupinfo is None: + startupinfo = winprocess.STARTUPINFO() + + if None not in (p2cread, c2pwrite, errwrite): + startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES + + startupinfo.hStdInput = int(p2cread) + startupinfo.hStdOutput = int(c2pwrite) + startupinfo.hStdError = int(errwrite) + if shell: + startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = winprocess.SW_HIDE + comspec = os.environ.get("COMSPEC", "cmd.exe") + args = comspec + " /c " + args + + # determine if we can create create a job + canCreateJob = winprocess.CanCreateJobObject() + + # set process creation flags + creationflags |= winprocess.CREATE_SUSPENDED + creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT + if canCreateJob: + # Uncomment this line below to discover very useful things about your environment + #print "++++ killableprocess: releng twistd patch not applied, we can create job objects" + creationflags |= winprocess.CREATE_BREAKAWAY_FROM_JOB + + # create the process + hp, ht, pid, tid = winprocess.CreateProcess( + executable, args, + None, None, # No special security + 1, # Must inherit handles! + creationflags, + winprocess.EnvironmentBlock(env), + cwd, startupinfo) + self._child_created = True + self._handle = hp + self._thread = ht + self.pid = pid + self.tid = tid + + if canCreateJob: + # We create a new job for this process, so that we can kill + # the process and any sub-processes + self._job = winprocess.CreateJobObject() + winprocess.AssignProcessToJobObject(self._job, int(hp)) + else: + self._job = None + + winprocess.ResumeThread(int(ht)) + ht.Close() + + if p2cread is not None: + p2cread.Close() + if c2pwrite is not None: + c2pwrite.Close() + if errwrite is not None: + errwrite.Close() + time.sleep(.1) + + def kill(self, group=True): + """Kill the process. If group=True, all sub-processes will also be killed.""" + self.kill_called = True + + if mswindows: + if group and self._job: + winprocess.TerminateJobObject(self._job, 127) + else: + winprocess.TerminateProcess(self._handle, 127) + self.returncode = 127 + else: + if group: + try: + os.killpg(self.pid, signal.SIGKILL) + except: pass + else: + os.kill(self.pid, signal.SIGKILL) + self.returncode = -9 + + def wait(self, timeout=None, group=True): + """Wait for the process to terminate. Returns returncode attribute. + If timeout seconds are reached and the process has not terminated, + it will be forcefully killed. If timeout is -1, wait will not + time out.""" + if timeout is not None: + # timeout is now in milliseconds + timeout = timeout * 1000 + + starttime = datetime.datetime.now() + + if mswindows: + if timeout is None: + timeout = -1 + rc = winprocess.WaitForSingleObject(self._handle, timeout) + + if (rc == winprocess.WAIT_OBJECT_0 or + rc == winprocess.WAIT_ABANDONED or + rc == winprocess.WAIT_FAILED): + # Object has either signaled, or the API call has failed. In + # both cases we want to give the OS the benefit of the doubt + # and supply a little time before we start shooting processes + # with an M-16. + + # Returns 1 if running, 0 if not, -1 if timed out + def check(): + now = datetime.datetime.now() + diff = now - starttime + if (diff.seconds * 1000 * 1000 + diff.microseconds) < (timeout * 1000): + if self._job: + if (winprocess.QueryInformationJobObject(self._job, 8)['BasicInfo']['ActiveProcesses'] > 0): + # Job Object is still containing active processes + return 1 + else: + # No job, we use GetExitCodeProcess, which will tell us if the process is still active + self.returncode = winprocess.GetExitCodeProcess(self._handle) + if (self.returncode == STILL_ACTIVE): + # Process still active, continue waiting + return 1 + # Process not active, return 0 + return 0 + else: + # Timed out, return -1 + return -1 + + notdone = check() + while notdone == 1: + time.sleep(.5) + notdone = check() + + if notdone == -1: + # Then check timed out, we have a hung process, attempt + # last ditch kill with explosives + self.kill(group) + + else: + # In this case waitforsingleobject timed out. We have to + # take the process behind the woodshed and shoot it. + self.kill(group) + + else: + if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): + def group_wait(timeout): + try: + os.waitpid(self.pid, 0) + except OSError, e: + pass # If wait has already been called on this pid, bad things happen + return self.returncode + elif sys.platform == 'darwin': + def group_wait(timeout): + try: + count = 0 + if timeout is None and self.kill_called: + timeout = 10 # Have to set some kind of timeout or else this could go on forever + if timeout is None: + while 1: + os.killpg(self.pid, signal.SIG_DFL) + while ((count * 2) <= timeout): + os.killpg(self.pid, signal.SIG_DFL) + # count is increased by 500ms for every 0.5s of sleep + time.sleep(.5); count += 500 + except exceptions.OSError: + return self.returncode + + if timeout is None: + if group is True: + return group_wait(timeout) + else: + subprocess.Popen.wait(self) + return self.returncode + + returncode = False + + now = datetime.datetime.now() + diff = now - starttime + while (diff.seconds * 1000 * 1000 + diff.microseconds) < (timeout * 1000) and ( returncode is False ): + if group is True: + return group_wait(timeout) + else: + if subprocess.poll() is not None: + returncode = self.returncode + time.sleep(.5) + now = datetime.datetime.now() + diff = now - starttime + return self.returncode + + return self.returncode + # We get random maxint errors from subprocesses __del__ + __del__ = lambda self: None + +def setpgid_preexec_fn(): + os.setpgid(0, 0) + +def runCommand(cmd, **kwargs): + if sys.platform != "win32": + return Popen(cmd, preexec_fn=setpgid_preexec_fn, **kwargs) + else: + return Popen(cmd, **kwargs) diff --git a/tools/addon-sdk-1.7/python-lib/mozrunner/qijo.py b/tools/addon-sdk-1.7/python-lib/mozrunner/qijo.py new file mode 100644 index 0000000..0580557 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/mozrunner/qijo.py @@ -0,0 +1,166 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE, addressof, c_size_t, c_ulong +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LARGE_INTEGER + +LPVOID = c_void_p +LPDWORD = POINTER(DWORD) +SIZE_T = c_size_t +ULONG_PTR = POINTER(c_ulong) + +# A ULONGLONG is a 64-bit unsigned integer. +# Thus there are 8 bytes in a ULONGLONG. +# XXX why not import c_ulonglong ? +ULONGLONG = BYTE * 8 + +class IO_COUNTERS(Structure): + # The IO_COUNTERS struct is 6 ULONGLONGs. + # TODO: Replace with non-dummy fields. + _fields_ = [('dummy', ULONGLONG * 6)] + +class JOBOBJECT_BASIC_ACCOUNTING_INFORMATION(Structure): + _fields_ = [('TotalUserTime', LARGE_INTEGER), + ('TotalKernelTime', LARGE_INTEGER), + ('ThisPeriodTotalUserTime', LARGE_INTEGER), + ('ThisPeriodTotalKernelTime', LARGE_INTEGER), + ('TotalPageFaultCount', DWORD), + ('TotalProcesses', DWORD), + ('ActiveProcesses', DWORD), + ('TotalTerminatedProcesses', DWORD)] + +class JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION(Structure): + _fields_ = [('BasicInfo', JOBOBJECT_BASIC_ACCOUNTING_INFORMATION), + ('IoInfo', IO_COUNTERS)] + +# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx +class JOBOBJECT_BASIC_LIMIT_INFORMATION(Structure): + _fields_ = [('PerProcessUserTimeLimit', LARGE_INTEGER), + ('PerJobUserTimeLimit', LARGE_INTEGER), + ('LimitFlags', DWORD), + ('MinimumWorkingSetSize', SIZE_T), + ('MaximumWorkingSetSize', SIZE_T), + ('ActiveProcessLimit', DWORD), + ('Affinity', ULONG_PTR), + ('PriorityClass', DWORD), + ('SchedulingClass', DWORD) + ] + +# see http://msdn.microsoft.com/en-us/library/ms684156%28VS.85%29.aspx +class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(Structure): + _fields_ = [('BasicLimitInformation', JOBOBJECT_BASIC_LIMIT_INFORMATION), + ('IoInfo', IO_COUNTERS), + ('ProcessMemoryLimit', SIZE_T), + ('JobMemoryLimit', SIZE_T), + ('PeakProcessMemoryUsed', SIZE_T), + ('PeakJobMemoryUsed', SIZE_T)] + +# XXX Magical numbers like 8 should be documented +JobObjectBasicAndIoAccountingInformation = 8 + +# ...like magical number 9 comes from +# http://community.flexerasoftware.com/archive/index.php?t-181670.html +# I wish I had a more canonical source +JobObjectExtendedLimitInformation = 9 + +class JobObjectInfo(object): + mapping = { 'JobObjectBasicAndIoAccountingInformation': 8, + 'JobObjectExtendedLimitInformation': 9 + } + structures = { 8: JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, + 9: JOBOBJECT_EXTENDED_LIMIT_INFORMATION + } + def __init__(self, _class): + if isinstance(_class, basestring): + assert _class in self.mapping, 'Class should be one of %s; you gave %s' % (self.mapping, _class) + _class = self.mapping[_class] + assert _class in self.structures, 'Class should be one of %s; you gave %s' % (self.structures, _class) + self.code = _class + self.info = self.structures[_class]() + + +QueryInformationJobObjectProto = WINFUNCTYPE( + BOOL, # Return type + HANDLE, # hJob + DWORD, # JobObjectInfoClass + LPVOID, # lpJobObjectInfo + DWORD, # cbJobObjectInfoLength + LPDWORD # lpReturnLength + ) + +QueryInformationJobObjectFlags = ( + (1, 'hJob'), + (1, 'JobObjectInfoClass'), + (1, 'lpJobObjectInfo'), + (1, 'cbJobObjectInfoLength'), + (1, 'lpReturnLength', None) + ) + +_QueryInformationJobObject = QueryInformationJobObjectProto( + ('QueryInformationJobObject', windll.kernel32), + QueryInformationJobObjectFlags + ) + +class SubscriptableReadOnlyStruct(object): + def __init__(self, struct): + self._struct = struct + + def _delegate(self, name): + result = getattr(self._struct, name) + if isinstance(result, Structure): + return SubscriptableReadOnlyStruct(result) + return result + + def __getitem__(self, name): + match = [fname for fname, ftype in self._struct._fields_ + if fname == name] + if match: + return self._delegate(name) + raise KeyError(name) + + def __getattr__(self, name): + return self._delegate(name) + +def QueryInformationJobObject(hJob, JobObjectInfoClass): + jobinfo = JobObjectInfo(JobObjectInfoClass) + result = _QueryInformationJobObject( + hJob=hJob, + JobObjectInfoClass=jobinfo.code, + lpJobObjectInfo=addressof(jobinfo.info), + cbJobObjectInfoLength=sizeof(jobinfo.info) + ) + if not result: + raise WinError() + return SubscriptableReadOnlyStruct(jobinfo.info) + +def test_qijo(): + from killableprocess import Popen + + popen = Popen('c:\\windows\\notepad.exe') + + try: + result = QueryInformationJobObject(0, 8) + raise AssertionError('throw should occur') + except WindowsError, e: + pass + + try: + result = QueryInformationJobObject(0, 1) + raise AssertionError('throw should occur') + except NotImplementedError, e: + pass + + result = QueryInformationJobObject(popen._job, 8) + if result['BasicInfo']['ActiveProcesses'] != 1: + raise AssertionError('expected ActiveProcesses to be 1') + popen.kill() + + result = QueryInformationJobObject(popen._job, 8) + if result.BasicInfo.ActiveProcesses != 0: + raise AssertionError('expected ActiveProcesses to be 0') + +if __name__ == '__main__': + print "testing." + test_qijo() + print "success!" diff --git a/tools/addon-sdk-1.7/python-lib/mozrunner/winprocess.py b/tools/addon-sdk-1.7/python-lib/mozrunner/winprocess.py new file mode 100644 index 0000000..4bea3fc --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/mozrunner/winprocess.py @@ -0,0 +1,383 @@ +# A module to expose various thread/process/job related structures and +# methods from kernel32 +# +# The MIT License +# +# Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se> +# +# Additions and modifications written by Benjamin Smedberg +# <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation +# <http://www.mozilla.org/> +# +# More Modifications +# Copyright (c) 2006-2007 by Mike Taylor <bear@code-bear.com> +# Copyright (c) 2007-2008 by Mikeal Rogers <mikeal@mozilla.com> +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of the +# author not be used in advertising or publicity pertaining to +# distribution of the software without specific, written prior +# permission. +# +# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE +from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD, \ + c_buffer, c_ulong, byref +from qijo import QueryInformationJobObject + +LPVOID = c_void_p +LPBYTE = POINTER(BYTE) +LPDWORD = POINTER(DWORD) +LPBOOL = POINTER(BOOL) + +def ErrCheckBool(result, func, args): + """errcheck function for Windows functions that return a BOOL True + on success""" + if not result: + raise WinError() + return args + + +# AutoHANDLE + +class AutoHANDLE(HANDLE): + """Subclass of HANDLE which will call CloseHandle() on deletion.""" + + CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE) + CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32)) + CloseHandle.errcheck = ErrCheckBool + + def Close(self): + if self.value and self.value != HANDLE(-1).value: + self.CloseHandle(self) + self.value = 0 + + def __del__(self): + self.Close() + + def __int__(self): + return self.value + +def ErrCheckHandle(result, func, args): + """errcheck function for Windows functions that return a HANDLE.""" + if not result: + raise WinError() + return AutoHANDLE(result) + +# PROCESS_INFORMATION structure + +class PROCESS_INFORMATION(Structure): + _fields_ = [("hProcess", HANDLE), + ("hThread", HANDLE), + ("dwProcessID", DWORD), + ("dwThreadID", DWORD)] + + def __init__(self): + Structure.__init__(self) + + self.cb = sizeof(self) + +LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) + +# STARTUPINFO structure + +class STARTUPINFO(Structure): + _fields_ = [("cb", DWORD), + ("lpReserved", LPWSTR), + ("lpDesktop", LPWSTR), + ("lpTitle", LPWSTR), + ("dwX", DWORD), + ("dwY", DWORD), + ("dwXSize", DWORD), + ("dwYSize", DWORD), + ("dwXCountChars", DWORD), + ("dwYCountChars", DWORD), + ("dwFillAttribute", DWORD), + ("dwFlags", DWORD), + ("wShowWindow", WORD), + ("cbReserved2", WORD), + ("lpReserved2", LPBYTE), + ("hStdInput", HANDLE), + ("hStdOutput", HANDLE), + ("hStdError", HANDLE) + ] +LPSTARTUPINFO = POINTER(STARTUPINFO) + +SW_HIDE = 0 + +STARTF_USESHOWWINDOW = 0x01 +STARTF_USESIZE = 0x02 +STARTF_USEPOSITION = 0x04 +STARTF_USECOUNTCHARS = 0x08 +STARTF_USEFILLATTRIBUTE = 0x10 +STARTF_RUNFULLSCREEN = 0x20 +STARTF_FORCEONFEEDBACK = 0x40 +STARTF_FORCEOFFFEEDBACK = 0x80 +STARTF_USESTDHANDLES = 0x100 + +# EnvironmentBlock + +class EnvironmentBlock: + """An object which can be passed as the lpEnv parameter of CreateProcess. + It is initialized with a dictionary.""" + + def __init__(self, dict): + if not dict: + self._as_parameter_ = None + else: + values = ["%s=%s" % (key, value) + for (key, value) in dict.iteritems()] + values.append("") + self._as_parameter_ = LPCWSTR("\0".join(values)) + +# CreateProcess() + +CreateProcessProto = WINFUNCTYPE(BOOL, # Return type + LPCWSTR, # lpApplicationName + LPWSTR, # lpCommandLine + LPVOID, # lpProcessAttributes + LPVOID, # lpThreadAttributes + BOOL, # bInheritHandles + DWORD, # dwCreationFlags + LPVOID, # lpEnvironment + LPCWSTR, # lpCurrentDirectory + LPSTARTUPINFO, # lpStartupInfo + LPPROCESS_INFORMATION # lpProcessInformation + ) + +CreateProcessFlags = ((1, "lpApplicationName", None), + (1, "lpCommandLine"), + (1, "lpProcessAttributes", None), + (1, "lpThreadAttributes", None), + (1, "bInheritHandles", True), + (1, "dwCreationFlags", 0), + (1, "lpEnvironment", None), + (1, "lpCurrentDirectory", None), + (1, "lpStartupInfo"), + (2, "lpProcessInformation")) + +def ErrCheckCreateProcess(result, func, args): + ErrCheckBool(result, func, args) + # return a tuple (hProcess, hThread, dwProcessID, dwThreadID) + pi = args[9] + return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID + +CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32), + CreateProcessFlags) +CreateProcess.errcheck = ErrCheckCreateProcess + +# flags for CreateProcess +CREATE_BREAKAWAY_FROM_JOB = 0x01000000 +CREATE_DEFAULT_ERROR_MODE = 0x04000000 +CREATE_NEW_CONSOLE = 0x00000010 +CREATE_NEW_PROCESS_GROUP = 0x00000200 +CREATE_NO_WINDOW = 0x08000000 +CREATE_SUSPENDED = 0x00000004 +CREATE_UNICODE_ENVIRONMENT = 0x00000400 + +# flags for job limit information +# see http://msdn.microsoft.com/en-us/library/ms684147%28VS.85%29.aspx +JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 +JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK = 0x00001000 + +# XXX these flags should be documented +DEBUG_ONLY_THIS_PROCESS = 0x00000002 +DEBUG_PROCESS = 0x00000001 +DETACHED_PROCESS = 0x00000008 + +# CreateJobObject() + +CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type + LPVOID, # lpJobAttributes + LPCWSTR # lpName + ) + +CreateJobObjectFlags = ((1, "lpJobAttributes", None), + (1, "lpName", None)) + +CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32), + CreateJobObjectFlags) +CreateJobObject.errcheck = ErrCheckHandle + +# AssignProcessToJobObject() + +AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type + HANDLE, # hJob + HANDLE # hProcess + ) +AssignProcessToJobObjectFlags = ((1, "hJob"), + (1, "hProcess")) +AssignProcessToJobObject = AssignProcessToJobObjectProto( + ("AssignProcessToJobObject", windll.kernel32), + AssignProcessToJobObjectFlags) +AssignProcessToJobObject.errcheck = ErrCheckBool + +# GetCurrentProcess() +# because os.getPid() is way too easy +GetCurrentProcessProto = WINFUNCTYPE(HANDLE # Return type + ) +GetCurrentProcessFlags = () +GetCurrentProcess = GetCurrentProcessProto( + ("GetCurrentProcess", windll.kernel32), + GetCurrentProcessFlags) +GetCurrentProcess.errcheck = ErrCheckHandle + +# IsProcessInJob() +try: + IsProcessInJobProto = WINFUNCTYPE(BOOL, # Return type + HANDLE, # Process Handle + HANDLE, # Job Handle + LPBOOL # Result + ) + IsProcessInJobFlags = ((1, "ProcessHandle"), + (1, "JobHandle", HANDLE(0)), + (2, "Result")) + IsProcessInJob = IsProcessInJobProto( + ("IsProcessInJob", windll.kernel32), + IsProcessInJobFlags) + IsProcessInJob.errcheck = ErrCheckBool +except AttributeError: + # windows 2k doesn't have this API + def IsProcessInJob(process): + return False + + +# ResumeThread() + +def ErrCheckResumeThread(result, func, args): + if result == -1: + raise WinError() + + return args + +ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type + HANDLE # hThread + ) +ResumeThreadFlags = ((1, "hThread"),) +ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32), + ResumeThreadFlags) +ResumeThread.errcheck = ErrCheckResumeThread + +# TerminateProcess() + +TerminateProcessProto = WINFUNCTYPE(BOOL, # Return type + HANDLE, # hProcess + UINT # uExitCode + ) +TerminateProcessFlags = ((1, "hProcess"), + (1, "uExitCode", 127)) +TerminateProcess = TerminateProcessProto( + ("TerminateProcess", windll.kernel32), + TerminateProcessFlags) +TerminateProcess.errcheck = ErrCheckBool + +# TerminateJobObject() + +TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type + HANDLE, # hJob + UINT # uExitCode + ) +TerminateJobObjectFlags = ((1, "hJob"), + (1, "uExitCode", 127)) +TerminateJobObject = TerminateJobObjectProto( + ("TerminateJobObject", windll.kernel32), + TerminateJobObjectFlags) +TerminateJobObject.errcheck = ErrCheckBool + +# WaitForSingleObject() + +WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type + HANDLE, # hHandle + DWORD, # dwMilliseconds + ) +WaitForSingleObjectFlags = ((1, "hHandle"), + (1, "dwMilliseconds", -1)) +WaitForSingleObject = WaitForSingleObjectProto( + ("WaitForSingleObject", windll.kernel32), + WaitForSingleObjectFlags) + +INFINITE = -1 +WAIT_TIMEOUT = 0x0102 +WAIT_OBJECT_0 = 0x0 +WAIT_ABANDONED = 0x0080 +WAIT_FAILED = 0xFFFFFFFF + +# GetExitCodeProcess() + +GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type + HANDLE, # hProcess + LPDWORD, # lpExitCode + ) +GetExitCodeProcessFlags = ((1, "hProcess"), + (2, "lpExitCode")) +GetExitCodeProcess = GetExitCodeProcessProto( + ("GetExitCodeProcess", windll.kernel32), + GetExitCodeProcessFlags) +GetExitCodeProcess.errcheck = ErrCheckBool + +def CanCreateJobObject(): + currentProc = GetCurrentProcess() + if IsProcessInJob(currentProc): + jobinfo = QueryInformationJobObject(HANDLE(0), 'JobObjectExtendedLimitInformation') + limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] + return bool(limitflags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) or bool(limitflags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) + else: + return True + +### testing functions + +def parent(): + print 'Starting parent' + currentProc = GetCurrentProcess() + if IsProcessInJob(currentProc): + print >> sys.stderr, "You should not be in a job object to test" + sys.exit(1) + assert CanCreateJobObject() + print 'File: %s' % __file__ + command = [sys.executable, __file__, '-child'] + print 'Running command: %s' % command + process = Popen(command) + process.kill() + code = process.returncode + print 'Child code: %s' % code + assert code == 127 + +def child(): + print 'Starting child' + currentProc = GetCurrentProcess() + injob = IsProcessInJob(currentProc) + print "Is in a job?: %s" % injob + can_create = CanCreateJobObject() + print 'Can create job?: %s' % can_create + process = Popen('c:\\windows\\notepad.exe') + assert process._job + jobinfo = QueryInformationJobObject(process._job, 'JobObjectExtendedLimitInformation') + print 'Job info: %s' % jobinfo + limitflags = jobinfo['BasicLimitInformation']['LimitFlags'] + print 'LimitFlags: %s' % limitflags + process.kill() + +if __name__ == '__main__': + import sys + from killableprocess import Popen + nargs = len(sys.argv[1:]) + if nargs: + if nargs != 1 or sys.argv[1] != '-child': + raise AssertionError('Wrong flags; run like `python /path/to/winprocess.py`') + child() + else: + parent() diff --git a/tools/addon-sdk-1.7/python-lib/mozrunner/wpk.py b/tools/addon-sdk-1.7/python-lib/mozrunner/wpk.py new file mode 100644 index 0000000..6c92f5d --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/mozrunner/wpk.py @@ -0,0 +1,80 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +from ctypes import sizeof, windll, addressof, c_wchar, create_unicode_buffer +from ctypes.wintypes import DWORD, HANDLE + +PROCESS_TERMINATE = 0x0001 +PROCESS_QUERY_INFORMATION = 0x0400 +PROCESS_VM_READ = 0x0010 + +def get_pids(process_name): + BIG_ARRAY = DWORD * 4096 + processes = BIG_ARRAY() + needed = DWORD() + + pids = [] + result = windll.psapi.EnumProcesses(processes, + sizeof(processes), + addressof(needed)) + if not result: + return pids + + num_results = needed.value / sizeof(DWORD) + + for i in range(num_results): + pid = processes[i] + process = windll.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ, + 0, pid) + if process: + module = HANDLE() + result = windll.psapi.EnumProcessModules(process, + addressof(module), + sizeof(module), + addressof(needed)) + if result: + name = create_unicode_buffer(1024) + result = windll.psapi.GetModuleBaseNameW(process, module, + name, len(name)) + # TODO: This might not be the best way to + # match a process name; maybe use a regexp instead. + if name.value.startswith(process_name): + pids.append(pid) + windll.kernel32.CloseHandle(module) + windll.kernel32.CloseHandle(process) + + return pids + +def kill_pid(pid): + process = windll.kernel32.OpenProcess(PROCESS_TERMINATE, 0, pid) + if process: + windll.kernel32.TerminateProcess(process, 0) + windll.kernel32.CloseHandle(process) + +if __name__ == '__main__': + import subprocess + import time + + # This test just opens a new notepad instance and kills it. + + name = 'notepad' + + old_pids = set(get_pids(name)) + subprocess.Popen([name]) + time.sleep(0.25) + new_pids = set(get_pids(name)).difference(old_pids) + + if len(new_pids) != 1: + raise Exception('%s was not opened or get_pids() is ' + 'malfunctioning' % name) + + kill_pid(tuple(new_pids)[0]) + + newest_pids = set(get_pids(name)).difference(old_pids) + + if len(newest_pids) != 0: + raise Exception('kill_pid() is malfunctioning') + + print "Test passed." diff --git a/tools/addon-sdk-1.7/python-lib/plural-rules-generator.py b/tools/addon-sdk-1.7/python-lib/plural-rules-generator.py new file mode 100644 index 0000000..5776fd8 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/plural-rules-generator.py @@ -0,0 +1,174 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# 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/. + +# Program used to generate /packages/api-utils/lib/l10n/plural-rules.js +# Fetch unicode.org data in order to build functions specific to each language +# that will return for a given integer, its plural form name. +# Plural form names are: zero, one, two, few, many, other. +# +# More information here: +# http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +# http://cldr.unicode.org/index/cldr-spec/plural-rules + +# Usage: +# $ python plural-rules-generator.py > ../packages/api-utils/lib/l10n/plural-rules.js + +import urllib2 +import xml.dom.minidom +import json +import re + +PRINT_CONDITIONS_IN_COMMENTS = False + +UNICODE_ORG_XML_URL = "http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml" + +CONDITION_RE = r'n( mod \d+)? (is|in|within|(not in))( not)? ([^\s]+)' + +# For a given regexp.MatchObject `g` for `CONDITION_RE`, +# returns the equivalent JS piece of code +# i.e. maps pseudo conditional language from unicode.org XML to JS code +def parseCondition(g): + lvalue = "n" + if g.group(1): + lvalue = "(n %% %d)" % int(g.group(1).replace("mod ", "")) + + operator = g.group(2) + if g.group(4): + operator += " not" + + rvalue = g.group(5) + + if operator == "is": + return "%s == %s" % (lvalue, rvalue) + if operator == "is not": + return "%s != %s" % (lvalue, rvalue) + + # "in", "within" or "not in" case: + notPrefix = "" + if operator == "not in": + notPrefix = "!" + + # `rvalue` is a comma seperated list of either: + # - numbers: 42 + # - ranges: 42..72 + sections = rvalue.split(',') + + if ".." not in rvalue: + # If we don't have range, but only a list of integer, + # we can simplify the generated code by using `isIn` + # n in 1,3,6,42 + return "%sisIn(%s, [%s])" % (notPrefix, lvalue, ", ".join(sections)) + + # n in 1..42 + # n in 1..3,42 + subCondition = [] + integers = [] + for sub in sections: + if ".." in sub: + left, right = sub.split("..") + subCondition.append("isBetween(%s, %d, %d)" % ( + lvalue, + int(left), + int(right) + )) + else: + integers.append(int(sub)) + if len(integers) > 1: + subCondition.append("isIn(%s, [%s])" % (lvalue, ", ".join(integers))) + elif len(integers) == 1: + subCondition.append("(%s == %s)" % (lvalue, integers[0])) + return "%s(%s)" % (notPrefix, " || ".join(subCondition)) + +def computeRules(): + # Fetch plural rules data directly from unicode.org website: + url = UNICODE_ORG_XML_URL + f = urllib2.urlopen(url) + doc = xml.dom.minidom.parse(f) + + # Read XML document and extract locale to rules mapping + localesMapping = {} + algorithms = {} + for index,pluralRules in enumerate(doc.getElementsByTagName("pluralRules")): + if not index in algorithms: + algorithms[index] = {} + for locale in pluralRules.getAttribute("locales").split(): + localesMapping[locale] = index + for rule in pluralRules.childNodes: + if rule.nodeType != rule.ELEMENT_NODE or rule.tagName != "pluralRule": + continue + pluralForm = rule.getAttribute("count") + algorithm = rule.firstChild.nodeValue + algorithms[index][pluralForm] = algorithm + + # Go through all rules and compute a Javascript code for each of them + rules = {} + for index,rule in algorithms.iteritems(): + lines = [] + for pluralForm in rule: + condition = rule[pluralForm] + originalCondition = str(condition) + + # Convert pseudo language to JS code + condition = rule[pluralForm].lower() + condition = re.sub(CONDITION_RE, parseCondition, condition) + condition = re.sub(r'or', "||", condition) + condition = re.sub(r'and', "&&", condition) + + # Prints original condition in unicode.org pseudo language + if PRINT_CONDITIONS_IN_COMMENTS: + lines.append( '// %s' % originalCondition ) + + lines.append( 'if (%s)' % condition ) + lines.append( ' return "%s";' % pluralForm ) + + rules[index] = "\n ".join(lines) + return localesMapping, rules + + +localesMapping, rules = computeRules() + +rulesLines = [] +for index in rules: + lines = rules[index] + rulesLines.append('"%d": function (n) {' % index) + rulesLines.append(' %s' % lines) + rulesLines.append(' return "other"') + rulesLines.append('},') + +print """/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +// This file is automatically generated with /python-lib/plural-rules-generator.py +// Fetching data from: %s + +// Mapping of short locale name == to == > rule index in following list +const LOCALES_TO_RULES = %s; + +// Utility functions for plural rules methods +function isIn(n, list) list.indexOf(n) !== -1; +function isBetween(n, start, end) start <= n && n <= end; + +// List of all plural rules methods, that maps an integer to the plural form name to use +const RULES = { + %s +}; + +/** + * Return a function that gives the plural form name for a given integer + * for the specified `locale` + * let fun = getRulesForLocale('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other' + */ +exports.getRulesForLocale = function getRulesForLocale(locale) { + let index = LOCALES_TO_RULES[locale]; + if (!(index in RULES)) + throw new Error('Plural form unknown for locale \"' + locale + '\"'); + return RULES[index]; +} +""" % (UNICODE_ORG_XML_URL, + json.dumps(localesMapping, sort_keys=True, indent=2), + "\n ".join(rulesLines)) diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/LICENSE.txt b/tools/addon-sdk-1.7/python-lib/simplejson/LICENSE.txt new file mode 100644 index 0000000..ad95f29 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2006 Bob Ippolito + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/__init__.py b/tools/addon-sdk-1.7/python-lib/simplejson/__init__.py new file mode 100644 index 0000000..adcce7e --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/__init__.py @@ -0,0 +1,376 @@ +r""" +A simple, fast, extensible JSON encoder and decoder + +JSON (JavaScript Object Notation) <http://json.org> is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +simplejson exposes an API familiar to uses of the standard library +marshal and pickle modules. + +Encoding basic Python object hierarchies:: + + >>> import simplejson + >>> simplejson.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print simplejson.dumps("\"foo\bar") + "\"foo\bar" + >>> print simplejson.dumps(u'\u1234') + "\u1234" + >>> print simplejson.dumps('\\') + "\\" + >>> print simplejson.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> simplejson.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson + >>> simplejson.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson + >>> print simplejson.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson + >>> simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') + [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> simplejson.loads('"\\"foo\\bar"') + u'"foo\x08ar' + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> simplejson.load(io) + [u'streaming API'] + +Specializing JSON object decoding:: + + >>> import simplejson + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> simplejson.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> import decimal + >>> simplejson.loads('1.1', parse_float=decimal.Decimal) + Decimal("1.1") + +Extending JSONEncoder:: + + >>> import simplejson + >>> class ComplexEncoder(simplejson.JSONEncoder): + ... def default(self, obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... return simplejson.JSONEncoder.default(self, obj) + ... + >>> dumps(2 + 1j, cls=ComplexEncoder) + '[2.0, 1.0]' + >>> ComplexEncoder().encode(2 + 1j) + '[2.0, 1.0]' + >>> list(ComplexEncoder().iterencode(2 + 1j)) + ['[', '2.0', ', ', '1.0', ']'] + + +Using simplejson from the shell to validate and +pretty-print:: + + $ echo '{"json":"obj"}' | python -msimplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -msimplejson.tool + Expecting property name: line 1 column 2 (char 2) + +Note that the JSON produced by this module's default settings +is a subset of YAML, so it may be used as a serializer for that as well. +""" +__version__ = '1.9.2' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONEncoder', +] + +if __name__ == '__main__': + import warnings + warnings.warn('python -msimplejson is deprecated, use python -msiplejson.tool', DeprecationWarning) + from simplejson.decoder import JSONDecoder + from simplejson.encoder import JSONEncoder +else: + from decoder import JSONDecoder + from encoder import JSONEncoder + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """ + Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and object + members will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. ``None`` is the most compact representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + """ + # cached encoder + if (skipkeys is False and ensure_ascii is True and + check_circular is True and allow_nan is True and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """ + Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + """ + # cached encoder + if (skipkeys is False and ensure_ascii is True and + check_circular is True and allow_nan is True and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """ + Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + If the contents of ``fp`` is encoded with an ASCII based encoding other + than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must + be specified. Encodings that are not ASCII based (such as UCS-2) are + not allowed, and should be wrapped with + ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` + object and passed to ``loads()`` + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """ + Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding + other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name + must be specified. Encodings that are not ASCII based (such as UCS-2) + are not allowed and should be decoded to ``unicode`` first. + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN, null, true, false. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + return cls(encoding=encoding, **kw).decode(s) + + +# +# Compatibility cruft from other libraries +# + + +def decode(s): + """ + demjson, python-cjson API compatibility hook. Use loads(s) instead. + """ + import warnings + warnings.warn("simplejson.loads(s) should be used instead of decode(s)", + DeprecationWarning) + return loads(s) + + +def encode(obj): + """ + demjson, python-cjson compatibility hook. Use dumps(s) instead. + """ + import warnings + warnings.warn("simplejson.dumps(s) should be used instead of encode(s)", + DeprecationWarning) + return dumps(obj) + + +def read(s): + """ + jsonlib, JsonUtils, python-json, json-py API compatibility hook. + Use loads(s) instead. + """ + import warnings + warnings.warn("simplejson.loads(s) should be used instead of read(s)", + DeprecationWarning) + return loads(s) + + +def write(obj): + """ + jsonlib, JsonUtils, python-json, json-py API compatibility hook. + Use dumps(s) instead. + """ + import warnings + warnings.warn("simplejson.dumps(s) should be used instead of write(s)", + DeprecationWarning) + return dumps(obj) + + +if __name__ == '__main__': + import simplejson.tool + simplejson.tool.main() diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/decoder.py b/tools/addon-sdk-1.7/python-lib/simplejson/decoder.py new file mode 100644 index 0000000..baf10e9 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/decoder.py @@ -0,0 +1,343 @@ +""" +Implementation of JSONDecoder +""" +import re +import sys + +from simplejson.scanner import Scanner, pattern +try: + from simplejson._speedups import scanstring as c_scanstring +except ImportError: + pass + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + import struct + import sys + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + lineno, colno = linecol(doc, pos) + if end is None: + return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( + msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, + 'true': True, + 'false': False, + 'null': None, +} + +def JSONConstant(match, context, c=_CONSTANTS): + s = match.group(0) + fn = getattr(context, 'parse_constant', None) + if fn is None: + rval = c[s] + else: + rval = fn(s) + return rval, None +pattern('(-?Infinity|NaN|true|false|null)')(JSONConstant) + + +def JSONNumber(match, context): + match = JSONNumber.regex.match(match.string, *match.span()) + integer, frac, exp = match.groups() + if frac or exp: + fn = getattr(context, 'parse_float', None) or float + res = fn(integer + (frac or '') + (exp or '')) + else: + fn = getattr(context, 'parse_int', None) or int + res = fn(integer) + return res, None +pattern(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?')(JSONNumber) + + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + end = chunk.end() + content, terminator = chunk.groups() + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + if terminator == '"': + break + elif terminator != '\\': + if strict: + raise ValueError(errmsg("Invalid control character %r at", s, end)) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + if esc != 'u': + try: + m = _b[esc] + except KeyError: + raise ValueError( + errmsg("Invalid \\escape: %r" % (esc,), s, end)) + end += 1 + else: + esc = s[end + 1:end + 5] + next_end = end + 5 + msg = "Invalid \\uXXXX escape" + try: + if len(esc) != 4: + raise ValueError + uni = int(esc, 16) + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise ValueError + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise ValueError + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + m = unichr(uni) + except ValueError: + raise ValueError(errmsg(msg, s, end)) + end = next_end + _append(m) + return u''.join(chunks), end + + +# Use speedup +try: + scanstring = c_scanstring +except NameError: + scanstring = py_scanstring + +def JSONString(match, context): + encoding = getattr(context, 'encoding', None) + strict = getattr(context, 'strict', True) + return scanstring(match.string, match.end(), encoding, strict) +pattern(r'"')(JSONString) + + +WHITESPACE = re.compile(r'\s*', FLAGS) + +def JSONObject(match, context, _w=WHITESPACE.match): + pairs = {} + s = match.string + end = _w(s, match.end()).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + return pairs, end + 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end)) + end += 1 + encoding = getattr(context, 'encoding', None) + strict = getattr(context, 'strict', True) + iterscan = JSONScanner.iterscan + while True: + key, end = scanstring(s, end, encoding, strict) + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting : delimiter", s, end)) + end = _w(s, end + 1).end() + try: + value, end = iterscan(s, idx=end, context=context).next() + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + pairs[key] = value + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == '}': + break + if nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end - 1)) + object_hook = getattr(context, 'object_hook', None) + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end +pattern(r'{')(JSONObject) + + +def JSONArray(match, context, _w=WHITESPACE.match): + values = [] + s = match.string + end = _w(s, match.end()).end() + # Look-ahead for trivial empty array + nextchar = s[end:end + 1] + if nextchar == ']': + return values, end + 1 + iterscan = JSONScanner.iterscan + while True: + try: + value, end = iterscan(s, idx=end, context=context).next() + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + values.append(value) + end = _w(s, end).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + if nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end)) + end = _w(s, end).end() + return values, end +pattern(r'\[')(JSONArray) + + +ANYTHING = [ + JSONObject, + JSONArray, + JSONString, + JSONConstant, + JSONNumber, +] + +JSONScanner = Scanner(ANYTHING) + + +class JSONDecoder(object): + """ + Simple JSON <http://json.org> decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + """ + + _scanner = Scanner(ANYTHING) + __all__ = ['__init__', 'decode', 'raw_decode'] + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True): + """ + ``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN, null, true, false. + This can be used to raise an exception if invalid JSON numbers + are encountered. + """ + self.encoding = encoding + self.object_hook = object_hook + self.parse_float = parse_float + self.parse_int = parse_int + self.parse_constant = parse_constant + self.strict = strict + + def decode(self, s, _w=WHITESPACE.match): + """ + Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, **kw): + """ + Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning + with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + """ + kw.setdefault('context', self) + try: + obj, end = self._scanner.iterscan(s, **kw).next() + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end + +__all__ = ['JSONDecoder'] diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/encoder.py b/tools/addon-sdk-1.7/python-lib/simplejson/encoder.py new file mode 100644 index 0000000..772a261 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/encoder.py @@ -0,0 +1,385 @@ +""" +Implementation of JSONEncoder +""" +import re + +try: + from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii +except ImportError: + pass + +ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(0x20): + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +# Assume this produces an infinity on all machines (probably not guaranteed) +INFINITY = float('1e66666') +FLOAT_REPR = repr + +def floatstr(o, allow_nan=True): + # Check for specials. Note that this type of test is processor- and/or + # platform-specific, so do tests which don't depend on the internals. + + if o != o: + text = 'NaN' + elif o == INFINITY: + text = 'Infinity' + elif o == -INFINITY: + text = '-Infinity' + else: + return FLOAT_REPR(o) + + if not allow_nan: + raise ValueError("Out of range float values are not JSON compliant: %r" + % (o,)) + + return text + + +def encode_basestring(s): + """ + Return a JSON representation of a Python string + """ + def replace(match): + return ESCAPE_DCT[match.group(0)] + return '"' + ESCAPE.sub(replace, s) + '"' + + +def py_encode_basestring_ascii(s): + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +try: + encode_basestring_ascii = c_encode_basestring_ascii +except NameError: + encode_basestring_ascii = py_encode_basestring_ascii + + +class JSONEncoder(object): + """ + Extensible JSON <http://json.org> encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + """ + __all__ = ['__init__', 'default', 'encode', 'iterencode'] + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """ + Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is False, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is True, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is True, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is True, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is True, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + self.current_indent_level = 0 + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def _newline_indent(self): + return '\n' + (' ' * (self.indent * self.current_indent_level)) + + def _iterencode_list(self, lst, markers=None): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + yield '[' + if self.indent is not None: + self.current_indent_level += 1 + newline_indent = self._newline_indent() + separator = self.item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + separator = self.item_separator + first = True + for value in lst: + if first: + first = False + else: + yield separator + for chunk in self._iterencode(value, markers): + yield chunk + if newline_indent is not None: + self.current_indent_level -= 1 + yield self._newline_indent() + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(self, dct, markers=None): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + key_separator = self.key_separator + if self.indent is not None: + self.current_indent_level += 1 + newline_indent = self._newline_indent() + item_separator = self.item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = self.item_separator + first = True + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + allow_nan = self.allow_nan + if self.sort_keys: + keys = dct.keys() + keys.sort() + items = [(k, dct[k]) for k in keys] + else: + items = dct.iteritems() + _encoding = self.encoding + _do_decode = (_encoding is not None + and not (_encoding == 'utf-8')) + for key, value in items: + if isinstance(key, str): + if _do_decode: + key = key.decode(_encoding) + elif isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = floatstr(key, allow_nan) + elif isinstance(key, (int, long)): + key = str(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif self.skipkeys: + continue + else: + raise TypeError("key %r is not a string" % (key,)) + if first: + first = False + else: + yield item_separator + yield encoder(key) + yield key_separator + for chunk in self._iterencode(value, markers): + yield chunk + if newline_indent is not None: + self.current_indent_level -= 1 + yield self._newline_indent() + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(self, o, markers=None): + if isinstance(o, basestring): + if self.ensure_ascii: + encoder = encode_basestring_ascii + else: + encoder = encode_basestring + _encoding = self.encoding + if (_encoding is not None and isinstance(o, str) + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + yield encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield floatstr(o, self.allow_nan) + elif isinstance(o, (list, tuple)): + for chunk in self._iterencode_list(o, markers): + yield chunk + elif isinstance(o, dict): + for chunk in self._iterencode_dict(o, markers): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + for chunk in self._iterencode_default(o, markers): + yield chunk + if markers is not None: + del markers[markerid] + + def _iterencode_default(self, o, markers=None): + newobj = self.default(o) + return self._iterencode(newobj, markers) + + def default(self, o): + """ + Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + """ + raise TypeError("%r is not JSON serializable" % (o,)) + + def encode(self, o): + """ + Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = list(self.iterencode(o)) + return ''.join(chunks) + + def iterencode(self, o): + """ + Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + """ + if self.check_circular: + markers = {} + else: + markers = None + return self._iterencode(o, markers) + +__all__ = ['JSONEncoder'] diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/scanner.py b/tools/addon-sdk-1.7/python-lib/simplejson/scanner.py new file mode 100644 index 0000000..2a18390 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/scanner.py @@ -0,0 +1,67 @@ +""" +Iterator based sre token scanner +""" +import re +from re import VERBOSE, MULTILINE, DOTALL +import sre_parse +import sre_compile +import sre_constants +from sre_constants import BRANCH, SUBPATTERN + +__all__ = ['Scanner', 'pattern'] + +FLAGS = (VERBOSE | MULTILINE | DOTALL) + +class Scanner(object): + def __init__(self, lexicon, flags=FLAGS): + self.actions = [None] + # Combine phrases into a compound pattern + s = sre_parse.Pattern() + s.flags = flags + p = [] + for idx, token in enumerate(lexicon): + phrase = token.pattern + try: + subpattern = sre_parse.SubPattern(s, + [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) + except sre_constants.error: + raise + p.append(subpattern) + self.actions.append(token) + + s.groups = len(p) + 1 # NOTE(guido): Added to make SRE validation work + p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) + self.scanner = sre_compile.compile(p) + + def iterscan(self, string, idx=0, context=None): + """ + Yield match, end_idx for each match + """ + match = self.scanner.scanner(string, idx).match + actions = self.actions + lastend = idx + end = len(string) + while True: + m = match() + if m is None: + break + matchbegin, matchend = m.span() + if lastend == matchend: + break + action = actions[m.lastindex] + if action is not None: + rval, next_pos = action(m, context) + if next_pos is not None and next_pos != matchend: + # "fast forward" the scanner + matchend = next_pos + match = self.scanner.scanner(string, matchend).match + yield rval, matchend + lastend = matchend + + +def pattern(pattern, flags=FLAGS): + def decorator(fn): + fn.pattern = pattern + fn.regex = re.compile(pattern, flags) + return fn + return decorator
\ No newline at end of file diff --git a/tools/addon-sdk-1.7/python-lib/simplejson/tool.py b/tools/addon-sdk-1.7/python-lib/simplejson/tool.py new file mode 100644 index 0000000..caa1818 --- /dev/null +++ b/tools/addon-sdk-1.7/python-lib/simplejson/tool.py @@ -0,0 +1,44 @@ +r""" +Using simplejson from the shell to validate and +pretty-print:: + + $ echo '{"json":"obj"}' | python -msimplejson + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -msimplejson + Expecting property name: line 1 column 2 (char 2) + +Note that the JSON produced by this module's default settings +is a subset of YAML, so it may be used as a serializer for that as well. +""" +import simplejson + +# +# Pretty printer: +# curl http://mochikit.com/examples/ajax_tables/domains.json | python -msimplejson.tool +# + +def main(): + import sys + if len(sys.argv) == 1: + infile = sys.stdin + outfile = sys.stdout + elif len(sys.argv) == 2: + infile = open(sys.argv[1], 'rb') + outfile = sys.stdout + elif len(sys.argv) == 3: + infile = open(sys.argv[1], 'rb') + outfile = open(sys.argv[2], 'wb') + else: + raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) + try: + obj = simplejson.load(infile) + except ValueError, e: + raise SystemExit(e) + simplejson.dump(obj, outfile, sort_keys=True, indent=4) + outfile.write('\n') + + +if __name__ == '__main__': + main() |