aboutsummaryrefslogtreecommitdiff
path: root/tools/addon-sdk-1.3/python-lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/addon-sdk-1.3/python-lib')
-rw-r--r--tools/addon-sdk-1.3/python-lib/.gitignore2
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/__init__.py766
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/application.ini11
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/bootstrap.js154
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js660
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/install.rdf31
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/bunch.py30
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/__init__.py0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apiparser.py388
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apirenderer.py299
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/generate.py230
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/renderapi.readme.md206
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/docs/webdocs.py195
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/manifest.py725
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/bootstrap.js71
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/install.rdf26
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/packaging.py395
-rwxr-xr-xtools/addon-sdk-1.3/python-lib/cuddlefish/preflight.py73
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/prefs.py111
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/rdf.py172
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/runner.py627
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/templates.py79
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/__init__.py61
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json5
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json5
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json6
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js7
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/lib/main.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/lib/main.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/main.js5
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js2
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/two.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/data/text.data1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js2
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/unreachable.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/msg.txt1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/subdir/submsg.txt1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js2
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/package.json3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/welcome.md3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html136
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html23
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIreference.html465
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIsample.md158
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md8
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md0
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too2
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json7
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json8
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json5
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js3
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md1
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js4
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apiparser.py524
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apirenderer.py28
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_generate.py76
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_init.py85
-rwxr-xr-xtools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_linker.py200
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_manifest.py256
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_packaging.py105
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_preflight.py143
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_rdf.py15
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_runner.py24
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_util.py18
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_version.py32
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_webdocs.py92
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_xpi.py253
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/util.py19
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/version.py6
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/version_comparator.py202
-rw-r--r--tools/addon-sdk-1.3/python-lib/cuddlefish/xpi.py105
-rw-r--r--tools/addon-sdk-1.3/python-lib/jetpack_sdk_env.py62
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/__init__.py603
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/blockparser.py95
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/blockprocessors.py460
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/commandline.py96
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/docs/AUTHORS43
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/docs/LICENSE30
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/etree_loader.py33
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/__init__.py0
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/abbr.py95
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/codehilite.py224
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/def_list.py104
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/extra.py49
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/fenced_code.py117
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/footnotes.py293
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/headerid.py195
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/html_tidy.py62
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/imagelinks.py119
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/meta.py90
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/rss.py114
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/tables.py97
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/toc.py140
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/extensions/wikilinks.py155
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/html4.py274
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/inlinepatterns.py371
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/odict.py162
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/postprocessors.py77
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/preprocessors.py214
-rw-r--r--tools/addon-sdk-1.3/python-lib/markdown/treeprocessors.py329
-rw-r--r--tools/addon-sdk-1.3/python-lib/mozrunner/__init__.py589
-rw-r--r--tools/addon-sdk-1.3/python-lib/mozrunner/killableprocess.py316
-rw-r--r--tools/addon-sdk-1.3/python-lib/mozrunner/qijo.py162
-rw-r--r--tools/addon-sdk-1.3/python-lib/mozrunner/winprocess.py383
-rw-r--r--tools/addon-sdk-1.3/python-lib/mozrunner/wpk.py76
-rw-r--r--tools/addon-sdk-1.3/python-lib/simplejson/__init__.py376
-rw-r--r--tools/addon-sdk-1.3/python-lib/simplejson/decoder.py343
-rw-r--r--tools/addon-sdk-1.3/python-lib/simplejson/encoder.py385
-rw-r--r--tools/addon-sdk-1.3/python-lib/simplejson/scanner.py67
-rw-r--r--tools/addon-sdk-1.3/python-lib/simplejson/tool.py44
186 files changed, 15964 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.3/python-lib/.gitignore b/tools/addon-sdk-1.3/python-lib/.gitignore
new file mode 100644
index 0000000..2f78cf5
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/__init__.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/__init__.py
new file mode 100644
index 0000000..a576721
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/__init__.py
@@ -0,0 +1,766 @@
+import sys
+import os
+import optparse
+import webbrowser
+
+from copy import copy
+import simplejson as json
+from cuddlefish import packaging
+from cuddlefish.bunch import Bunch
+from cuddlefish.version import get_version
+
+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,
+ 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', 'firefox_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'])),
+ ]
+ ),
+
+ ("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, parser_groups, defaults=None):
+ parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption)
+
+ 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
+
+ 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
+
+ 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:
+ sys.exit(-1)
+
+def test_all_packages(env_root, defaults):
+ deps = []
+ target_cfg = Bunch(name = "testpkgs", dependencies = deps, version="fake")
+ pkg_cfg = packaging.build_config(env_root, target_cfg)
+ for name in pkg_cfg.packages:
+ if name != "testpkgs":
+ deps.append(name)
+ print >>sys.stderr, "Testing all available packages: %s." % (", ".join(deps))
+ sys.stderr.flush()
+ run(arguments=["test", "--dependencies"],
+ target_cfg=target_cfg,
+ pkg_cfg=pkg_cfg,
+ defaults=defaults)
+
+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 run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
+ defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'),
+ stdout=sys.stdout):
+ parser_kwargs = dict(arguments=arguments,
+ global_options=global_options,
+ parser_groups=parser_groups,
+ usage=usage,
+ 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_docs(env_root, filename=args[1])
+ else:
+ docs_home = generate.generate_docs(env_root)
+ webbrowser.open(docs_home)
+ return
+ elif command == "sdocs":
+ from cuddlefish.docs import generate
+
+ # TODO: Allow user to change this filename via cmd line.
+ filename = generate.generate_static_docs(env_root, base_url=options.baseurl)
+ print >>stdout, "Wrote %s." % filename
+ return
+
+ 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'])
+ enforce_timeouts = True
+ elif command == "run":
+ use_main = True
+ else:
+ print >>sys.stderr, "Unknown command: %s" % command
+ print >>sys.stderr, "Try using '--help' for assistance."
+ sys.exit(1)
+
+ 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
+
+ # the harness_guid is used for an XPCOM class ID. We use the
+ # JetpackID for the add-on ID and the XPCOM contract ID.
+ if "harnessClassID" in target_cfg:
+ # For the sake of non-bootstrapped extensions, we allow to specify the
+ # classID of harness' XPCOM component in package.json. This makes it
+ # possible to register the component using a static chrome.manifest file
+ harness_guid = target_cfg["harnessClassID"]
+ else:
+ import uuid
+ harness_guid = str(uuid.uuid4())
+
+ # 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"
+
+ if "id" in target_cfg:
+ jid = target_cfg["id"]
+ else:
+ jid = harness_guid
+ if not ("@" in jid or jid.startswith("{")):
+ jid = jid + "@jetpack"
+ unique_prefix = '%s-' % jid # used for resource: URLs
+ bundle_id = jid
+
+ # the resource: URL's prefix is treated too much like a DNS hostname
+ unique_prefix = unique_prefix.lower()
+ unique_prefix = unique_prefix.replace("@", "-at-")
+ unique_prefix = unique_prefix.replace(".", "-dot-")
+
+ 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
+ uri_prefix = "resource://%s" % unique_prefix
+ # 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 URI 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"
+ try:
+ manifest = build_manifest(target_cfg, pkg_cfg, deps, uri_prefix, scan_tests,
+ 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,
+ prefix=unique_prefix, # used to create resource: URLs
+ include_dep_tests=options.dep_tests
+ )
+
+ if 'resources' in build:
+ resources = build.resources
+ for name in resources:
+ resources[name] = os.path.abspath(resources[name])
+
+ harness_contract_id = ('@mozilla.org/harness-service;1?id=%s' % jid)
+ harness_options = {
+ 'bootstrap': {
+ 'contractID': harness_contract_id,
+ 'classID': '{%s}' % harness_guid
+ },
+ 'jetpackID': jid,
+ 'bundleID': bundle_id,
+ 'uriPrefix': uri_prefix,
+ 'staticArgs': options.static_args,
+ 'name': target,
+ }
+
+ harness_options.update(build)
+
+ if command == "test":
+ # This should be contained in the test runner package.
+ harness_options['main'] = 'test-harness/run-tests'
+ else:
+ harness_options['main'] = target_cfg.get('main')
+
+ for option in inherited_options:
+ harness_options[option] = getattr(options, option)
+
+ harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps)
+
+ sdk_version = get_version(env_root)
+ 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")
+
+ harness_options['manifest'] = manifest.get_harness_options_manifest(uri_prefix)
+
+ from cuddlefish.rdf import gen_manifest, RDFUpdate
+
+ manifest_rdf = gen_manifest(template_root_dir=app_extension_dir,
+ target_cfg=target_cfg,
+ bundle_id=bundle_id,
+ 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,
+ norun=options.no_run,
+ used_files=used_files,
+ enable_mobile=options.enable_mobile,
+ mobile_app_name=options.mobile_app_name)
+ 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.3/python-lib/cuddlefish/app-extension/application.ini b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/application.ini
new file mode 100644
index 0000000..6cec69a
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/app-extension/bootstrap.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/bootstrap.js
new file mode 100644
index 0000000..2d72ec2
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/bootstrap.js
@@ -0,0 +1,154 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Weave.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dan Mills <thunder@mozilla.com>
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+// For more information on the context in which this script is executed, see:
+// https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+// Object containing information about the XPCOM harness service
+// that manages our addon.
+
+var gHarness;
+
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+var manager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+// Dynamically evaluate and initialize the XPCOM component in
+// components/harness.js, which bootstraps our addon. (We want to keep
+// components/harness.js around so that versions of Gecko that don't
+// support rebootless addons can still work.)
+
+function setupHarness(installPath, loadReason) {
+ var harnessJs = installPath.clone();
+ harnessJs.append("components");
+ harnessJs.append("harness.js");
+ var path = ios.newFileURI(harnessJs).spec;
+ var harness = {};
+ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader);
+ loader.loadSubScript(path, harness);
+
+ var HarnessService = harness.buildHarnessService(installPath);
+ var factory = HarnessService.prototype._xpcom_factory;
+ var proto = HarnessService.prototype;
+
+ // We want to keep this factory around for the lifetime of
+ // the addon so legacy code with access to Components can
+ // access the addon if needed.
+ manager.registerFactory(proto.classID,
+ proto.classDescription,
+ proto.contractID,
+ factory);
+
+ var harnessService = factory.createInstance(null, Ci.nsISupports);
+ harnessService = harnessService.wrappedJSObject;
+
+ gHarness = {
+ service: harnessService,
+ classID: proto.classID,
+ contractID: proto.contractID,
+ factory: factory
+ };
+
+ if (loadReason == "startup")
+ // Simulate a startup event; the harness service will take care of
+ // waiting until the app is ready for the extension's code to run.
+ harnessService.observe(null, "profile-after-change", null);
+ else
+ harnessService.load(loadReason);
+}
+
+function reasonToString(reason) {
+ // If you change these names, change them in harness.js's lifeCycleObserver192
+ // too.
+ switch (reason) {
+ case ADDON_INSTALL:
+ return "install";
+ case ADDON_UNINSTALL:
+ return "uninstall";
+ case ADDON_ENABLE:
+ return "enable";
+ case ADDON_DISABLE:
+ return "disable";
+ case ADDON_UPGRADE:
+ return "upgrade";
+ case ADDON_DOWNGRADE:
+ return "downgrade";
+ // The startup and shutdown strings are also used outside of
+ // lifeCycleObserver192.
+ case APP_STARTUP:
+ return "startup";
+ case APP_SHUTDOWN:
+ return "shutdown";
+ }
+ return undefined;
+}
+
+function install(data, reason) {
+ // We shouldn't start up here; startup() will always be called when
+ // an extension should load, and install() sometimes gets called when
+ // an extension has been installed but is disabled.
+}
+
+function startup(data, reason) {
+ if (!gHarness)
+ setupHarness(data.installPath, reasonToString(reason));
+}
+
+function shutdown(data, reason) {
+ if (gHarness) {
+ var harness = gHarness;
+ gHarness = undefined;
+ harness.service.unload(reasonToString(reason));
+ manager.unregisterFactory(harness.classID, harness.factory);
+ }
+}
+
+function uninstall(data, reason) {
+ // We shouldn't shutdown here; shutdown() will always be called when
+ // an extension should shutdown, and uninstall() sometimes gets
+ // called when startup() has never been called before it.
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js
new file mode 100644
index 0000000..23030e9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/components/harness.js
@@ -0,0 +1,660 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Weave.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dan Mills <thunder@mozilla.com>
+ * Atul Varma <atul@mozilla.com>
+ * Drew Willcoxon <adw@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This file contains an XPCOM component which "bootstraps" a Jetpack
+// program.
+//
+// The main entry point, `NSGetModule()`, is data-driven, and obtains
+// a lot of its configuration information from a JSON file
+// called `harness-options.json` in the root directory of the extension
+// or application it's a part of.
+//
+// `NSGetModule()` then uses this configuration information to
+// dynamically create an XPCOM component called a "Harness Service",
+// which is responsible for setting up and shutting down the Jetpack
+// program's CommonJS environment. It's also the main mechanism through
+// which other parts of the application can communicate with the Jetpack
+// program.
+//
+// If we're on Gecko 1.9.3, which supports rebootless extensions, the
+// bootstrap.js file actually evaluates this file and calls parts of
+// it automatically.
+//
+// It should be noted that a lot of what's done by the Harness Service is
+// very similar to what's normally done by a `chrome.manifest` file: the
+// difference here is that everything the Harness Service does is
+// undoable during the lifetime of the application. This is the
+// foundation of what makes it possible for Jetpack-based extensions
+// to be installed and uninstalled without needing to reboot the
+// application being extended.
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const obSvc = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+const ioSvc = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+const THUNDERBIRD_ID = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";
+const FENNEC_ID = "{a23983c0-fd0e-11dc-95ff-0800200c9a66}";
+
+// This function builds and returns a Harness Service XPCOM component.
+//
+// Parameters:
+//
+// rootFileSpec - nsILocalFile corresponding to root of extension
+// (required).
+//
+// dump - function to output string to console (required).
+//
+// logError - function to log an exception (required).
+//
+// onQuit - function called when the app quits (required).
+//
+// options - JSON configuration information passed in from the
+// environment (required).
+
+function buildHarnessService(rootFileSpec, dump, logError,
+ onQuit, options) {
+ if (arguments.length == 1) {
+ ({dump, logError, onQuit, options}) = getDefaults(rootFileSpec);
+ }
+
+ // The loader for securable modules, typically a Cuddlefish loader.
+ var loader;
+
+ // Singleton Harness Service.
+ var harnessService;
+
+ // Whether we've initialized or not yet.
+ var isStarted;
+
+ // Whether we've been asked to quit or not yet.
+ var isQuitting;
+
+ // The Jetpack program's main module.
+ var program;
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var resProt = ioService.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+ function quit(status) {
+ if (status === undefined)
+ status = "OK";
+ if (status != "OK" && status != "FAIL") {
+ dump("Warning: quit() expected 'OK' or 'FAIL' as an " +
+ "argument, but got '" + status + "' instead.");
+ status = "FAIL";
+ }
+
+ if (isQuitting)
+ return;
+
+ isQuitting = true;
+
+ if (harnessService)
+ harnessService.unload();
+
+ onQuit(status);
+ }
+
+ function logErrorAndBail(e) {
+ logError(e);
+ quit("FAIL");
+ }
+
+ function ensureIsDir(dir) {
+ if (!(dir.exists() && dir.isDirectory))
+ throw new Error("directory not found: " + dir.path);
+ }
+
+ function getDir(path) {
+ var dir = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ dir.initWithPath(path);
+ ensureIsDir(dir);
+ return dir;
+ }
+
+ function buildLoader() {
+ // TODO: This variable doesn't seem to be used, we should
+ // be able to remove it.
+ var compMgr = Components.manager;
+ compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
+
+ for (let name in options.resources) {
+ var path = options.resources[name];
+ var dir;
+ if (typeof(path) == "string")
+ dir = getDir(path);
+ else {
+ dir = rootFileSpec.clone();
+ path.forEach(function(part) { dir.append(part); });
+ ensureIsDir(dir);
+ }
+ var dirUri = ioService.newFileURI(dir);
+ resProt.setSubstitution(name, dirUri);
+ }
+
+ var jsm = {};
+ Cu.import(options.loader, jsm);
+ var packaging = new Packaging();
+ var loader = new jsm.Loader({rootPaths: options.rootPaths.slice(),
+ print: dump,
+ packaging: packaging,
+ metadata: options.metadata,
+ uriPrefix: options.uriPrefix,
+ name: options.name,
+ globals: { packaging: packaging }
+ });
+ packaging.__setLoader(loader);
+ return loader;
+ }
+
+ // This will be exposed as the 'packaging' global to all
+ // modules loaded within our loader.
+
+ function Packaging() {
+ this.__packages = options.manifest;
+ }
+
+ Packaging.prototype = {
+ __setLoader: function setLoader(loader) {
+ this.__loader = loader;
+ },
+
+ get root() {
+ return rootFileSpec.clone();
+ },
+
+ get harnessService() {
+ return harnessService;
+ },
+
+ get buildHarnessService() {
+ return buildHarnessService;
+ },
+
+ get options() {
+ return options;
+ },
+
+ enableE10s: options.enable_e10s,
+
+ jetpackID: options.jetpackID,
+ uriPrefix: options.uriPrefix,
+
+ bundleID: options.bundleID,
+
+ getModuleInfo: function getModuleInfo(path) {
+ return this.__packages[path];
+ },
+
+ createLoader: function createLoader() {
+ return buildLoader();
+ }
+ };
+
+ // Singleton XPCOM component that is responsible for instantiating
+ // a Cuddlefish loader and running the main program, if any.
+
+ function HarnessService() {
+ this.wrappedJSObject = this;
+ }
+
+ HarnessService.prototype = {
+ get classDescription() {
+ // This needs to be unique, lest we regress bug 554489.
+ return "Harness Service for " + options.bootstrap.contractID;
+ },
+
+ get contractID() { return options.bootstrap.contractID; },
+
+ get classID() { return Components.ID(options.bootstrap.classID); },
+
+ _xpcom_categories: [{ category: "profile-after-change" }],
+
+ _xpcom_factory: {
+ get singleton() {
+ return harnessService;
+ },
+
+ createInstance: function(outer, iid) {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ if (!harnessService)
+ harnessService = new HarnessService();
+ return harnessService.QueryInterface(iid);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ get loader() {
+ if (!loader)
+ loader = buildLoader();
+ return loader;
+ },
+
+ get options() {
+ return options;
+ },
+
+ load: function Harness_load(reason) {
+ if (isStarted)
+ return;
+
+ isStarted = true;
+ obSvc.addObserver(this, "quit-application-granted", true);
+ if (options.main) {
+ try {
+
+ if (reason)
+ options.loadReason = reason;
+ program = this.loader.require(options.main);
+ if ('main' in program)
+ program.main(options, {quit: quit, print: dump});
+
+ // Send application readiness notification
+ const APP_READY_TOPIC = options.jetpackID + "_APPLICATION_READY";
+ obSvc.notifyObservers(null, APP_READY_TOPIC, null);
+
+ } catch (e) {
+ this.loader.console.exception(e);
+ quit("FAIL");
+ }
+ }
+ },
+
+ unload: function Harness_unload(reason) {
+ if (!isStarted)
+ return;
+
+ isStarted = false;
+ harnessService = null;
+
+ obSvc.removeObserver(this, "quit-application-granted");
+
+ lifeCycleObserver192.unload();
+
+ // Notify the program of unload.
+ if (program) {
+ if (typeof(program.onUnload) === "function") {
+ try {
+ program.onUnload(reason);
+ }
+ catch (err) {
+ if (loader)
+ loader.console.exception(err);
+ }
+ }
+ program = null;
+ }
+
+ // Notify the loader of unload.
+ if (loader) {
+ loader.unload(reason);
+ loader = null;
+ }
+
+ for (let name in options.resources)
+ resProt.setSubstitution(name, null);
+ },
+
+ observe: function Harness_observe(subject, topic, data) {
+ try {
+ switch (topic) {
+ case "profile-after-change":
+ var appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+ switch (appInfo.ID) {
+ case THUNDERBIRD_ID:
+ case FENNEC_ID:
+ obSvc.addObserver(this, "xul-window-visible", true);
+ break;
+ case FIREFOX_ID:
+ obSvc.addObserver(this, "sessionstore-windows-restored", true);
+ break;
+ default:
+ obSvc.addObserver(this, "final-ui-startup", true);
+ break;
+ }
+ lifeCycleObserver192.init(options.bundleID, logError);
+ break;
+ case "final-ui-startup": // XULRunner
+ case "sessionstore-windows-restored": // Firefox
+ case "xul-window-visible": // Thunderbird, Fennec
+ obSvc.removeObserver(this, topic);
+ this.load(lifeCycleObserver192.loadReason || "startup");
+ break;
+ case "quit-application-granted":
+ this.unload(lifeCycleObserver192.unloadReason || "shutdown");
+ quit("OK");
+ break;
+ }
+ } catch (e) {
+ logErrorAndBail(e);
+ }
+ }
+ };
+
+ var factory = HarnessService.prototype._xpcom_factory;
+ if (!factory.wrappedJSObject)
+ factory.wrappedJSObject = factory;
+
+ return HarnessService;
+}
+
+// This is an error logger of last resort; if we're here, then
+// we weren't able to initialize Cuddlefish and display a nice
+// traceback through it.
+
+function defaultLogError(e, print) {
+ if (!print)
+ print = dump;
+
+ var level = "error";
+ print(e + " (" + e.fileName + ":" + e.lineNumber + ")\n", level);
+ if (e.stack)
+ print("stack:\n" + e.stack + "\n", level);
+}
+
+// Builds an onQuit() function that writes a result file if necessary
+// and does some other extra things to enhance developer ergonomics.
+
+function buildDevQuit(options, dump) {
+ // Absolute path to a file that we put our result code in. Ordinarily
+ // we'd just exit the process with a zero or nonzero return code, but
+ // there doesn't appear to be a way to do this in XULRunner.
+ var resultFile = options.resultFile;
+
+ // Whether we've written resultFile or not.
+ var fileWritten = false;
+
+ function attemptQuit() {
+ var appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
+ getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit);
+ }
+
+ return function onQuit(result) {
+ dump(result + "\n");
+
+ function writeResult() {
+ if (!fileWritten)
+ try {
+ var file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(resultFile);
+
+ var foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ foStream.init(file, -1, -1, 0);
+ foStream.write(result, result.length);
+ foStream.close();
+ fileWritten = true;
+ } catch (e) {
+ dump(e + "\n");
+ }
+ }
+
+ writeResult();
+ attemptQuit();
+ };
+}
+
+function buildForsakenConsoleDump(dump) {
+ var buffer = "";
+ var cService = Cc['@mozilla.org/consoleservice;1'].getService()
+ .QueryInterface(Ci.nsIConsoleService);
+
+ function stringify(arg) {
+ try {
+ return String(arg);
+ }
+ catch(ex) {
+ return "<toString() error>";
+ }
+ }
+
+ return function forsakenConsoleDump(msg, level) {
+ msg = stringify(msg);
+ if (msg.indexOf('\n') >= 0) {
+ var str = buffer + msg;
+ if (level === "error") {
+ var err = Cc["@mozilla.org/scripterror;1"]
+ .createInstance(Ci.nsIScriptError);
+ str = str.replace(/^error: /, "");
+ err.init(str, null, null, 0, 0, 0, "Add-on SDK");
+ cService.logMessage(err);
+ }
+ else
+ cService.logStringMessage(str);
+ buffer = "";
+ } else {
+ buffer += msg;
+ }
+ };
+}
+
+function getDefaults(rootFileSpec) {
+ // Default options to pass back.
+ var options;
+
+ try {
+ var environ = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+
+ var jsonData;
+ var optionsFile = rootFileSpec.clone();
+ optionsFile.append('harness-options.json');
+ if (optionsFile.exists()) {
+ var fiStream = Cc['@mozilla.org/network/file-input-stream;1']
+ .createInstance(Ci.nsIFileInputStream);
+ var siStream = Cc['@mozilla.org/scriptableinputstream;1']
+ .createInstance(Ci.nsIScriptableInputStream);
+ fiStream.init(optionsFile, 1, 0, false);
+ siStream.init(fiStream);
+ var data = new String();
+ data += siStream.read(-1);
+ siStream.close();
+ fiStream.close();
+ jsonData = data;
+ }
+ else {
+ throw new Error("harness-options.json file must exist.");
+ }
+
+ options = JSON.parse(jsonData);
+ } catch (e) {
+ defaultLogError(e);
+ throw e;
+ }
+
+ var onQuit = function() {};
+ var doDump = buildForsakenConsoleDump(dump);
+
+ if ('resultFile' in options)
+ onQuit = buildDevQuit(options, print);
+
+ var logFile;
+ var logStream;
+
+ if ('logFile' in options) {
+ logFile = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ logFile.initWithPath(options.logFile);
+
+ logStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ logStream.init(logFile, 26 /* PR_WRONLY | PR_APPEND | PR_CREATE_FILE */,
+ -1 , 0);
+ }
+
+ function print(msg, level) {
+ doDump(msg, level);
+ if (logStream && typeof(msg) == "string") {
+ logStream.write(msg, msg.length);
+ logStream.flush();
+ }
+ }
+
+ function logError(e) {
+ defaultLogError(e, print);
+ }
+
+ return {options: options, onQuit: onQuit, dump: print,
+ logError: logError};
+}
+
+// Gecko 2, entry point for non-bootstrapped extensions (which register this
+// component via chrome.manifest.)
+// FIXME: no install/uninstall notifications on 2.0 for non-bootstrapped addons
+function NSGetFactory(cid) {
+ try {
+ if (!NSGetFactory.fn) {
+ var rootFileSpec = __LOCATION__.parent.parent;
+ var HarnessService = buildHarnessService(rootFileSpec);
+ NSGetFactory.fn = XPCOMUtils.generateNSGetFactory([HarnessService]);
+ }
+ } catch(e) {
+ Components.utils.reportError(e);
+ dump(e);
+ throw e;
+ }
+ return NSGetFactory.fn(cid);
+}
+
+// Everything below is only used on Gecko 1.9.2 or below.
+
+function NSGetModule(compMgr, fileSpec) {
+ var rootFileSpec = fileSpec.parent.parent;
+ var HarnessService = buildHarnessService(rootFileSpec);
+ return XPCOMUtils.generateModule([HarnessService]);
+}
+
+// Program life-cycle events originate in bootstrap.js on 1.9.3. But 1.9.2
+// doesn't use bootstrap.js, so we need to do a little extra work there to
+// determine the reasons for app startup and shutdown. That's what this
+// singleton is for. On 1.9.3 all methods are no-ops.
+var lifeCycleObserver192 = {
+ get loadReason() {
+ if (this._inited) {
+ // If you change these names, change them in bootstrap.js too.
+ if (this._addonIsNew)
+ return "install";
+ return "startup";
+ }
+ return undefined;
+ },
+
+ get unloadReason() {
+ if (this._inited) {
+ // If you change these names, change them in bootstrap.js too.
+ switch (this._emState) {
+ case "item-uninstalled":
+ return "uninstall";
+ case "item-disabled":
+ return "disable";
+ }
+ return "shutdown";
+ }
+ return undefined;
+ },
+
+ // This must be called first to initialize the singleton. It must be called
+ // on profile-after-change.
+ init: function lifeCycleObserver192_init(bundleID, logError) {
+ // This component is present in 1.9.2 but not 2.0.
+ if ("@mozilla.org/extensions/manager;1" in Cc && !this._inited) {
+ obSvc.addObserver(this, "em-action-requested", true);
+ this._bundleID = bundleID;
+ this._logError = logError;
+ this._inited = true;
+
+ try {
+ // This throws if the pref doesn't exist, which is the case when no
+ // new add-ons were installed.
+ var addonIdStr = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch).
+ getCharPref("extensions.newAddons");
+ }
+ catch (err) {}
+ if (addonIdStr) {
+ var addonIds = addonIdStr.split(",");
+ this._addonIsNew = addonIds.indexOf(this._bundleID) >= 0;
+ }
+ }
+ },
+
+ unload: function lifeCycleObserver192_unload() {
+ if (this._inited && !this._unloaded) {
+ obSvc.removeObserver(this, "em-action-requested");
+ delete this._logError;
+ this._unloaded = true;
+ }
+ },
+
+ observe: function lifeCycleObserver192_observe(subj, topic, data) {
+ try {
+ if (topic === "em-action-requested") {
+ if (subj instanceof Ci.nsIUpdateItem && subj.id === this._bundleID)
+ this._emState = data;
+ }
+ }
+ catch (err) {
+ this._logError(err);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ])
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/install.rdf b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/install.rdf
new file mode 100644
index 0000000..e76c25d
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/app-extension/install.rdf
@@ -0,0 +1,31 @@
+<?xml version="1.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>true</em:unpack>
+
+ <!-- Firefox -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>8.0</em:minVersion>
+ <em:maxVersion>9.*</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:iconURL></em:iconURL>
+ <em:icon64URL></em:icon64URL>
+ <em:homepageURL></em:homepageURL>
+ <em:optionsURL></em:optionsURL>
+ <em:updateURL></em:updateURL>
+ </Description>
+</RDF>
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/bunch.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/bunch.py
new file mode 100644
index 0000000..7262cbc
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/bunch.py
@@ -0,0 +1,30 @@
+# 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.3/python-lib/cuddlefish/docs/__init__.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/__init__.py
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apiparser.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apiparser.py
new file mode 100644
index 0000000..05a24fc
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apiparser.py
@@ -0,0 +1,388 @@
+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.3/python-lib/cuddlefish/docs/apirenderer.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apirenderer.py
new file mode 100644
index 0000000..2488aa9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/apirenderer.py
@@ -0,0 +1,299 @@
+import sys, os
+import markdown
+import apiparser
+import time
+
+# 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.3/python-lib/cuddlefish/docs/generate.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/generate.py
new file mode 100644
index 0000000..1c0fbf9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/generate.py
@@ -0,0 +1,230 @@
+import os
+import sys
+import shutil
+import hashlib
+import tarfile
+import StringIO
+
+from cuddlefish import packaging
+from cuddlefish import Bunch
+from cuddlefish.docs import apiparser
+from cuddlefish.docs import apirenderer
+from cuddlefish.docs import webdocs
+import simplejson as json
+
+DOCS_DIR = "doc"
+DIGEST = "status.md5"
+TGZ_FILENAME = "addon-sdk-docs.tgz"
+
+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, base_url=None):
+ docs_dir = os.path.join(env_root, DOCS_DIR)
+ clean_generated_docs(docs_dir)
+ generate_docs(env_root, base_url=base_url, stdout=StringIO.StringIO())
+ tgz = tarfile.open(TGZ_FILENAME, 'w:gz')
+ tgz.add(docs_dir, DOCS_DIR)
+ tgz.close()
+ return TGZ_FILENAME
+
+def generate_docs(env_root, base_url=None, filename=None, stdout=sys.stdout):
+ docs_dir = os.path.join(env_root, DOCS_DIR)
+ base_url = calculate_base_url(base_url, docs_dir)
+ # if we were given a filename, just generate the named file
+ # and return its URL
+ if filename:
+ return generate_named_file(env_root, base_url, filename)
+ # if the generated docs don't exist, generate everything
+ if not os.path.exists(os.path.join(docs_dir, "index.html")):
+ print >>stdout, "Generating documentation..."
+ generate_docs_from_scratch(env_root, base_url, docs_dir)
+ current_status = calculate_current_status(env_root)
+ open(os.path.join(env_root, DOCS_DIR, DIGEST), "w").write(current_status)
+ else:
+ current_status = calculate_current_status(env_root)
+ previous_status_file = os.path.join(env_root, 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, docs_dir)
+ open(os.path.join(env_root, DOCS_DIR, DIGEST), "w").write(current_status)
+ return base_url + "index.html"
+
+def calculate_base_url(base_url, docs_dir):
+ if base_url == None:
+ base_url_path = docs_dir
+ # this is to ensure the path starts with "/"
+ # whether or not it's on Windows
+ # there might be a better way
+ if not docs_dir.startswith("/"):
+ base_url_path = "/" + base_url_path
+ base_url_path_pieces = base_url_path.split(os.sep)
+ base_url = "file://" + "/".join(base_url_path_pieces) + "/"
+ return base_url
+
+def generate_named_file(env_root, base_url, filename):
+ docs_dir = os.path.join(env_root, DOCS_DIR)
+ web_docs = webdocs.WebDocs(env_root, base_url)
+
+ # next, generate api doc or guide doc
+ abs_path = os.path.abspath(filename)
+ if abs_path.startswith(os.path.join(env_root, 'packages')):
+ return generate_api_doc(env_root, abs_path, web_docs)
+ elif abs_path.startswith(os.path.join(env_root, DOCS_DIR, 'dev-guide-source')):
+ return generate_guide_doc(env_root, abs_path, web_docs)
+ else:
+ raise ValueError("Not a valid path to a documentation file")
+
+# 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):
+ 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(env_root, 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(env_root, 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):
+ web_docs = webdocs.WebDocs(env_root, base_url)
+ 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, pkg_name + ".html")
+ if not os.path.exists(package_filename):
+ package_doc_html = web_docs.create_package_page(pkg_name)
+ open(package_filename, "w").write(package_doc_html)
+
+ # 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)
+
+ # generate all the guide docs
+ dev_guide_src = os.path.join(env_root, DOCS_DIR, "dev-guide-source")
+ generate_file_tree(env_root, dev_guide_src, web_docs, generate_guide_doc)
+
+ # make /md/dev-guide/welcome.html the top level index file
+ shutil.copy(os.path.join(env_root, DOCS_DIR, 'dev-guide', 'welcome.html'), \
+ os.path.join(docs_dir, 'index.html'))
+
+def generate_file_tree(env_root, src_dir, web_docs, generate_file):
+ 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)
+ generate_file(env_root, src_path, web_docs)
+
+def generate_api_doc(env_root, src_dir, web_docs):
+ if src_dir.endswith(".md"):
+ dest_dir, filename = get_api_doc_dest_path(env_root, src_dir)
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+
+ # parse and JSONify the API docs
+ docs_md = open(src_dir, 'r').read()
+ docs_parsed = list(apiparser.parse_hunks(docs_md))
+ docs_json = json.dumps(docs_parsed)
+ dest_path_json = os.path.join(dest_dir, filename) + ".json"
+ replace_file(dest_path_json, docs_json)
+
+ # write the HTML div files
+ docs_div = apirenderer.json_to_div(docs_parsed, src_dir)
+ dest_path_div = os.path.join(dest_dir, filename) + ".div"
+ replace_file(dest_path_div, docs_div)
+
+ # write the standalone HTML files
+ docs_html = web_docs.create_module_page(src_dir)
+ dest_path_html = os.path.join(dest_dir, filename) + ".html"
+ replace_file(dest_path_html, docs_html)
+
+ return dest_path_html
+
+def generate_guide_doc(env_root, src_dir, web_docs):
+ if src_dir.endswith(".md"):
+ dest_dir, filename = get_guide_doc_dest_path(env_root, src_dir)
+ if not os.path.exists(dest_dir):
+ os.makedirs(dest_dir)
+ # write the standalone HTML files
+ docs_html = web_docs.create_guide_page(src_dir)
+ dest_path_html = os.path.join(dest_dir, filename) + ".html"
+ replace_file(dest_path_html, docs_html)
+ return dest_path_html
+
+def replace_file(dest_path, file_contents):
+ if os.path.exists(dest_path):
+ os.remove(dest_path)
+ open(dest_path, "w").write(file_contents)
+
+# 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(env_root, DOCS_DIR, "dev-guide-source")) + 1:]
+ return os.path.split(os.path.join(env_root, DOCS_DIR, "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:]
+ return os.path.split(os.path.join(env_root, DOCS_DIR, src_dir_relative)[:-3]) \ No newline at end of file
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/renderapi.readme.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/renderapi.readme.md
new file mode 100644
index 0000000..7086fe1
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/renderapi.readme.md
@@ -0,0 +1,206 @@
+
+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.3/python-lib/cuddlefish/docs/webdocs.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/webdocs.py
new file mode 100644
index 0000000..3528ac3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/docs/webdocs.py
@@ -0,0 +1,195 @@
+import sys, os, re, errno
+import markdown
+import simplejson as json
+
+from cuddlefish import packaging
+from cuddlefish import Bunch
+from cuddlefish.docs import apiparser
+from cuddlefish.docs import apirenderer
+
+INDEX_PAGE = '/doc/static-files/base.html'
+BASE_URL_INSERTION_POINT = '<base '
+HIGH_LEVEL_PACKAGE_SUMMARIES = '<li id="high-level-package-summaries">'
+LOW_LEVEL_PACKAGE_SUMMARIES = '<li id="low-level-package-summaries">'
+CONTENT_ID = '<div id="main-content">'
+TITLE_ID = '<title>'
+DEFAULT_TITLE = 'Add-on SDK Documentation'
+
+def get_modules(modules_json):
+ modules = []
+ for name in modules_json:
+ typ = modules_json[name][0]
+ if typ == "directory":
+ sub_modules = get_modules(modules_json[name][1])
+ for sub_module in sub_modules:
+ modules.append([name, sub_module[0]])
+ elif typ == "file":
+ if not name.startswith(".") and name.endswith('.js'):
+ modules.append([name[:-3]])
+ return modules
+
+def get_documented_modules(package_name, modules_json, doc_path):
+ modules = get_modules(modules_json)
+ documented_modules = []
+ for module in modules:
+ path = os.path.join(*module)
+ if module_md_exists(doc_path, path):
+ documented_modules.append(module)
+ if package_name == "addon-kit":
+ # hack for bug 664001, self-maker.js is in api-utils, self.md is in
+ # addon-kit. Real fix is for this function to look for all .md files,
+ # not for .js files with matching .md file in the same package.
+ documented_modules.append(["self"])
+ return documented_modules
+
+def module_md_exists(root, module_name):
+ module_md_path = os.path.join(root, module_name + '.md')
+ return os.path.exists(module_md_path)
+
+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_high_level(package_json):
+ return not is_low_level(package_json)
+
+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 = '/'):
+ 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_documented_modules(package_name, libs, doc_path)
+ modules.sort()
+ module_items = ''
+ relative_doc_path = doc_path[len(self.root) + 1:]
+ relative_doc_URL = "/".join(relative_doc_path.split(os.sep))
+ for module in modules:
+ module_link = tag_wrap('/'.join(module), 'a', \
+ {'href': relative_doc_URL + '/' + '/'.join(module) + '.html'})
+ module_items += tag_wrap(module_link, 'li', {'class':'module'})
+ return tag_wrap(module_items, 'ul', {'class':'modules'})
+
+ 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 + "/" \
+ + package_name + '.html'})
+ text = tag_wrap(package_link, 'h4')
+ text += self._create_module_list(package_json)
+ packages += tag_wrap(text, 'div', {'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')
+ base_tag = 'href="' + base_url + '"'
+ base_page = insert_after(base_page, BASE_URL_INSERTION_POINT, base_tag)
+ 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(\
+ 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.3/python-lib/cuddlefish/manifest.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/manifest.py
new file mode 100644
index 0000000..03ca0aa
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/manifest.py
@@ -0,0 +1,725 @@
+
+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_uri(self, prefix):
+ uri = "%s%s-%s/%s" % \
+ (prefix, self.packageName, self.sectionName, self.moduleName)
+ if not uri.endswith(".js"):
+ uri += ".js"
+ return uri
+
+ def get_entry_for_manifest(self, prefix):
+ 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 self.requirements[req]:
+ them = self.requirements[req] # this is another ManifestEntry
+ them_uri = them.get_uri(prefix)
+ entry["requirements"][req] = {"uri": them_uri}
+ else:
+ # something magic. The manifest entry indicates that they're
+ # allowed to require() it
+ entry["requirements"][req] = {}
+ if self.datamap:
+ entry["requirements"]["self"] = {
+ "mapSHA256": self.datamap.data_manifest_hash,
+ "mapName": self.packageName+"-data",
+ "dataURIPrefix": "%s%s-data/" % (prefix, 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, uri_prefix):
+ 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%s-data/" % (uri_prefix, 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, uri_prefix, 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.uri_prefix = uri_prefix
+ 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
+
+ def build(self, scan_tests):
+ # process the top module, which recurses to process everything it
+ # reaches
+ if "main" in self.target_cfg:
+ self.top_uri = self.process_module(self.find_top(self.target_cfg))
+ 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.
+ 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 tname in os.listdir(d):
+ if tname.startswith("test-") and tname.endswith(".js"):
+ #re.search(r'^test-.*\.js$', tname):
+ tmi = ModuleInfo(self.target_cfg, "tests", tname[:-3],
+ os.path.join(d, tname), None)
+ self.process_module(tmi)
+
+ # 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_harness_options_manifest(self, uri_prefix):
+ manifest = {}
+ for me in self.get_module_entries():
+ uri = me.get_uri(uri_prefix)
+ manifest[uri] = me.get_entry_for_manifest(uri_prefix)
+ 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 mi.section == "tests":
+ requires["chrome"] = {}
+ 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", "loader", "manifest"):
+ me.add_requirement(reqname, None)
+ 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, self.uri_prefix)
+ 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, uri_prefix, scan_tests,
+ 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, uri_prefix, extra_modules)
+ mxt.build(scan_tests)
+ return mxt
+
+
+
+COMMENT_PREFIXES = ["//", "/*", "*", "dump("]
+
+REQUIRE_RE = r"(?<![\'\"])require\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" or filename == "securable-module.js":
+ # these are 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.3/python-lib/cuddlefish/mobile-killer/bootstrap.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/bootstrap.js
new file mode 100644
index 0000000..9dfd993
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/bootstrap.js
@@ -0,0 +1,71 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Weave.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Alexandre Poirot <poirot.alex@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"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");
+
+function startup(data, reason) {
+ let Watcher = {
+ window: null,
+ onOpenWindow: function(window) {
+ window = window.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ window.addEventListener("keydown", this, true);
+ },
+ 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.
+ 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);
+ Cu.reportError("Mobile killer ready to kill firefox.");
+}
+
+function install() {}
+function shutdown() {}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/install.rdf b/tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/install.rdf
new file mode 100644
index 0000000..9ade0a9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/mobile-killer/install.rdf
@@ -0,0 +1,26 @@
+<?xml version="1.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-killer@mozilla.com</em:id>
+ <em:version>1.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+
+ <!-- Fennec -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id>
+ <em:minVersion>4.0b7</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Mobile killer</em:name>
+ <em:description>Allow cfx to automatically kill firefox.</em:description>
+ <em:creator>Mozilla Corporation</em:creator>
+
+ </Description>
+</RDF>
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/packaging.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/packaging.py
new file mode 100644
index 0000000..3dfc2ae
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/packaging.py
@@ -0,0 +1,395 @@
+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_BAD_PACKAGE_NAME_RE = re.compile(r'[\s\.]')
+
+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: package names cannot contain spaces or periods: bl arg
+
+ >>> validate_resource_hostname('BLARG')
+ Traceback (most recent call last):
+ ...
+ ValueError: package names need to be lowercase: BLARG
+
+ >>> validate_resource_hostname('foo@bar')
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid resource hostname: foo@bar
+ """
+
+ # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details.
+ if not name.islower():
+ raise ValueError('package names need to be lowercase: %s' % name)
+
+ # See https://bugzilla.mozilla.org/show_bug.cgi?id=597837 for details.
+ if RESOURCE_BAD_PACKAGE_NAME_RE.search(name):
+ raise ValueError('package names cannot contain spaces or periods: %s' % name)
+
+ if not RESOURCE_HOSTNAME_RE.match(name):
+ raise ValueError('invalid resource hostname: %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'])
+
+ 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, prefix='',
+ include_tests=True,
+ include_dep_tests=False,
+ default_loader=DEFAULT_LOADER):
+ validate_resource_hostname(prefix)
+
+ build = Bunch(resources=Bunch(),
+ resourcePackages=Bunch(),
+ packageData=Bunch(),
+ rootPaths=[],
+ )
+
+ 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):
+ lib_base = os.path.basename(dirname)
+ name = "-".join([prefix + cfg.name, section])
+ validate_resource_hostname(name)
+ if name in build.resources:
+ raise KeyError('resource already defined', name)
+ build.resourcePackages[name] = cfg.name
+ build.resources[name] = dirname
+ resource_url = 'resource://%s/' % name
+
+ if is_code:
+ build.rootPaths.insert(0, resource_url)
+
+ if is_data:
+ build.packageData[cfg.name] = resource_url
+
+ 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 ("loader" in dep_cfg) and ("loader" not in build):
+ build.loader = "resource://%s-%s" % (prefix + 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']
+
+ 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.3/python-lib/cuddlefish/preflight.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/preflight.py
new file mode 100755
index 0000000..141a3e8
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/preflight.py
@@ -0,0 +1,73 @@
+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.3/python-lib/cuddlefish/prefs.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/prefs.py
new file mode 100644
index 0000000..76b3a19
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/prefs.py
@@ -0,0 +1,111 @@
+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
+}
+
+DEFAULT_FENNEC_PREFS = {
+ 'javascript.options.showInConsole': True,
+ 'browser.console.showInPanel': True,
+ 'browser.firstrun.show.uidiscovery': False
+}
+
+# When launching a temporary new Firefox profile, use these preferences.
+DEFAULT_FIREFOX_PREFS = {
+ 'extensions.checkCompatibility.nightly' : False,
+ 'browser.startup.homepage' : 'about:blank',
+ 'startup.homepage_welcome_url' : 'about:blank',
+ 'devtools.errorconsole.enabled' : True,
+
+ # 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,
+ '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',
+ # 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'
+ }
+
+# 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,
+ # disable extension stuffs
+ 'extensions.update.enabled' : False,
+ 'extensions.update.notifyUser' : False,
+ # 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.3/python-lib/cuddlefish/rdf.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/rdf.py
new file mode 100644
index 0000000..9f64fc0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/rdf.py
@@ -0,0 +1,172 @@
+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, bundle_id,
+ update_url=None, bootstrap=True, enable_mobile=False):
+ install_rdf = os.path.join(template_root_dir, "install.rdf")
+ manifest = RDFManifest(install_rdf)
+
+ manifest.set("em:id", bundle_id)
+ 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())
+ manifest.set("em:unpack", "true")
+ if update_url:
+ manifest.set("em:updateURL", update_url)
+ else:
+ manifest.remove("em:updateURL")
+
+ if enable_mobile:
+ dom = manifest.dom
+ 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("{a23983c0-fd0e-11dc-95ff-0800200c9a66}"))
+ ta_desc.appendChild(elem)
+
+ elem = dom.createElement("em:minVersion")
+ elem.appendChild(dom.createTextNode("4.0b7"))
+ ta_desc.appendChild(elem)
+
+ elem = dom.createElement("em:maxVersion")
+ elem.appendChild(dom.createTextNode("9.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.3/python-lib/cuddlefish/runner.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/runner.py
new file mode 100644
index 0000000..72e6585
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/runner.py
@@ -0,0 +1,627 @@
+import os
+import sys
+import time
+import tempfile
+import atexit
+import shutil
+import shlex
+import subprocess
+import re
+
+import simplejson as json
+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
+
+# 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'", mobile_app_name)
+ 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-killer addon
+ # in order to kill running firefox instance
+ 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, 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)
+
+ 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()
+ harness_options['logFile'] = logfile
+
+ env = {}
+ env.update(os.environ)
+ env['MOZ_NO_REMOTE'] = '1'
+ env['XPCOM_DEBUG_BREAK'] = 'warn'
+ env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
+ 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__))
+ killer_dir = os.path.join(mydir, "mobile-killer")
+ addons.append(killer_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":
+ # in case we run it on a mobile device, we only have to launch it
+ runner.start()
+ profile.cleanup()
+ time.sleep(1)
+ print >>sys.stderr, "Remote application launched 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.3/python-lib/cuddlefish/templates.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/templates.py
new file mode 100644
index 0000000..577bcc5
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/templates.py
@@ -0,0 +1,79 @@
+#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 1.1/GPL 2.0/LGPL 2.1",
+ "version": "0.1"
+}
+'''
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/__init__.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/__init__.py
new file mode 100644
index 0000000..c0b34dc
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/__init__.py
@@ -0,0 +1,61 @@
+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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md
new file mode 100644
index 0000000..916389e
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md
@@ -0,0 +1 @@
+minimal docs
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js
new file mode 100644
index 0000000..27af5dd
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = function(options, callbacks) {
+ console.log("minimal");
+ callbacks.quit();
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js
@@ -0,0 +1 @@
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md
new file mode 100644
index 0000000..2b38e6a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md
@@ -0,0 +1 @@
+I am documentation for foo.
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js
new file mode 100644
index 0000000..3045af3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js
@@ -0,0 +1,3 @@
+exports.testThing = function(test) {
+ test.assertEqual(2, 1 + 1);
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md
new file mode 100644
index 0000000..916389e
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md
@@ -0,0 +1 @@
+minimal docs
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js
new file mode 100644
index 0000000..27af5dd
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = function(options, callbacks) {
+ console.log("minimal");
+ callbacks.quit();
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js
new file mode 100644
index 0000000..24095f2
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js
@@ -0,0 +1,7 @@
+if (this.sendMessage) {
+} else {
+ require('bar');
+
+ exports.register = function(process) {
+ };
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js
new file mode 100644
index 0000000..460045b
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js
@@ -0,0 +1 @@
+require('chrome');
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js
new file mode 100644
index 0000000..2bb1e10
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js
@@ -0,0 +1 @@
+require('bar');
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json
@@ -0,0 +1 @@
+{}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/lib/main.js
new file mode 100644
index 0000000..1bae788
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/lib/main.js
@@ -0,0 +1 @@
+exports.main = "'main' mainly reigns in main(.js)";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/five/package.json
new file mode 100644
index 0000000..98e4b85
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js
new file mode 100644
index 0000000..950c7b0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js
@@ -0,0 +1 @@
+exports.main = 42;
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js
new file mode 100644
index 0000000..950c7b0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js
@@ -0,0 +1 @@
+exports.main = 42;
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/lib/main.js
new file mode 100644
index 0000000..d5c9345
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/lib/main.js
@@ -0,0 +1 @@
+var a = require("four-a");
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/package.json
new file mode 100644
index 0000000..53180b9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/four/package.json
@@ -0,0 +1,3 @@
+{ "name": "four",
+ "main": "main"
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/main.js
new file mode 100644
index 0000000..85f37e3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/main.js
@@ -0,0 +1,5 @@
+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.3/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js
new file mode 100644
index 0000000..c24fdee
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js
@@ -0,0 +1,2 @@
+exports.foo = 1;
+var main = require("../main");
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/two.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/two.js
new file mode 100644
index 0000000..ddc754f
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/lib/two.js
@@ -0,0 +1,4 @@
+
+// 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.3/python-lib/cuddlefish/tests/linker-files/one/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/package.json
new file mode 100644
index 0000000..0420771
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/one/package.json
@@ -0,0 +1,3 @@
+{ "name": "one",
+ "main": "main"
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/data/text.data b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/data/text.data
new file mode 100644
index 0000000..1269488
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/data/text.data
@@ -0,0 +1 @@
+data
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js
new file mode 100644
index 0000000..7e651fb
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js
@@ -0,0 +1,2 @@
+var self = require("self"); // trigger inclusion of data
+exports.main = function () { console.log("main"); };
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js
new file mode 100644
index 0000000..d529639
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js
@@ -0,0 +1 @@
+exports.unused = "just pretend I'm not here";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/package.json
new file mode 100644
index 0000000..ef14839
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/seven/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "seven",
+ "id": "jid1"
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js
new file mode 100644
index 0000000..0fa8f43
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js
@@ -0,0 +1 @@
+exports.unused = "I am.";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/package.json
new file mode 100644
index 0000000..906b249
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/package.json
@@ -0,0 +1,3 @@
+{ "name": "six",
+ "main": "./unreachable"
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/unreachable.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/unreachable.js
new file mode 100644
index 0000000..a208943
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/six/unreachable.js
@@ -0,0 +1 @@
+exports.main = "I am outside lib/ and cannot be reached, yet";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/msg.txt b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/data/subdir/submsg.txt b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js
new file mode 100644
index 0000000..ed76814
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = 42;
+require("./subdir/subfile");
+require("self"); // trigger inclusion of our data/ directory
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js
new file mode 100644
index 0000000..66bff8b
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js
@@ -0,0 +1 @@
+exports.main = "I should be included in a subdir";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js
new file mode 100644
index 0000000..e70fe71
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js
@@ -0,0 +1 @@
+exports.main = "unused, linker should not include me in the XPI";
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js
new file mode 100644
index 0000000..950c7b0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js
@@ -0,0 +1 @@
+exports.main = 42;
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js
new file mode 100644
index 0000000..950c7b0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js
@@ -0,0 +1 @@
+exports.main = 42;
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js
new file mode 100644
index 0000000..9084831
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js
@@ -0,0 +1,2 @@
+exports.foo = "you found me down here";
+
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/linker-files/three/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/lib/main.js
new file mode 100644
index 0000000..b4fe4f5
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/lib/main.js
@@ -0,0 +1,4 @@
+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.3/python-lib/cuddlefish/tests/linker-files/three/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/package.json
new file mode 100644
index 0000000..cbfbc5b
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/linker-files/three/package.json
@@ -0,0 +1,3 @@
+{ "name": "three",
+ "main": "main"
+}
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md
new file mode 100644
index 0000000..51f3108
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/no_h1.md
@@ -0,0 +1,3 @@
+## A heading ##
+
+*Some words!*
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/welcome.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/welcome.md
new file mode 100644
index 0000000..573822a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/dev-guide-source/welcome.md
@@ -0,0 +1,3 @@
+# An Imposing Title #
+
+*Some words!*
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html
new file mode 100644
index 0000000..cdcb284
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/another.html
@@ -0,0 +1 @@
+another file \ No newline at end of file
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html
new file mode 100644
index 0000000..8b896cd
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/base.html
@@ -0,0 +1,136 @@
+<!-- modified by Noelle Murata [fiveinchpixie@gmail.com] -->
+<html>
+<head>
+ <base />
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <script type="text/javascript" src="syntaxhighlighter/scripts/shCore.js"></script>
+ <script type="text/javascript" src="syntaxhighlighter/scripts/shBrushJScript.js"></script>
+ <link rel="stylesheet" type="text/css" href="syntaxhighlighter/styles/shCore.css" />
+ <link rel="stylesheet" type="text/css" href="syntaxhighlighter/styles/shThemeDefault.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="css/base.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="css/sdk-docs.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="css/api-reference.css" />
+ <title></title>
+</head>
+<body>
+<div id="container">
+ <div id="columns">
+
+ <div id="left-column">
+
+ <h1 class="site-title">
+ <a href="dev-guide/welcome.html" title="Return to the Add-on SDK's homepage">
+ <img alt="Firefox" src="https://addons.mozilla.org/media/img/zamboni/app_icons/firefox.png"> Add-on SDK</a>
+ </h1>
+
+ <div class="sidebar-section" id="addon-development">
+ <h2 class="sidebar-section-header"><a href="dev-guide/addon-development/about.html">Developer Guide</a></h2>
+ <div class="sidebar-section-contents" id="default-section-contents">
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/addon-development/tutorials.html">Tutorials</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li><h4><a href="dev-guide/addon-development/getting-started.html">Getting Started</a></h4>
+ <ul>
+ <li><a href="dev-guide/addon-development/installation.html">Installation</a></li>
+ <li><a href="dev-guide/addon-development/implementing-simple-addon.html">A Simple Add-on</a></li>
+ <li><a href="dev-guide/addon-development/commonjs.html">CommonJS</a></li>
+ <li><a href="dev-guide/addon-development/implementing-reusable-module.html">Reusable Modules</a></li>
+ <li><a href="dev-guide/addon-development/troubleshooting.html">Troubleshooting</a></li>
+ </ul></li>
+ <li><h4><a href="dev-guide/addon-development/api-intro.html" >Introducing the APIs</a></h4>
+ <ul>
+ <li><a href="dev-guide/addon-development/api-idioms.html">Common Idioms</a></li>
+ <li><a href="dev-guide/addon-development/api-modules.html">API Overview</a></li>
+ </ul></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/addon-development/guides.html">Programming Guides</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li><h4><a href="dev-guide/addon-development/events.html">Working with Events</a></h4></li>
+ <li><h4><a href="dev-guide/addon-development/web-content.html">Working with Content Scripts</a></h4></li>
+ <li><h4><a href="dev-guide/addon-development/program-id.html">The Program ID</a></h4></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/addon-development/reference.html">Reference</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li id="high-level-package-summaries"></li>
+ <li><h4><a href="dev-guide/addon-development/globals.html">Globals</a></h4></li>
+ <li><h4><a href="dev-guide/addon-development/cfx-tool.html">cfx</a></h4></li>
+ <li><h4><a href="dev-guide/addon-development/package-spec.html">Package Specification</a></h4></li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/addon-development/experimental.html">Experimental</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li><h4><a href="dev-guide/addon-development/xul-extensions.html">XUL Extensions</a></h4></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="sidebar-section" id="module-development">
+ <h2 class="sidebar-section-header"><a href="dev-guide/module-development/about.html">Internals Guide</a></h2>
+ <div class="sidebar-section-contents">
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/module-development/guides.html">Programming Guides</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li><h4><a href="dev-guide/module-development/best-practices.html">Low-Level Module Best Practices</a></h4>
+ <li><h4><a href="dev-guide/module-development/chrome.html">Chrome Authority</a></h4>
+ <li><h4><a href="dev-guide/module-development/xpi.html">XPI Generation</a></h4>
+ <li><h4><a href="dev-guide/module-development/e10s.html">Out-of-Process Add-ons</a></h4>
+ </ul>
+ </div>
+ </div>
+
+ <div class="sidebar-subsection">
+ <h3 class="sidebar-subsection-header"><a href="dev-guide/module-development/reference.html">Reference</a></h3>
+ <div class="sidebar-subsection-contents">
+ <ul>
+ <li id="low-level-package-summaries"></li>
+ <li><h4><a href="dev-guide/module-development/globals.html">Globals</a></h4></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="sidebar-section" id="appendices">
+ <h3><a href="https://wiki.mozilla.org/Labs/Jetpack/Release_Notes">Release Notes</a></h3>
+ <h3><a href="dev-guide/appendices/glossary.html">Glossary</a></h3>
+ <h3><a href="dev-guide/appendices/credits.html">Credits</a></h3>
+
+ </div>
+
+ </div>
+
+ <div id="main-content">
+ <div id="right-column">
+ </div>
+ </div>
+ </div>
+ <div id="footer">
+ <span class="full-name">Add-on SDK Documentation</span>
+ </div>
+
+</div>
+
+</body>
+<script src="js/jquery.js"></script>
+<script src="js/main.js"></script>
+</html>
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html
new file mode 100644
index 0000000..b84eeec
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/doc/static-files/index.html
@@ -0,0 +1,23 @@
+<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.3/python-lib/cuddlefish/tests/static-files/docs/APIreference.html b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIreference.html
new file mode 100644
index 0000000..a33a4f9
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIreference.html
@@ -0,0 +1,465 @@
+
+<!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"><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.3/python-lib/cuddlefish/tests/static-files/docs/APIsample.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIsample.md
new file mode 100644
index 0000000..24271ec
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/docs/APIsample.md
@@ -0,0 +1,158 @@
+# 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.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md
new file mode 100644
index 0000000..9f61e86
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/aardvark-feeder.md
@@ -0,0 +1,8 @@
+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.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js
new file mode 100644
index 0000000..0591fe0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = function(options, callbacks) {
+ console.log("1 + 1 =", require("bar-module").add(1, 1));
+ callbacks.quit();
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json
new file mode 100644
index 0000000..07eb9b9
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js
new file mode 100644
index 0000000..0591fe0
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = function(options, callbacks) {
+ console.log("1 + 1 =", require("bar-module").add(1, 1));
+ callbacks.quit();
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js
new file mode 100644
index 0000000..d115349
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js
@@ -0,0 +1,3 @@
+// 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.3/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json b/tools/addon-sdk-1.3/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.3/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.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js
new file mode 100644
index 0000000..44a9a43
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js
@@ -0,0 +1,3 @@
+exports.add = function add(a, b) {
+ return a + b;
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json
new file mode 100644
index 0000000..62e3c12
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md
new file mode 100644
index 0000000..916389e
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/docs/main.md
@@ -0,0 +1 @@
+minimal docs
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js
new file mode 100644
index 0000000..27af5dd
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js
@@ -0,0 +1,4 @@
+exports.main = function(options, callbacks) {
+ console.log("minimal");
+ callbacks.quit();
+};
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json
new file mode 100644
index 0000000..530f3c2
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js
new file mode 100644
index 0000000..5e2d20a
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js
@@ -0,0 +1,4 @@
+// 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.3/python-lib/cuddlefish/tests/test_apiparser.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apiparser.py
new file mode 100644
index 0000000..1e95f4c
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apiparser.py
@@ -0,0 +1,524 @@
+
+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", "# Title #\n\nSome text here\n\n"))
+ 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": 11,
+ })
+
+ self.assertEqual(p_test["params"][1],
+ {"name": "argTwo",
+ "required": False,
+ "datatype": "bool",
+ "description": "This is the second argument.",
+ "line_number": 12,
+ })
+
+ self.assertEqual(p_test["params"][2],
+ {"name": "argThree",
+ "required": False,
+ "default": "default",
+ "datatype": "uri",
+ "line_number": 13,
+ "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": 18,
+ })
+ self.assertEqual(p3["props"][1],
+ {"name": "secondToLastOption",
+ "required": False,
+ "default": "True",
+ "datatype": "bool",
+ "description": "The last property.",
+ "line_number": 19,
+ })
+ 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': 28,
+ 'name': 'append',
+ 'params': [{'props':[{'line_number': 33,
+ 'required': False,
+ 'datatype': 'uri',
+ 'name': 'icon',
+ 'description': 'The HREF of an icon to show as the \
+method of accessing your features slideBar'},
+ {'line_number': 34,
+ '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': 37,
+ 'required': False,
+ 'datatype': 'uri',
+ 'name': 'url',
+ 'description': 'The url to load into the content area \
+of the feature'},
+ {'line_number': 38,
+ 'required': False,
+ 'datatype': 'int',
+ 'name': 'width',
+ 'description': 'Width of the content area and the \
+selected slide size'},
+ {'line_number': 39,
+ 'required': False,
+ 'datatype': 'bool',
+ 'name': 'persist',
+ 'description': 'Default slide behavior when being \
+selected as follows:\nIf true: blah; If false: double blah.'},
+ {'line_number': 42,
+ 'required': False,
+ 'datatype': 'bool',
+ 'name': 'autoReload',
+ 'description': 'Automatically reload content on \
+select'},
+ {'line_number': 43,
+ 'required': False,
+ 'datatype': 'function',
+ 'name': 'onClick',
+ 'description': 'Callback when the icon is \
+clicked'},
+ {'line_number': 44,
+ 'required': False,
+ 'datatype': 'function',
+ 'name': 'onSelect',
+ 'description': 'Callback when the feature is selected'},
+ {'line_number': 45,
+ 'required': False,
+ 'datatype': 'function',
+ 'name': 'onReady',
+ 'description':
+ 'Callback when featured is loaded'}],
+ 'line_number': 31,
+ '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": 63,
+ "description": "The callback",
+ })
+ self.assertEqual(p_test["params"][2]["props"][1],
+ {"name": "random",
+ "required": False,
+ "datatype": "bool",
+ "line_number": 64,
+ "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.3/python-lib/cuddlefish/tests/test_apirenderer.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apirenderer.py
new file mode 100644
index 0000000..43ee872
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_apirenderer.py
@@ -0,0 +1,28 @@
+
+import os
+import unittest
+from cuddlefish.docs.apiparser import parse_hunks, ParseError
+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.3/python-lib/cuddlefish/tests/test_generate.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_generate.py
new file mode 100644
index 0000000..72a4487
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_generate.py
@@ -0,0 +1,76 @@
+import os
+import shutil
+import unittest
+import StringIO
+
+from cuddlefish.docs import generate
+from cuddlefish.tests import env_root
+
+INITIAL_FILESET = [ ["static-files", "base.html"], \
+ ["dev-guide", "welcome.html"], \
+ ["packages", "aardvark", "aardvark.html"] ]
+
+EXTENDED_FILESET = [ ["static-files", "base.html"], \
+ ["dev-guide", "extra.html"], \
+ ["dev-guide", "welcome.html"], \
+ ["packages", "aardvark", "aardvark.html"] ]
+
+EXTRAFILE = ["dev-guide", "extra.html"]
+
+class Generate_Docs_Tests(unittest.TestCase):
+ def test_generate_static_docs_does_not_smoke(self):
+ filename = 'testdocs.tgz'
+ if os.path.exists(filename):
+ os.remove(filename)
+ filename = generate.generate_static_docs(env_root)
+ self.assertTrue(os.path.exists(filename))
+ os.remove(filename)
+
+ def test_generate_docs_does_not_smoke(self):
+ test_root = os.path.join(env_root, "python-lib", "cuddlefish", "tests", "static-files")
+ 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", "welcome.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)))
+ 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.3/python-lib/cuddlefish/tests/test_init.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_init.py
new file mode 100644
index 0000000..f11ce2c
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_init.py
@@ -0,0 +1,85 @@
+import os, unittest, shutil
+from StringIO import StringIO
+from cuddlefish import initializer
+from cuddlefish.templates import MAIN_JS, TEST_MAIN_JS, PACKAGE_JSON
+
+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)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_linker.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_linker.py
new file mode 100755
index 0000000..b2e4f19
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_linker.py
@@ -0,0 +1,200 @@
+import os.path
+import shutil
+import zipfile
+from StringIO import StringIO
+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,
+ "P/", scan_tests=False)
+ m = m.get_harness_options_manifest("P/")
+
+ def assertReqIs(modname, reqname, uri):
+ reqs = m["P/one-lib/%s.js" % modname]["requirements"]
+ self.failUnlessEqual(reqs[reqname]["uri"], uri)
+ assertReqIs("main", "panel", "P/addon-kit-lib/panel.js")
+ assertReqIs("main", "two.js", "P/one-lib/two.js")
+ assertReqIs("main", "./two", "P/one-lib/two.js")
+ assertReqIs("main", "addon-kit/tabs.js", "P/addon-kit-lib/tabs.js")
+ assertReqIs("main", "./subdir/three", "P/one-lib/subdir/three.js")
+ assertReqIs("two", "main", "P/one-lib/main.js")
+ assertReqIs("subdir/three", "../main", "P/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, "P/", 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,
+ "P/", scan_tests=False)
+ m = m.get_harness_options_manifest("P/")
+ def assertReqIs(modname, reqname, uri):
+ reqs = m["P/three-lib/%s.js" % modname]["requirements"]
+ self.failUnlessEqual(reqs[reqname]["uri"], uri)
+ assertReqIs("main", "three-a", "P/three-a-lib/main.js")
+ assertReqIs("main", "three-b", "P/three-b-lib/main.js")
+ assertReqIs("main", "three-c", "P/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,
+ "P/", scan_tests=False)
+ m = m.get_harness_options_manifest("P/")
+ reqs = m["P/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,
+ "P/", 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,
+ "P/", 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_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/jid1-at-jetpack-api-utils-lib/cuddlefish.js", names)
+ self.assertIn("resources/jid1-at-jetpack-api-utils-lib/securable-module.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 "jid1-at-jetpack-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/jid1-at-jetpack-seven-data/text.data",
+ names)
+ self.failIf("resources/jid1-at-jetpack-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/jid1-at-jetpack-api-utils-lib/cuddlefish.js", names)
+ self.assertIn("resources/jid1-at-jetpack-api-utils-lib/securable-module.js", names)
+ testfiles = [fn for fn in names if "jid1-at-jetpack-seven-tests" in fn]
+ self.failUnlessEqual([], testfiles)
+ self.assertIn("resources/jid1-at-jetpack-seven-data/text.data",
+ names)
+ self.failUnless("resources/jid1-at-jetpack-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.3/python-lib/cuddlefish/tests/test_manifest.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_manifest.py
new file mode 100644
index 0000000..c9324ee
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_manifest.py
@@ -0,0 +1,256 @@
+
+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 two loader files
+ 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, [])
+
+ mod = """let {Cc,Ci} = require('chrome');"""
+ requires, problems, err = scan2(mod, "securable-module.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.3/python-lib/cuddlefish/tests/test_packaging.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_packaging.py
new file mode 100644
index 0000000..aa21131
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_packaging.py
@@ -0,0 +1,105 @@
+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,
+ prefix='guid-'
+ )
+ 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,
+ 'resource://guid-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")))
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_preflight.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_preflight.py
new file mode 100644
index 0000000..a71afbc
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_preflight.py
@@ -0,0 +1,143 @@
+
+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.3/python-lib/cuddlefish/tests/test_rdf.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_rdf.py
new file mode 100644
index 0000000..4585143
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_rdf.py
@@ -0,0 +1,15 @@
+import unittest
+import xml.dom.minidom
+
+from cuddlefish import rdf
+
+
+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",""))
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_runner.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_runner.py
new file mode 100644
index 0000000..15ec8d2
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_runner.py
@@ -0,0 +1,24 @@
+import sys
+
+from cuddlefish import runner
+
+def xulrunner_app_runner_doctests():
+ """
+ >>> 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.3/python-lib/cuddlefish/tests/test_util.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_util.py
new file mode 100644
index 0000000..8f5e697
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_util.py
@@ -0,0 +1,18 @@
+
+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.3/python-lib/cuddlefish/tests/test_version.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_version.py
new file mode 100644
index 0000000..2fb32f3
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_version.py
@@ -0,0 +1,32 @@
+import os
+import unittest
+import shutil
+
+from cuddlefish.version import get_version
+
+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.
+ env_root = os.environ.get('CUDDLEFISH_ROOT')
+ version = get_version(env_root)
+ self.failUnless(isinstance(version, str), (version, type(version)))
+ self.failUnless(len(version) > 0, version)
+ def test_read(self):
+ basedir = self.make_basedir()
+ f = open(os.path.join(basedir, ".version"), "w")
+ f.write("versioniffic\n")
+ f.close()
+ sdk_version = get_version(basedir)
+ self.failUnlessEqual(sdk_version, "versioniffic")
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_webdocs.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_webdocs.py
new file mode 100644
index 0000000..c8d66d4
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_webdocs.py
@@ -0,0 +1,92 @@
+import os, re
+import unittest
+
+from cuddlefish.docs import webdocs
+from cuddlefish.tests import env_root
+
+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/welcome.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)
+
+ 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/aardvark.html"' in doc)
+ self.assertTrue(\
+ '<a href="packages/anteater_files/anteater.html"' in doc)
+ self.assertTrue(\
+ '<a href="packages/aardvark/doc/main.html">main</a>' in doc)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_xpi.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_xpi.py
new file mode 100644
index 0000000..d824392
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/tests/test_xpi.py
@@ -0,0 +1,253 @@
+import os
+import unittest
+import zipfile
+import pprint
+import shutil
+
+import simplejson as json
+from cuddlefish import xpi, packaging, manifest
+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 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,
+ "P/", 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,
+ prefix="p-",
+ 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",
+ "resources/",
+ "resources/p-api-utils-data/",
+ "resources/p-api-utils-lib/",
+ "resources/p-three-lib/",
+ "resources/p-three-lib/main.js",
+ "resources/p-three-a-data/",
+ "resources/p-three-a-data/msg.txt",
+ "resources/p-three-a-data/subdir/",
+ "resources/p-three-a-data/subdir/submsg.txt",
+ "resources/p-three-a-lib/",
+ "resources/p-three-a-lib/main.js",
+ "resources/p-three-a-lib/subdir/",
+ "resources/p-three-a-lib/subdir/subfile.js",
+ "resources/p-three-b-lib/",
+ "resources/p-three-b-lib/main.js",
+ "resources/p-three-c-lib/",
+ "resources/p-three-c-lib/main.js",
+ "resources/p-three-c-lib/sub/",
+ "resources/p-three-c-lib/sub/foo.js",
+ # notably absent: p-three-a-lib/unused.js
+ ]
+ # 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))
+
+
+
+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}
+ 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.3/python-lib/cuddlefish/util.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/util.py
new file mode 100644
index 0000000..b3d1ec1
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/util.py
@@ -0,0 +1,19 @@
+
+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.3/python-lib/cuddlefish/version.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/version.py
new file mode 100644
index 0000000..f2a91a7
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/version.py
@@ -0,0 +1,6 @@
+import os
+
+def get_version(env_root):
+ f = open(os.path.join(env_root, ".version"), "r")
+ sdk_version = f.read().strip()
+ return sdk_version
diff --git a/tools/addon-sdk-1.3/python-lib/cuddlefish/version_comparator.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/version_comparator.py
new file mode 100644
index 0000000..b89bd6c
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/version_comparator.py
@@ -0,0 +1,202 @@
+'''
+ 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.3/python-lib/cuddlefish/xpi.py b/tools/addon-sdk-1.3/python-lib/cuddlefish/xpi.py
new file mode 100644
index 0000000..2708106
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/cuddlefish/xpi.py
@@ -0,0 +1,105 @@
+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']
+
+ 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
+
+ new_resources = {}
+ for resource in harness_options['resources']:
+ new_resources[resource] = ['resources', resource]
+ base_arcpath = ZIPSEP.join(['resources', resource])
+ # 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)
+ abs_dirname = harness_options['resources'][resource]
+ # 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',
+ resource,
+ make_zipfile_path(abs_dirname,
+ os.path.join(dirpath, filename)),
+ ])
+ files_to_copy[str(arcpath)] = str(abspath)
+ harness_options['resources'] = new_resources
+
+ # 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.3/python-lib/jetpack_sdk_env.py b/tools/addon-sdk-1.3/python-lib/jetpack_sdk_env.py
new file mode 100644
index 0000000..ca44eba
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/jetpack_sdk_env.py
@@ -0,0 +1,62 @@
+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.3/python-lib/markdown/__init__.py b/tools/addon-sdk-1.3/python-lib/markdown/__init__.py
new file mode 100644
index 0000000..0d1c504
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/blockparser.py b/tools/addon-sdk-1.3/python-lib/markdown/blockparser.py
new file mode 100644
index 0000000..e18b338
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/blockprocessors.py b/tools/addon-sdk-1.3/python-lib/markdown/blockprocessors.py
new file mode 100644
index 0000000..79f4db9
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/commandline.py b/tools/addon-sdk-1.3/python-lib/markdown/commandline.py
new file mode 100644
index 0000000..1eedc6d
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/docs/AUTHORS b/tools/addon-sdk-1.3/python-lib/markdown/docs/AUTHORS
new file mode 100644
index 0000000..cfe2b34
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/markdown/docs/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.3/python-lib/markdown/docs/LICENSE b/tools/addon-sdk-1.3/python-lib/markdown/docs/LICENSE
new file mode 100644
index 0000000..4cd8b14
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/markdown/docs/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.3/python-lib/markdown/etree_loader.py b/tools/addon-sdk-1.3/python-lib/markdown/etree_loader.py
new file mode 100644
index 0000000..e2599b2
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/__init__.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/markdown/extensions/__init__.py
diff --git a/tools/addon-sdk-1.3/python-lib/markdown/extensions/abbr.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/abbr.py
new file mode 100644
index 0000000..783220e
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/codehilite.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/codehilite.py
new file mode 100644
index 0000000..c5d496b
--- /dev/null
+++ b/tools/addon-sdk-1.3/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('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+ def _number(self, txt):
+ """ Use <ol> for line numbering """
+ # Fix Whitespace
+ txt = txt.replace('\t', ' '*TAB_LENGTH)
+ txt = txt.replace(" "*4, "&nbsp; &nbsp; ")
+ txt = txt.replace(" "*3, "&nbsp; &nbsp;")
+ txt = txt.replace(" "*2, "&nbsp; ")
+
+ # 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.3/python-lib/markdown/extensions/def_list.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/def_list.py
new file mode 100644
index 0000000..73a1c85
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/extra.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/extra.py
new file mode 100644
index 0000000..4a2ffbf
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/fenced_code.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/fenced_code.py
new file mode 100644
index 0000000..307b1dc
--- /dev/null
+++ b/tools/addon-sdk-1.3/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">&lt;p&gt;block two&lt;/p&gt;\\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('&', '&amp;')
+ txt = txt.replace('<', '&lt;')
+ txt = txt.replace('>', '&gt;')
+ txt = txt.replace('"', '&quot;')
+ return txt
+
+
+def makeExtension(configs=None):
+ return FencedCodeExtension()
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/tools/addon-sdk-1.3/python-lib/markdown/extensions/footnotes.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/footnotes.py
new file mode 100644
index 0000000..6dacab7
--- /dev/null
+++ b/tools/addon-sdk-1.3/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, "&#8617;")
+ return text.replace(NBSP_PLACEHOLDER, "&#160;")
+
+def makeExtension(configs=[]):
+ """ Return an instance of the FootnoteExtension """
+ return FootnoteExtension(configs=configs)
+
diff --git a/tools/addon-sdk-1.3/python-lib/markdown/extensions/headerid.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/headerid.py
new file mode 100644
index 0000000..f70a7a9
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/html_tidy.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/html_tidy.py
new file mode 100644
index 0000000..5105e33
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/imagelinks.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/imagelinks.py
new file mode 100644
index 0000000..ee0b708
--- /dev/null
+++ b/tools/addon-sdk-1.3/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 = """&nbsp;<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 += "&nbsp;"
+ 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.3/python-lib/markdown/extensions/meta.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/meta.py
new file mode 100644
index 0000000..1b555b2
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/rss.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/rss.py
new file mode 100644
index 0000000..1274da2
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/tables.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/tables.py
new file mode 100644
index 0000000..1d3c920
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/toc.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/toc.py
new file mode 100644
index 0000000..1624ccf
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/extensions/wikilinks.py b/tools/addon-sdk-1.3/python-lib/markdown/extensions/wikilinks.py
new file mode 100644
index 0000000..df44e1c
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/html4.py b/tools/addon-sdk-1.3/python-lib/markdown/html4.py
new file mode 100644
index 0000000..08f241d
--- /dev/null
+++ b/tools/addon-sdk-1.3/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("&", "&amp;")
+ if "<" in text:
+ text = text.replace("<", "&lt;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ 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("&", "&amp;")
+ if "<" in text:
+ text = text.replace("<", "&lt;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ if "\"" in text:
+ text = text.replace("\"", "&quot;")
+ if "\n" in text:
+ text = text.replace("\n", "&#10;")
+ 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("&", "&amp;")
+ if ">" in text:
+ text = text.replace(">", "&gt;")
+ if "\"" in text:
+ text = text.replace("\"", "&quot;")
+ 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.3/python-lib/markdown/inlinepatterns.py b/tools/addon-sdk-1.3/python-lib/markdown/inlinepatterns.py
new file mode 100644
index 0000000..89fa3b2
--- /dev/null
+++ b/tools/addon-sdk-1.3/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]*;)' # &amp;
+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('"', "&quot;")
+ 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.3/python-lib/markdown/odict.py b/tools/addon-sdk-1.3/python-lib/markdown/odict.py
new file mode 100644
index 0000000..bf3ef07
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/postprocessors.py b/tools/addon-sdk-1.3/python-lib/markdown/postprocessors.py
new file mode 100644
index 0000000..80227bb
--- /dev/null
+++ b/tools/addon-sdk-1.3/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('&', '&amp;')
+ html = html.replace('<', '&lt;')
+ html = html.replace('>', '&gt;')
+ return html.replace('"', '&quot;')
+
+
+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.3/python-lib/markdown/preprocessors.py b/tools/addon-sdk-1.3/python-lib/markdown/preprocessors.py
new file mode 100644
index 0000000..712a1e8
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/markdown/treeprocessors.py b/tools/addon-sdk-1.3/python-lib/markdown/treeprocessors.py
new file mode 100644
index 0000000..1dc612a
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/mozrunner/__init__.py b/tools/addon-sdk-1.3/python-lib/mozrunner/__init__.py
new file mode 100644
index 0000000..1219a71
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/mozrunner/__init__.py
@@ -0,0 +1,589 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla Corporation Code.
+#
+# The Initial Developer of the Original Code is
+# Mikeal Rogers.
+# Portions created by the Initial Developer are Copyright (C) 2008-2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Mikeal Rogers <mikeal.rogers@gmail.com>
+# Clint Talbert <ctalbert@mozilla.com>
+# Henrik Skupin <hskupin@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+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 distutils import dir_util
+from time import sleep
+
+# conditional (version-dependent) imports
+try:
+ from xml.etree import ElementTree
+except ImportError:
+ from elementtree import ElementTree
+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
+
+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 install_addon(self, path):
+ """Installs the given addon or directory of addons in the profile."""
+ 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:
+ tmpdir = None
+ if addon.endswith('.xpi'):
+ tmpdir = tempfile.mkdtemp(suffix = "." + os.path.split(addon)[-1])
+ compressed_file = zipfile.ZipFile(addon, "r")
+ for name in compressed_file.namelist():
+ if name.endswith('/'):
+ makedirs(os.path.join(tmpdir, name))
+ else:
+ if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))):
+ makedirs(os.path.dirname(os.path.join(tmpdir, name)))
+ data = compressed_file.read(name)
+ f = open(os.path.join(tmpdir, name), 'wb')
+ f.write(data) ; f.close()
+ addon = tmpdir
+
+ tree = ElementTree.ElementTree(file=os.path.join(addon, 'install.rdf'))
+ # description_element =
+ # tree.find('.//{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description/')
+
+ desc = tree.find('.//{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+ apps = desc.findall('.//{http://www.mozilla.org/2004/em-rdf#}targetApplication')
+ for app in apps:
+ desc.remove(app)
+ if len(desc) and desc.attrib.has_key('{http://www.mozilla.org/2004/em-rdf#}id'):
+ addon_id = desc.attrib['{http://www.mozilla.org/2004/em-rdf#}id']
+ elif len(desc) and desc.find('.//{http://www.mozilla.org/2004/em-rdf#}id') is not None:
+ addon_id = desc.find('.//{http://www.mozilla.org/2004/em-rdf#}id').text
+ else:
+ about = [e for e in tree.findall(
+ './/{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description') if
+ e.get('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about') ==
+ 'urn:mozilla:install-manifest'
+ ]
+
+ x = e.find('.//{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+
+ if len(about) == 0:
+ addon_element = tree.find('.//{http://www.mozilla.org/2004/em-rdf#}id')
+ addon_id = addon_element.text
+ else:
+ addon_id = about[0].get('{http://www.mozilla.org/2004/em-rdf#}id')
+
+ addon_path = os.path.join(self.profile, 'extensions', addon_id)
+ copytree(addon, addon_path, preserve_symlinks=1)
+ self.addons_installed.append(addon_path)
+
+ 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 clean_preferences(self):
+ """Removed preferences added by mozrunner."""
+ lines = open(os.path.join(self.profile, 'user.js'), 'r').read().splitlines()
+ s = lines.index('#MozRunner Prefs Start') ; e = lines.index('#MozRunner Prefs End')
+ cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:])
+ f = open(os.path.join(self.profile, 'user.js'), 'w')
+ f.write(cleaned_prefs) ; f.flush() ; f.close()
+
+ 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,
+ }
+
+ @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,
+ }
+ 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:
+ # assumes self.app_name is defined, as it should be for
+ # implementors
+ import _winreg
+ app_key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, r"Software\Mozilla\Mozilla %s" % self.app_name)
+ 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: # XXX not sure what type of exception this should be
+ 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 name in reversed(self.names):
+ appdir = os.path.join('Applications', name.capitalize()+'.app')
+ if os.path.isdir(os.path.join(os.path.expanduser('~/'), appdir)):
+ binary = os.path.join(os.path.expanduser('~/'), appdir,
+ 'Contents/MacOS/'+name+'-bin')
+ elif os.path.isdir('/'+appdir):
+ binary = os.path.join("/"+appdir, 'Contents/MacOS/'+name+'-bin')
+
+ if binary is not None:
+ if not os.path.isfile(binary):
+ binary = binary.replace(name+'-bin', 'firefox-bin')
+ if not os.path.isfile(binary):
+ binary = None
+ 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
+
+ @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
+
+ 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.3/python-lib/mozrunner/killableprocess.py b/tools/addon-sdk-1.3/python-lib/mozrunner/killableprocess.py
new file mode 100644
index 0000000..892ed87
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/mozrunner/qijo.py b/tools/addon-sdk-1.3/python-lib/mozrunner/qijo.py
new file mode 100644
index 0000000..d4da88c
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/mozrunner/qijo.py
@@ -0,0 +1,162 @@
+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.3/python-lib/mozrunner/winprocess.py b/tools/addon-sdk-1.3/python-lib/mozrunner/winprocess.py
new file mode 100644
index 0000000..4bea3fc
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/mozrunner/wpk.py b/tools/addon-sdk-1.3/python-lib/mozrunner/wpk.py
new file mode 100644
index 0000000..ecf5fb5
--- /dev/null
+++ b/tools/addon-sdk-1.3/python-lib/mozrunner/wpk.py
@@ -0,0 +1,76 @@
+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.3/python-lib/simplejson/__init__.py b/tools/addon-sdk-1.3/python-lib/simplejson/__init__.py
new file mode 100644
index 0000000..adcce7e
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/simplejson/decoder.py b/tools/addon-sdk-1.3/python-lib/simplejson/decoder.py
new file mode 100644
index 0000000..baf10e9
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/simplejson/encoder.py b/tools/addon-sdk-1.3/python-lib/simplejson/encoder.py
new file mode 100644
index 0000000..772a261
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/simplejson/scanner.py b/tools/addon-sdk-1.3/python-lib/simplejson/scanner.py
new file mode 100644
index 0000000..2a18390
--- /dev/null
+++ b/tools/addon-sdk-1.3/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.3/python-lib/simplejson/tool.py b/tools/addon-sdk-1.3/python-lib/simplejson/tool.py
new file mode 100644
index 0000000..caa1818
--- /dev/null
+++ b/tools/addon-sdk-1.3/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()