From 545e470e52f0ac6a3a072bf559c796b42c6066b6 Mon Sep 17 00:00:00 2001 From: Ben Murdoch Date: Tue, 15 Jun 2010 19:36:43 +0100 Subject: Merge webkit.org at r61121: Initial merge by git. Change-Id: Icd6db395c62285be384d137164d95d7466c98760 --- WebKitTools/Scripts/build-webkit | 5 +- WebKitTools/Scripts/do-webcore-rename | 68 +- WebKitTools/Scripts/ensure-valid-python | 23 +- WebKitTools/Scripts/old-run-webkit-tests | 18 + WebKitTools/Scripts/run-bindings-tests | 3 +- WebKitTools/Scripts/update-webkit-auxiliary-libs | 21 +- WebKitTools/Scripts/webkitdirs.pm | 15 +- .../Scripts/webkitpy/common/checkout/changelog.py | 10 + .../webkitpy/common/checkout/changelog_unittest.py | 14 +- .../Scripts/webkitpy/common/checkout/scm.py | 55 +- .../webkitpy/common/checkout/scm_unittest.py | 60 +- .../Scripts/webkitpy/common/config/committers.py | 7 +- .../Scripts/webkitpy/common/net/bugzilla.py | 39 +- .../webkitpy/common/net/bugzilla_unittest.py | 19 +- .../Scripts/webkitpy/common/net/buildbot.py | 7 +- .../webkitpy/common/net/buildbot_unittest.py | 12 + .../Scripts/webkitpy/common/net/rietveld.py | 6 +- .../webkitpy/common/system/outputcapture.py | 7 +- WebKitTools/Scripts/webkitpy/common/system/user.py | 9 + .../json_layout_results_generator.py | 1 + .../layout_package/json_results_generator.py | 28 +- .../Scripts/webkitpy/layout_tests/port/base.py | 8 + .../Scripts/webkitpy/layout_tests/port/chromium.py | 7 + .../webkitpy/layout_tests/port/chromium_linux.py | 12 +- .../webkitpy/layout_tests/port/chromium_mac.py | 9 +- .../webkitpy/layout_tests/port/chromium_win.py | 9 +- .../webkitpy/layout_tests/port/websocket_server.py | 9 +- .../Scripts/webkitpy/thirdparty/__init__.py | 7 +- .../thirdparty/autoinstalled/.mechanize.url | 1 - .../webkitpy/thirdparty/autoinstalled/.pep8.py.url | 1 - .../webkitpy/thirdparty/autoinstalled/README | 2 - .../webkitpy/thirdparty/autoinstalled/__init__.py | 1 - .../autoinstalled/clientform/.ClientForm.py.url | 1 - .../autoinstalled/clientform/ClientForm.py | 3401 -------------------- .../autoinstalled/clientform/__init__.py | 1 - .../thirdparty/autoinstalled/irc/.ircbot.py.url | 1 - .../thirdparty/autoinstalled/irc/.irclib.py.url | 1 - .../thirdparty/autoinstalled/irc/__init__.py | 1 - .../thirdparty/autoinstalled/irc/ircbot.py | 438 --- .../thirdparty/autoinstalled/irc/irclib.py | 1560 --------- .../thirdparty/autoinstalled/mechanize/__init__.py | 140 - .../thirdparty/autoinstalled/mechanize/_auth.py | 522 --- .../autoinstalled/mechanize/_beautifulsoup.py | 1080 ------- .../autoinstalled/mechanize/_clientcookie.py | 1707 ---------- .../thirdparty/autoinstalled/mechanize/_debug.py | 28 - .../thirdparty/autoinstalled/mechanize/_file.py | 60 - .../autoinstalled/mechanize/_firefox3cookiejar.py | 249 -- .../thirdparty/autoinstalled/mechanize/_gzip.py | 103 - .../autoinstalled/mechanize/_headersutil.py | 232 -- .../thirdparty/autoinstalled/mechanize/_html.py | 631 ---- .../thirdparty/autoinstalled/mechanize/_http.py | 758 ----- .../autoinstalled/mechanize/_lwpcookiejar.py | 185 -- .../autoinstalled/mechanize/_mechanize.py | 676 ---- .../autoinstalled/mechanize/_mozillacookiejar.py | 161 - .../autoinstalled/mechanize/_msiecookiejar.py | 388 --- .../thirdparty/autoinstalled/mechanize/_opener.py | 436 --- .../autoinstalled/mechanize/_pullparser.py | 390 --- .../thirdparty/autoinstalled/mechanize/_request.py | 87 - .../autoinstalled/mechanize/_response.py | 527 --- .../thirdparty/autoinstalled/mechanize/_rfc3986.py | 241 -- .../thirdparty/autoinstalled/mechanize/_seek.py | 16 - .../autoinstalled/mechanize/_sockettimeout.py | 6 - .../autoinstalled/mechanize/_testcase.py | 73 - .../thirdparty/autoinstalled/mechanize/_upgrade.py | 40 - .../thirdparty/autoinstalled/mechanize/_urllib2.py | 55 - .../autoinstalled/mechanize/_useragent.py | 352 -- .../thirdparty/autoinstalled/mechanize/_util.py | 291 -- .../webkitpy/thirdparty/autoinstalled/pep8.py | 1254 -------- .../autoinstalled/rietveld/.upload.py.url | 1 - .../thirdparty/autoinstalled/rietveld/__init__.py | 1 - .../thirdparty/autoinstalled/rietveld/upload.py | 1702 ---------- .../Scripts/webkitpy/tool/bot/irc_command.py | 27 + .../webkitpy/tool/bot/irc_command_unittest.py | 38 + .../Scripts/webkitpy/tool/bot/queueengine.py | 2 +- .../Scripts/webkitpy/tool/bot/sheriffircbot.py | 22 +- .../webkitpy/tool/bot/sheriffircbot_unittest.py | 6 +- .../Scripts/webkitpy/tool/commands/download.py | 12 + .../webkitpy/tool/commands/download_unittest.py | 4 + .../tool/commands/earlywarningsystem_unittest.py | 13 +- .../Scripts/webkitpy/tool/commands/queues.py | 48 +- .../webkitpy/tool/commands/queues_unittest.py | 27 +- .../Scripts/webkitpy/tool/commands/queuestest.py | 67 +- .../Scripts/webkitpy/tool/commands/upload.py | 2 - .../webkitpy/tool/commands/upload_unittest.py | 4 - WebKitTools/Scripts/webkitpy/tool/mocktool.py | 35 +- .../Scripts/webkitpy/tool/steps/abstractstep.py | 7 +- .../Scripts/webkitpy/tool/steps/confirmdiff.py | 3 + WebKitTools/Scripts/webkitpy/tool/steps/options.py | 1 - .../Scripts/webkitpy/tool/steps/postcodereview.py | 42 +- .../webkitpy/tool/steps/preparechangelog.py | 16 +- .../tool/steps/preparechangelog_unittest.py | 55 + 91 files changed, 685 insertions(+), 18035 deletions(-) mode change 100644 => 100755 WebKitTools/Scripts/ensure-valid-python delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.mechanize.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.pep8.py.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/README delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/__init__.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/.ClientForm.py.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/ClientForm.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/__init__.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/irc/.ircbot.py.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/irc/.irclib.py.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/irc/__init__.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/irc/ircbot.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/irc/irclib.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/__init__.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_auth.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_beautifulsoup.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_clientcookie.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_debug.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_file.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_firefox3cookiejar.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_gzip.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_headersutil.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_html.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_http.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_lwpcookiejar.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_mechanize.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_mozillacookiejar.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_msiecookiejar.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_opener.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_pullparser.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_request.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_response.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_rfc3986.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_seek.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_sockettimeout.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_testcase.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_upgrade.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_urllib2.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_useragent.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/mechanize/_util.py delete mode 100755 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/pep8.py delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/rietveld/.upload.py.url delete mode 100644 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/rietveld/__init__.py delete mode 100755 WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/rietveld/upload.py create mode 100644 WebKitTools/Scripts/webkitpy/tool/bot/irc_command_unittest.py create mode 100644 WebKitTools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py (limited to 'WebKitTools/Scripts') diff --git a/WebKitTools/Scripts/build-webkit b/WebKitTools/Scripts/build-webkit index e70117f..a29a6fd 100755 --- a/WebKitTools/Scripts/build-webkit +++ b/WebKitTools/Scripts/build-webkit @@ -56,7 +56,7 @@ my $makeArgs; my $startTime = time(); my ($threeDCanvasSupport, $threeDRenderingSupport, $channelMessagingSupport, $clientBasedGeolocationSupport, $databaseSupport, $datagridSupport, $datalistSupport, - $domStorageSupport, $eventsourceSupport, $filtersSupport, $geolocationSupport, $iconDatabaseSupport, $indexedDatabaseSupport, + $domStorageSupport, $eventsourceSupport, $filtersSupport, $geolocationSupport, $iconDatabaseSupport, $imageResizerSupport, $indexedDatabaseSupport, $javaScriptDebuggerSupport, $mathmlSupport, $offlineWebApplicationSupport, $rubySupport, $systemMallocSupport, $sandboxSupport, $sharedWorkersSupport, $svgSupport, $svgAnimationSupport, $svgAsImageSupport, $svgDOMObjCBindingsSupport, $svgFontsSupport, $svgForeignObjectSupport, $svgUseSupport, $videoSupport, $webSocketsSupport, $wmlSupport, $wcssSupport, $xhtmlmpSupport, $workersSupport, @@ -105,6 +105,9 @@ my @features = ( { option => "icon-database", desc => "Toggle Icon database support", define => "ENABLE_ICONDATABASE", default => 1, value => \$iconDatabaseSupport }, + + { option => "image-resizer", desc => "Toggle Image Resizer API support", + define => "ENABLE_IMAGE_RESIZER", default => 0, value => \$imageResizerSupport }, { option => "indexed-database", desc => "Toggle Indexed Database API support", define => "ENABLE_INDEXED_DATABASE", default => 0, value => \$indexedDatabaseSupport }, diff --git a/WebKitTools/Scripts/do-webcore-rename b/WebKitTools/Scripts/do-webcore-rename index 32dd05e..e2ce019 100755 --- a/WebKitTools/Scripts/do-webcore-rename +++ b/WebKitTools/Scripts/do-webcore-rename @@ -29,16 +29,42 @@ # Script to do a rename in JavaScriptCore, WebCore, and WebKit. use strict; + +use File::Find; use FindBin; +use Getopt::Long qw(:config pass_through); + use lib $FindBin::Bin; use webkitdirs; -use File::Find; use VCSUtils; setConfiguration(); chdirWebKit(); -my %words; +my $showHelp; +my $verbose; + +my $programName = basename($0); +my $usage = < \$showHelp, + 'verbose|v' => \$verbose, +); + +if (!$getOptionsResult || $showHelp) { + print STDERR $usage; + exit 1; +} + +my @directoriesToIgnoreList = ( + "icu", +); +my %directoriesToIgnore = map { $_ => 1 } @directoriesToIgnoreList; # find all files we want to process @@ -52,17 +78,13 @@ sub wanted { my $file = $_; - if ($file eq "icu") { - $File::Find::prune = 1; - return; - } - - if ($file =~ /^\../) { + # Ignore excluded and hidden files/directories. + if ($directoriesToIgnore{$file} or $file =~ /^\../ or $file =~ /^ChangeLog/) { + print "Ignoring $File::Find::name\n" if $verbose; $File::Find::prune = 1; return; } - return if $file =~ /^ChangeLog/; return if -d $file; push @paths, $File::Find::name; @@ -70,28 +92,10 @@ sub wanted # Setting isDOMTypeRename to 1 rather than 0 expands the regexps used # below to handle custom JavaScript bindings. -my $isDOMTypeRename = 1; +my $isDOMTypeRename = 0; my %renames = ( - "WebGLArray" => "ArrayBufferView", - "WebGLArrayBuffer" => "ArrayBuffer", - "WebGLByteArray" => "Int8Array", - "WebGLFloatArray" => "FloatArray", - "WebGLIntArray" => "Int32Array", - "WebGLIntegralTypedArrayBase" => "IntegralTypedArrayBase", - "WebGLShortArray" => "Int16Array", - "WebGLUnsignedByteArray" => "Uint8Array", - "WebGLUnsignedIntArray" => "Uint32Array", - "WebGLUnsignedShortArray" => "Uint16Array", - "WebGLTypedArrayBase" => "TypedArrayBase", - # JSDOMWindow constructors. - "webGLArrayBuffer" => "arrayBuffer", - "webGLByteArray" => "int8Array", - "webGLFloatArray" => "floatArray", - "webGLIntArray" => "int32Array", - "webGLShortArray" => "int16Array", - "webGLUnsignedByteArray" => "uint8Array", - "webGLUnsignedIntArray" => "uint32Array", - "webGLUnsignedShortArray" => "uint16Array", + # Renames go here in the form of: + # "Tokenizer" => "DocumentParser", ); my %renamesContemplatedForTheFuture = ( @@ -224,7 +228,7 @@ for my $file (sort @paths) { my $contents; { local $/; - open FILE, $file or die; + open FILE, $file or die "Failed to open $file"; $contents = ; close FILE; } @@ -242,7 +246,7 @@ for my $file (sort @paths) { } if ($newContents ne $contents) { - open FILE, ">", $file or die; + open FILE, ">", $file or die "Failed to open $file"; print FILE $newContents; close FILE; } diff --git a/WebKitTools/Scripts/ensure-valid-python b/WebKitTools/Scripts/ensure-valid-python old mode 100644 new mode 100755 index c21ad4e..4640a01 --- a/WebKitTools/Scripts/ensure-valid-python +++ b/WebKitTools/Scripts/ensure-valid-python @@ -31,6 +31,7 @@ use File::Basename; use File::Spec; use File::Temp qw(tempdir); use FindBin; +use Getopt::Long; use lib $FindBin::Bin; use webkitdirs; @@ -41,7 +42,7 @@ my $macPythonMD5 = "84489bba813fdbb6041b69d4310a86da"; my $macPythonInstallerName = "Python.mpkg"; # We could use a consistent download location, like the source or build directory. -my $tempDirectory = File::Temp->newdir(); +my $tempDirectory = File::Temp->tempdir("WebKitPythonXXXX"); my $downloadDirectory = $tempDirectory; my $mountPoint = File::Spec->join($tempDirectory, "mount"); @@ -117,11 +118,27 @@ sub installMacPython() sub main() { + my $checkOnly = 0; + my $showHelp = 0; + my $getOptionsResult = GetOptions( + 'check-only!' => \$checkOnly, + 'help|h' => \$showHelp, + ); + if (!$getOptionsResult || $showHelp) { + print STDERR <catfile($tmpDir, "layout-test-results"); @@ -224,6 +226,11 @@ if (!defined($platform)) { $platform = "undefined"; } +if (!checkPythonVersion()) { + print "WARNING: Your platform does not have Python 2.5+, which is required to run websocket server, so disabling websocket/tests.\n"; + $testWebSocket = 0; +} + my $programName = basename($0); my $launchSafariDefault = $launchSafari ? "launch" : "do not launch"; my $httpDefault = $testHTTP ? "run" : "do not run"; @@ -457,6 +464,9 @@ if (!$testHTTP) { $ignoredDirectories{'http'} = 1; $ignoredDirectories{'websocket'} = 1; } +if (!$testWebSocket) { + $ignoredDirectories{'websocket'} = 1; +} if (!$testMedia) { $ignoredDirectories{'media'} = 1; @@ -1358,6 +1368,7 @@ sub openDumpTool() # Port spesifics if (isQt()) { $CLEAN_ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins"; + $CLEAN_ENV{QT_DRT_WEBVIEW_MODE} = $ENV{"QT_DRT_WEBVIEW_MODE"}; } my @args = ($dumpTool, @toolArgs); @@ -1425,6 +1436,13 @@ sub configureAndOpenHTTPDIfNeeded() $isHttpdOpen = openHTTPD(@args); } +sub checkPythonVersion() +{ + # we have not chdir to sourceDir yet. + system sourceDir() . "/WebKitTools/Scripts/ensure-valid-python", "--check-only"; + return exitStatus($?) == 0; +} + sub openWebSocketServerIfNeeded() { return 1 if $isWebSocketServerOpen; diff --git a/WebKitTools/Scripts/run-bindings-tests b/WebKitTools/Scripts/run-bindings-tests index 56b2aff..4a093d1 100755 --- a/WebKitTools/Scripts/run-bindings-tests +++ b/WebKitTools/Scripts/run-bindings-tests @@ -116,6 +116,7 @@ def main(argv): 'V8', 'ObjC', 'GObject', + 'CPP' ] for generator in generators: @@ -128,7 +129,7 @@ def main(argv): print 'All tests passed!' return 0 else: - print '(To update the reference files, execute "run-bindings-test --reset-results")' + print '(To update the reference files, execute "run-bindings-tests --reset-results")' return -1 diff --git a/WebKitTools/Scripts/update-webkit-auxiliary-libs b/WebKitTools/Scripts/update-webkit-auxiliary-libs index 9c52449..19e4ad3 100755 --- a/WebKitTools/Scripts/update-webkit-auxiliary-libs +++ b/WebKitTools/Scripts/update-webkit-auxiliary-libs @@ -31,11 +31,12 @@ use strict; use warnings; -use HTTP::Date qw(str2time); use File::Find; -use File::Temp (); use File::Spec; +use File::Temp (); use FindBin; +use HTTP::Date qw(str2time); +use POSIX; use lib $FindBin::Bin; use webkitdirs; @@ -58,9 +59,21 @@ my $tmpDir = File::Spec->rel2abs(File::Temp::tempdir("webkitlibsXXXXXXX", TMPDIR print "Checking Last-Modified date of $zipFile...\n"; my $result = system "curl -s -I $auxiliaryLibsURL | grep Last-Modified > \"$tmpDir/$file.headers\""; -print STDERR "Couldn't check Last-Modified date of new $zipFile.\n" if $result; -if (!$result && open NEW, "$tmpDir/$file.headers") { +if (WEXITSTATUS($result)) { + print STDERR "Couldn't check Last-Modified date of new $zipFile.\n"; + print STDERR "Please ensure that $auxiliaryLibsURL is reachable.\n"; + + if (! -f "$webkitLibrariesDir/$file.headers") { + print STDERR "Unable to check Last-Modified date and no version of $file to fall back to.\n"; + exit 1; + } + + print STDERR "Falling back to existing version of $file.\n"; + exit 0; +} + +if (open NEW, "$tmpDir/$file.headers") { my $new = lastModifiedToUnixTime(); close NEW; diff --git a/WebKitTools/Scripts/webkitdirs.pm b/WebKitTools/Scripts/webkitdirs.pm index ca17757..33ae7da 100644 --- a/WebKitTools/Scripts/webkitdirs.pm +++ b/WebKitTools/Scripts/webkitdirs.pm @@ -115,7 +115,9 @@ sub determineBaseProductDir return if defined $baseProductDir; determineSourceDir(); - if (isAppleMacWebKit()) { + $baseProductDir = $ENV{"WEBKITOUTPUTDIR"}; + + if (!defined($baseProductDir) and isAppleMacWebKit()) { # Silently remove ~/Library/Preferences/xcodebuild.plist which can # cause build failure. The presence of # ~/Library/Preferences/xcodebuild.plist can prevent xcodebuild from @@ -148,7 +150,7 @@ sub determineBaseProductDir } if (!defined($baseProductDir)) { # Port-spesific checks failed, use default - $baseProductDir = $ENV{"WEBKITOUTPUTDIR"} || "$sourceDir/WebKitBuild"; + $baseProductDir = "$sourceDir/WebKitBuild"; } if (isGit() && isGitBranchBuild()) { @@ -1434,16 +1436,15 @@ sub buildGtkProject($$@) return buildAutotoolsProject($clean, @buildArgs); } -sub buildChromiumMakefile($$$) +sub buildChromiumMakefile($$) { - my ($dir, $target, $clean) = @_; - chdir $dir; + my ($target, $clean) = @_; if ($clean) { return system qw(rm -rf out); } my $config = configuration(); my $numCpus = (grep /processor/, `cat /proc/cpuinfo`) || 1; - my @command = ("make", "-j$numCpus", "BUILDTYPE=$config", $target); + my @command = ("make", "-fMakefile.chromium", "-j$numCpus", "BUILDTYPE=$config", $target); print join(" ", @command) . "\n"; return system @command; } @@ -1501,7 +1502,7 @@ sub buildChromium($@) $result = buildChromiumVisualStudioProject("WebKit/chromium/WebKit.sln", $clean); } elsif (isLinux()) { # Linux build - build using make. - $ result = buildChromiumMakefile("WebKit/chromium/", "all", $clean); + $ result = buildChromiumMakefile("all", $clean); } else { print STDERR "This platform is not supported by chromium.\n"; } diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py index 6220fbd..40657eb 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/changelog.py @@ -36,6 +36,8 @@ import textwrap from webkitpy.common.system.deprecated_logging import log from webkitpy.common.config.committers import CommitterList +from webkitpy.common.net.bugzilla import parse_bug_id + def view_source_url(revision_number): # FIMXE: This doesn't really belong in this file, but we don't have a @@ -88,6 +90,9 @@ class ChangeLogEntry(object): def contents(self): return self._contents + def bug_id(self): + return parse_bug_id(self._contents) + # FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead. class ChangeLog(object): @@ -183,3 +188,8 @@ class ChangeLog(object): for line in fileinput.FileInput(self.path, inplace=1): # Trailing comma suppresses printing newline print line.replace("NOBODY (OOPS!)", reviewer.encode("utf-8")), + + def set_short_description_and_bug_url(self, short_description, bug_url): + message = "%s\n %s" % (short_description, bug_url) + for line in fileinput.FileInput(self.path, inplace=1): + print line.replace("Need a short description and bug URL (OOPS!)", message.encode("utf-8")), diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py index 864428a..6aeb1f8 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/changelog_unittest.py @@ -38,7 +38,7 @@ from StringIO import StringIO from webkitpy.common.checkout.changelog import * -class ChangeLogsTest(unittest.TestCase): +class ChangeLogTest(unittest.TestCase): _example_entry = u'''2009-08-17 Peter Kasting @@ -131,6 +131,18 @@ class ChangeLogsTest(unittest.TestCase): os.remove(changelog_path) self.assertEquals(actual_contents, expected_contents) + def test_set_short_description_and_bug_url(self): + changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog) + changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8")) + short_description = "A short description" + bug_url = "http://example.com/b/2344" + ChangeLog(changelog_path).set_short_description_and_bug_url(short_description, bug_url) + actual_contents = self._read_file_contents(changelog_path, "utf-8") + expected_message = "%s\n %s" % (short_description, bug_url) + expected_contents = changelog_contents.replace("Need a short description and bug URL (OOPS!)", expected_message) + os.remove(changelog_path) + self.assertEquals(actual_contents, expected_contents) + _revert_message = """ Unreviewed, rolling out r12345. http://trac.webkit.org/changeset/12345 http://example.com/123 diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py index eea76be..fc4c6fd 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm.py @@ -240,7 +240,7 @@ class SCM: def supports_local_commits(): raise NotImplementedError, "subclasses must implement" - def svn_merge_base(): + def remote_merge_base(): raise NotImplementedError, "subclasses must implement" def commit_locally_with_message(self, message): @@ -465,11 +465,11 @@ class Git(SCM): def discard_local_commits(self): # FIXME: This should probably use cwd=self.checkout_root - self.run(['git', 'reset', '--hard', self.svn_branch_name()]) + self.run(['git', 'reset', '--hard', self.remote_branch_ref()]) def local_commits(self): # FIXME: This should probably use cwd=self.checkout_root - return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.svn_branch_name()]).splitlines() + return self.run(['git', 'log', '--pretty=oneline', 'HEAD...' + self.remote_branch_ref()]).splitlines() def rebase_in_progress(self): return os.path.exists(os.path.join(self.checkout_root, '.git/rebase-apply')) @@ -507,7 +507,7 @@ class Git(SCM): return git_commit if self.should_squash(squash): - return self.svn_merge_base() + return self.remote_merge_base() # FIXME: Non-squash behavior should match commit_with_message. It raises an error # if there are working copy changes and --squash or --no-squash wasn't passed in. @@ -602,14 +602,14 @@ class Git(SCM): if num_local_commits > 1 or (num_local_commits > 0 and not self.working_directory_is_clean()): raise ScriptError(message=self._get_squash_error_message(num_local_commits)) - if squash and self._svn_branch_has_extra_commits(): + if squash and self._remote_branch_has_extra_commits(): raise ScriptError(message="Cannot use --squash when HEAD is not fully merged/rebased to %s. " - "This branch needs to be synced first." % self.svn_branch_name()) + "This branch needs to be synced first." % self.remote_branch_ref()) return squash - def _svn_branch_has_extra_commits(self): - return len(run_command(['git', 'rev-list', '--max-count=1', self.svn_branch_name(), '^HEAD'])) + def _remote_branch_has_extra_commits(self): + return len(run_command(['git', 'rev-list', '--max-count=1', self.remote_branch_ref(), '^HEAD'])) def commit_with_message(self, message, username=None, git_commit=None, squash=None): # Username is ignored during Git commits. @@ -624,7 +624,7 @@ class Git(SCM): squash = self.should_squash(squash) if squash: - self.run(['git', 'reset', '--soft', self.svn_branch_name()]) + self.run(['git', 'reset', '--soft', self.remote_branch_ref()]) self.commit_locally_with_message(message) elif not self.working_directory_is_clean(): if not len(self.local_commits()): @@ -650,8 +650,8 @@ class Git(SCM): # We want to squash all this branch's commits into one commit with the proper description. # We do this by doing a "merge --squash" into a new commit branch, then dcommitting that. - MERGE_BRANCH = 'webkit-patch-land' - self.delete_branch(MERGE_BRANCH) + MERGE_BRANCH_NAME = 'webkit-patch-land' + self.delete_branch(MERGE_BRANCH_NAME) # We might be in a directory that's present in this branch but not in the # trunk. Move up to the top of the tree so that git commands that expect a @@ -662,7 +662,7 @@ class Git(SCM): # We wrap in a try...finally block so if anything goes wrong, we clean up the branches. commit_succeeded = True try: - self.run(['git', 'checkout', '-q', '-b', MERGE_BRANCH, self.svn_branch_name()]) + self.run(['git', 'checkout', '-q', '-b', MERGE_BRANCH_NAME, self.remote_branch_ref()]) for commit in commit_ids: # We're on a different branch now, so convert "head" to the branch name. @@ -681,7 +681,7 @@ class Git(SCM): # And then swap back to the original branch and clean up. self.clean_working_directory() self.run(['git', 'checkout', '-q', branch_name]) - self.delete_branch(MERGE_BRANCH) + self.delete_branch(MERGE_BRANCH_NAME) return output @@ -693,18 +693,31 @@ class Git(SCM): return self.run(['git', 'svn', 'log', '--limit=1']) # Git-specific methods: + def _branch_ref_exists(self, branch_ref): + return self.run(['git', 'show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0 - def delete_branch(self, branch): - if self.run(['git', 'show-ref', '--quiet', '--verify', 'refs/heads/' + branch], return_exit_code=True) == 0: - self.run(['git', 'branch', '-D', branch]) + def delete_branch(self, branch_name): + if self._branch_ref_exists('refs/heads/' + branch_name): + self.run(['git', 'branch', '-D', branch_name]) - def svn_merge_base(self): - return self.run(['git', 'merge-base', self.svn_branch_name(), 'HEAD']).strip() + def remote_merge_base(self): + return self.run(['git', 'merge-base', self.remote_branch_ref(), 'HEAD']).strip() + + def remote_branch_ref(self): + # Use references so that we can avoid collisions, e.g. we don't want to operate on refs/heads/trunk if it exists. - def svn_branch_name(self): # FIXME: This should so something like: Git.read_git_config('svn-remote.svn.fetch').split(':')[1] # but that doesn't work if the git repo is tracking multiple svn branches. - return 'trunk' + remote_branch_refs = [ + 'refs/remotes/trunk', # A git-svn checkout as per http://trac.webkit.org/wiki/UsingGitWithWebKit. + 'refs/remotes/origin/master', # A git clone of git://git.webkit.org/WebKit.git that is not tracking svn. + ] + + for ref in remote_branch_refs: + if self._branch_ref_exists(ref): + return ref + + raise ScriptError(message="Can't find a branch to diff against. %s branches do not exist." % " and ".join(remote_branch_refs)) def commit_locally_with_message(self, message): self.run(['git', 'commit', '--all', '-F', '-'], input=message) @@ -726,7 +739,7 @@ class Git(SCM): # A B : [A, B] (different from git diff, which would use "rev-list A..B") def commit_ids_from_commitish_arguments(self, args): if not len(args): - args.append('%s..HEAD' % self.svn_branch_name()) + args.append('%s..HEAD' % self.remote_branch_ref()) commit_ids = [] for commitish in args: diff --git a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py index 8eea4d8..36a1d1c 100644 --- a/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/checkout/scm_unittest.py @@ -635,25 +635,63 @@ Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== class GitTest(SCMTest): - def _setup_git_clone_of_svn_repository(self): + def setUp(self): + """Sets up fresh git repository with one commit. Then setups a second git + repo that tracks the first one.""" + self.original_dir = os.getcwd() + + self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2") + run_command(['git', 'init', self.untracking_checkout_path]) + + os.chdir(self.untracking_checkout_path) + write_into_file_at_path('foo_file', 'foo') + run_command(['git', 'add', 'foo_file']) + run_command(['git', 'commit', '-am', 'dummy commit']) + self.untracking_scm = detect_scm_system(self.untracking_checkout_path) + + self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") + run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) + os.chdir(self.tracking_git_checkout_path) + self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) + + def tearDown(self): + # Change back to a valid directory so that later calls to os.getcwd() do not fail. + os.chdir(self.original_dir) + run_command(['rm', '-rf', self.tracking_git_checkout_path]) + run_command(['rm', '-rf', self.untracking_checkout_path]) + + def test_remote_branch_ref(self): + self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master') + + os.chdir(self.untracking_checkout_path) + self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref) + + +class GitSVNTest(SCMTest): + + def _setup_git_checkout(self): self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") # --quiet doesn't make git svn silent, so we use run_silent to redirect output run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) + os.chdir(self.git_checkout_path) - def _tear_down_git_clone_of_svn_repository(self): + def _tear_down_git_checkout(self): + # Change back to a valid directory so that later calls to os.getcwd() do not fail. + os.chdir(self.original_dir) run_command(['rm', '-rf', self.git_checkout_path]) def setUp(self): + self.original_dir = os.getcwd() + SVNTestRepository.setup(self) - self._setup_git_clone_of_svn_repository() - os.chdir(self.git_checkout_path) + self._setup_git_checkout() self.scm = detect_scm_system(self.git_checkout_path) # For historical reasons, we test some checkout code here too. self.checkout = Checkout(self.scm) def tearDown(self): SVNTestRepository.tear_down(self) - self._tear_down_git_clone_of_svn_repository() + self._tear_down_git_checkout() def test_detection(self): scm = detect_scm_system(self.git_checkout_path) @@ -683,25 +721,24 @@ class GitTest(SCMTest): self.assertEqual(len(self.scm.local_commits()), 0) def test_delete_branch(self): - old_branch = run_command(['git', 'symbolic-ref', 'HEAD']).strip() new_branch = 'foo' run_command(['git', 'checkout', '-b', new_branch]) self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) - run_command(['git', 'checkout', old_branch]) + run_command(['git', 'checkout', '-b', 'bar']) self.scm.delete_branch(new_branch) self.assertFalse(re.search(r'foo', run_command(['git', 'branch']))) - def test_svn_merge_base(self): + def test_remote_merge_base(self): # Diff to merge-base should include working-copy changes, # which the diff to svn_branch.. doesn't. test_file = os.path.join(self.git_checkout_path, 'test_file') write_into_file_at_path(test_file, 'foo') - diff_to_common_base = _git_diff(self.scm.svn_branch_name() + '..') - diff_to_merge_base = _git_diff(self.scm.svn_merge_base()) + diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..') + diff_to_merge_base = _git_diff(self.scm.remote_merge_base()) self.assertFalse(re.search(r'foo', diff_to_common_base)) self.assertTrue(re.search(r'foo', diff_to_merge_base)) @@ -888,6 +925,9 @@ class GitTest(SCMTest): scm = detect_scm_system(self.git_checkout_path) self.assertRaises(ScriptError, scm.commit_with_message, "another test commit", squash=True) + def test_remote_branch_ref(self): + self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk') + def test_reverse_diff(self): self._shared_test_reverse_diff() diff --git a/WebKitTools/Scripts/webkitpy/common/config/committers.py b/WebKitTools/Scripts/webkitpy/common/config/committers.py index d9c541f..37bd4eb 100644 --- a/WebKitTools/Scripts/webkitpy/common/config/committers.py +++ b/WebKitTools/Scripts/webkitpy/common/config/committers.py @@ -70,6 +70,7 @@ committers_unable_to_review = [ Committer("Alexander Kellett", ["lypanov@mac.com", "a-lists001@lypanov.net", "lypanov@kde.org"], "lypanov"), Committer("Alexander Pavlov", "apavlov@chromium.org"), Committer("Andre Boule", "aboule@apple.com"), + Committer("Andrei Popescu", "andreip@google.com", "andreip"), Committer("Andrew Wellington", ["andrew@webkit.org", "proton@wiretapped.net"], "proton"), Committer("Andras Becsi", "abecsi@webkit.org", "bbandix"), Committer("Andy Estes", "aestes@apple.com", "estes"), @@ -133,14 +134,15 @@ committers_unable_to_review = [ Committer("Krzysztof Kowalczyk", "kkowalczyk@gmail.com"), Committer("Levi Weintraub", "lweintraub@apple.com"), Committer("Mads Ager", "ager@chromium.org"), + Committer("Marcus Voltis Bulach", "bulach@chromium.org"), Committer("Matt Lilek", ["webkit@mattlilek.com", "pewtermoose@webkit.org"]), Committer("Matt Perry", "mpcomplete@chromium.org"), Committer("Maxime Britto", ["maxime.britto@gmail.com", "britto@apple.com"]), Committer("Maxime Simon", ["simon.maxime@gmail.com", "maxime.simon@webkit.org"], "maxime.simon"), - Committer("Martin Robinson", ["mrobinson@webkit.org", "martin.james.robinson@gmail.com"]), + Committer("Martin Robinson", ["mrobinson@igalia.com", "mrobinson@webkit.org", "martin.james.robinson@gmail.com"], "mrobinson"), Committer("Michelangelo De Simone", "michelangelo@webkit.org", "michelangelo"), Committer("Mike Belshe", ["mbelshe@chromium.org", "mike@belshe.com"]), - Committer("Mike Fenton", ["mike.fenton@torchmobile.com", "mifenton@rim.com"], "mfenton"), + Committer("Mike Fenton", ["mifenton@rim.com", "mike.fenton@torchmobile.com"], "mfenton"), Committer("Mike Thole", ["mthole@mikethole.com", "mthole@apple.com"]), Committer("Mikhail Naganov", "mnaganov@chromium.org"), Committer("MORITA Hajime", "morrita@google.com", "morrita"), @@ -166,6 +168,7 @@ committers_unable_to_review = [ Committer("Yong Li", ["yong.li.webkit@gmail.com", "yong.li@torchmobile.com"], "yong"), Committer("Yongjun Zhang", "yongjun.zhang@nokia.com"), Committer("Yuzo Fujishima", "yuzo@google.com", "yuzo"), + Committer("Zhenyao Mo", "zmo@google.com"), Committer("Zoltan Herczeg", "zherczeg@webkit.org", "zherczeg"), Committer("Zoltan Horvath", "zoltan@webkit.org", "zoltan"), ] diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py index 26d3652..40db32c 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla.py @@ -113,6 +113,9 @@ class Attachment(object): def commit_queue(self): return self._attachment_dictionary.get("commit-queue") + def in_rietveld(self): + return self._attachment_dictionary.get("in-rietveld") + def url(self): # FIXME: This should just return # self._bugzilla().attachment_url_for_id(self.id()). scm_unittest.py @@ -158,6 +161,9 @@ class Bug(object): def id(self): return self.bug_dictionary["id"] + def title(self): + return self.bug_dictionary["title"] + def assigned_to_email(self): return self.bug_dictionary["assigned_to_email"] @@ -201,6 +207,9 @@ class Bug(object): # a valid committer. return filter(lambda patch: patch.committer(), patches) + def in_rietveld_queue_patches(self): + return [patch for patch in self.patches() if patch.in_rietveld() == None] + # A container for all of the logic for making and parsing buzilla queries. class BugzillaQueries(object): @@ -264,6 +273,16 @@ class BugzillaQueries(object): return sum([self._fetch_bug(bug_id).commit_queued_patches() for bug_id in self.fetch_bug_ids_from_commit_queue()], []) + def fetch_first_patch_from_rietveld_queue(self): + # rietveld-queue processes all patches that don't have in-rietveld set. + query_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=notsubstring&value0-0-0=in-rietveld&field0-1-0=attachments.ispatch&type0-1-0=equals&value0-1-0=1&order=Last+Changed&field0-2-0=attachments.isobsolete&type0-2-0=equals&value0-2-0=0" + bugs = self._fetch_bug_ids_advanced_query(query_url) + if not len(bugs): + return None + + patches = self._fetch_bug(bugs[0]).in_rietveld_queue_patches() + return patches[0] if len(patches) else None + def _fetch_bug_ids_from_review_queue(self): review_queue_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review?" return self._fetch_bug_ids_advanced_query(review_queue_url) @@ -474,6 +493,8 @@ class Bugzilla(object): self._parse_attachment_flag( element, 'review', attachment, 'reviewer_email') self._parse_attachment_flag( + element, 'in-rietveld', attachment, 'rietveld_uploader_email') + self._parse_attachment_flag( element, 'commit-queue', attachment, 'committer_email') return attachment @@ -592,7 +613,8 @@ class Bugzilla(object): comment_text=None, mark_for_review=False, mark_for_commit_queue=False, - mark_for_landing=False, bug_id=None): + mark_for_landing=False, + bug_id=None): self.browser['description'] = description self.browser['ispatch'] = ("1",) self.browser['flag_type-1'] = ('?',) if mark_for_review else ('X',) @@ -703,7 +725,7 @@ class Bugzilla(object): self.browser["blocked"] = unicode(blocked) if assignee == None: assignee = self.username - if assignee: + if assignee and not self.browser.find_control("assigned_to").disabled: self.browser["assigned_to"] = assignee self.browser["short_desc"] = bug_title self.browser["comment"] = bug_description @@ -730,8 +752,10 @@ class Bugzilla(object): # FIXME: This will break if we ever re-order attachment flags if flag_name == "review": return self.browser.find_control(type='select', nr=0) - if flag_name == "commit-queue": + elif flag_name == "commit-queue": return self.browser.find_control(type='select', nr=1) + elif flag_name == "in-rietveld": + return self.browser.find_control(type='select', nr=2) raise Exception("Don't know how to find flag named \"%s\"" % flag_name) def clear_attachment_flags(self, @@ -758,8 +782,8 @@ class Bugzilla(object): attachment_id, flag_name, flag_value, - comment_text, - additional_comment_text): + comment_text=None, + additional_comment_text=None): # FIXME: We need a way to test this function on a live bugzilla # instance. @@ -774,7 +798,10 @@ class Bugzilla(object): self.browser.open(self.attachment_url_for_id(attachment_id, 'edit')) self.browser.select_form(nr=1) - self.browser.set_value(comment_text, name='comment', nr=0) + + if comment_text: + self.browser.set_value(comment_text, name='comment', nr=0) + self._find_select_element_for_flag(flag_name).value = (flag_value,) self.browser.submit() diff --git a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py index ce992e7..3556121 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/bugzilla_unittest.py @@ -96,6 +96,11 @@ class BugzillaTest(unittest.TestCase): status="+" setter="two@test.com" /> + ''' _expected_example_attachment_parsing = { @@ -111,6 +116,8 @@ class BugzillaTest(unittest.TestCase): 'reviewer_email' : 'one@test.com', 'commit-queue' : '+', 'committer_email' : 'two@test.com', + 'in-rietveld': '+', + 'rietveld_uploader_email': 'three@test.com', 'attacher_email' : 'christian.plesner.hansen@gmail.com', } @@ -191,12 +198,12 @@ removed-because-it-was-really-long ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg== - - + + """ diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot.py index 6c6ed43..c849ef1 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/buildbot.py +++ b/WebKitTools/Scripts/webkitpy/common/net/buildbot.py @@ -333,7 +333,12 @@ class BuildBot(object): builder['built_revision'] = int(revision_string) \ if not re.match('\D', revision_string) \ else None - builder['is_green'] = not re.search('fail', cell.renderContents()) + + # FIXME: We treat slave lost as green even though it is not to + # work around the Qts bot being on a broken internet connection. + # The real fix is https://bugs.webkit.org/show_bug.cgi?id=37099 + builder['is_green'] = not re.search('fail', cell.renderContents()) or \ + not not re.search('lost', cell.renderContents()) status_link_regexp = r"builders/(?P.*)/builds/(?P\d+)" link_match = re.match(status_link_regexp, status_link['href']) diff --git a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py b/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py index 5e04745..5384321 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py +++ b/WebKitTools/Scripts/webkitpy/common/net/buildbot_unittest.py @@ -169,6 +169,10 @@ class BuildBotTest(unittest.TestCase): Qt Linux Release 47383
failed
compile-webkit idle
3 pending + + Qt Windows 32-bit Debug + 60563
failed
failed
slave
lost + building
ETA in
~ 5 mins
at 08:25 ''' _expected_example_one_box_parsings = [ @@ -196,6 +200,14 @@ class BuildBotTest(unittest.TestCase): 'activity': 'idle', 'pending_builds': 3, }, + { + 'is_green': True, + 'build_number' : 2090, + 'name': u'Qt Windows 32-bit Debug', + 'built_revision': 60563, + 'activity': 'building', + 'pending_builds': 0, + }, ] def test_status_parsing(self): diff --git a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py b/WebKitTools/Scripts/webkitpy/common/net/rietveld.py index 572d1fd..eccda3a 100644 --- a/WebKitTools/Scripts/webkitpy/common/net/rietveld.py +++ b/WebKitTools/Scripts/webkitpy/common/net/rietveld.py @@ -51,6 +51,10 @@ class Rietveld(object): if not message: raise ScriptError("Rietveld requires a message.") + # Rietveld has a 100 character limit on message length. + if len(message) > 100: + message = message[:100] + args = [ # First argument is empty string to mimic sys.argv. "", @@ -70,5 +74,5 @@ class Rietveld(object): # Use RealMain instead of calling upload from the commandline so that # we can pass in the diff ourselves. Otherwise, upload will just use # git diff for git checkouts, which doesn't respect --squash and --git-commit. - issue, patchset = upload.RealMain(args[1:], data=diff) + issue, patchset = upload.RealMain(args, data=diff) return issue diff --git a/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py b/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py index 592a669..68a3919 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py +++ b/WebKitTools/Scripts/webkitpy/common/system/outputcapture.py @@ -52,9 +52,12 @@ class OutputCapture(object): def restore_output(self): return (self._restore_output_with_name("stdout"), self._restore_output_with_name("stderr")) - def assert_outputs(self, testcase, function, args=[], kwargs={}, expected_stdout="", expected_stderr=""): + def assert_outputs(self, testcase, function, args=[], kwargs={}, expected_stdout="", expected_stderr="", expected_exception=None): self.capture_output() - return_value = function(*args, **kwargs) + if expected_exception: + return_value = testcase.assertRaises(expected_exception, function, *args, **kwargs) + else: + return_value = function(*args, **kwargs) (stdout_string, stderr_string) = self.restore_output() testcase.assertEqual(stdout_string, expected_stdout) testcase.assertEqual(stderr_string, expected_stderr) diff --git a/WebKitTools/Scripts/webkitpy/common/system/user.py b/WebKitTools/Scripts/webkitpy/common/system/user.py index 82fa0d3..b4df3cb 100644 --- a/WebKitTools/Scripts/webkitpy/common/system/user.py +++ b/WebKitTools/Scripts/webkitpy/common/system/user.py @@ -104,5 +104,14 @@ class User(object): response = raw_input("%s [Y/n]: " % message) return not response or response.lower() == "y" + def can_open_url(self): + try: + webbrowser.get() + return True + except webbrowser.Error, e: + return False + def open_url(self, url): + if not self.can_open_url(): + _log.warn("Failed to open %s" % url) webbrowser.open(url) diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py index cee44ad..bb214f7 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_layout_results_generator.py @@ -77,6 +77,7 @@ class JSONLayoutResultsGenerator(json_results_generator.JSONResultsGenerator): self._test_timings = dict( (path_to_name(test_tuple.filename), test_tuple.test_run_time) for test_tuple in test_timings) + self._svn_repositories = port.test_repository_paths() self._generate_json_output() diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py index 0993cbd..1cf1b95 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/layout_package/json_results_generator.py @@ -38,6 +38,8 @@ import time import urllib2 import xml.dom.minidom +from webkitpy.common.checkout import scm +from webkitpy.common.system.executive import ScriptError from webkitpy.layout_tests.layout_package import test_expectations import webkitpy.thirdparty.simplejson as simplejson @@ -46,6 +48,7 @@ _log = logging.getLogger("webkitpy.layout_tests.layout_package." class JSONResultsGenerator(object): + """A JSON results generator for generic tests.""" MAX_NUMBER_OF_BUILD_RESULTS_TO_LOG = 750 # Min time (seconds) that will be added to the JSON. @@ -60,8 +63,6 @@ class JSONResultsGenerator(object): RESULTS = "results" TIMES = "times" BUILD_NUMBERS = "buildNumbers" - WEBKIT_SVN = "webkitRevision" - CHROME_SVN = "chromeRevision" TIME = "secondsSinceEpoch" TESTS = "tests" @@ -102,7 +103,6 @@ class JSONResultsGenerator(object): all_tests: List of all the tests that were run. This should not include skipped tests. """ - self._port = port self._builder_name = builder_name self._build_name = build_name self._build_number = build_number @@ -114,6 +114,7 @@ class JSONResultsGenerator(object): self._passed_tests = passed_tests self._skipped_tests = skipped_tests self._all_tests = all_tests + self._svn_repositories = port.test_repository_paths() self._generate_json_output() @@ -132,6 +133,7 @@ class JSONResultsGenerator(object): Args: in_directory: The directory where svn is to be run. """ + if os.path.exists(os.path.join(in_directory, '.svn')): # Note: Not thread safe: http://bugs.python.org/issue2320 output = subprocess.Popen(["svn", "info", "--xml"], @@ -312,23 +314,11 @@ class JSONResultsGenerator(object): self._insert_item_into_raw_list(results_for_builder, self._build_number, self.BUILD_NUMBERS) - # These next two branches test to see which source repos we can - # pull revisions from. - if hasattr(self._port, 'path_from_webkit_base'): - path_to_webkit = self._port.path_from_webkit_base('WebCore') + # Include SVN revisions for the given repositories. + for (name, path) in self._svn_repositories: self._insert_item_into_raw_list(results_for_builder, - self._get_svn_revision(path_to_webkit), - self.WEBKIT_SVN) - - if hasattr(self._port, 'path_from_chromium_base'): - try: - path_to_chrome = self._port.path_from_chromium_base() - self._insert_item_into_raw_list(results_for_builder, - self._get_svn_revision(path_to_chrome), - self.CHROME_SVN) - except AssertionError: - # We're not in a Chromium checkout, that's ok. - pass + self._get_svn_revision(path), + name + 'Revision') self._insert_item_into_raw_list(results_for_builder, int(time.time()), diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py index 782c87c..e73579f 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/base.py @@ -537,6 +537,14 @@ class Port(object): expectations, determining search paths, and logging information.""" raise NotImplementedError('Port.version') + def test_repository_paths(self): + """Returns a list of (repository_name, repository_path) tuples + of its depending code base. By default it returns a list that only + contains a ('webkit', ) tuple. + """ + return [('webkit', self.layout_tests_dir())] + + _WDIFF_DEL = '##WDIFF_DEL##' _WDIFF_ADD = '##WDIFF_ADD##' _WDIFF_END = '##WDIFF_END##' diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py index db23eb8..e7f9ac8 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium.py @@ -232,6 +232,13 @@ class ChromiumPort(base.Port): raise ValueError('Unsupported test_platform_name: %s' % test_platform_name) + def test_repository_paths(self): + # Note: for JSON file's backward-compatibility we use 'chrome' rather + # than 'chromium' here. + repos = super(ChromiumPort, self).test_repository_paths() + repos.append(('chrome', self.path_from_chromium_base())) + return repos + # # PROTECTED METHODS # diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py index 0818d51..4df43e0 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_linux.py @@ -81,15 +81,15 @@ class ChromiumLinuxPort(chromium.ChromiumPort): # def _build_path(self, *comps): - if self._options.use_drt: - base = os.path.join(self.path_from_webkit_base(), 'WebKit', - 'chromium') - else: - base = self.path_from_chromium_base() + base = self.path_from_chromium_base() if os.path.exists(os.path.join(base, 'sconsbuild')): return os.path.join(base, 'sconsbuild', *comps) - else: + if os.path.exists(os.path.join(base, 'out', *comps)) or not self._options.use_drt: return os.path.join(base, 'out', *comps) + base = self.path_from_webkit_base() + if os.path.exists(os.path.join(base, 'sconsbuild')): + return os.path.join(base, 'sconsbuild', *comps) + return os.path.join(base, 'out', *comps) def _check_apache_install(self): result = chromium.check_file_exists(self._path_to_apache(), diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py index aa3ac8d..abd84ae 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_mac.py @@ -102,10 +102,11 @@ class ChromiumMacPort(chromium.ChromiumPort): # def _build_path(self, *comps): - if self._options.use_drt: - return self.path_from_webkit_base('WebKit', 'chromium', - 'xcodebuild', *comps) - return self.path_from_chromium_base('xcodebuild', *comps) + path = self.path_from_chromium_base('xcodebuild', *comps) + if os.path.exists(path) or not self._options.use_drt: + return path + return self.path_from_webkit_base('WebKit', 'chromium', 'xcodebuild', + *comps) def _check_wdiff_install(self): try: diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py index ec1c33c..8072bc0 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/chromium_win.py @@ -117,13 +117,14 @@ class ChromiumWinPort(chromium.ChromiumPort): # def _build_path(self, *comps): - if self._options.use_drt: - return os.path.join(self.path_from_webkit_base(), 'WebKit', - 'chromium', *comps) p = self.path_from_chromium_base('webkit', *comps) if os.path.exists(p): return p - return self.path_from_chromium_base('chrome', *comps) + p = self.path_from_chromium_base('chrome', *comps) + if os.path.exists(p) or not self._options.use_drt: + return p + return os.path.join(self.path_from_webkit_base(), 'WebKit', 'chromium', + *comps) def _lighttpd_path(self, *comps): return self.path_from_chromium_base('third_party', 'lighttpd', 'win', diff --git a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py b/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py index 22ae780..81bf39e 100644 --- a/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py +++ b/WebKitTools/Scripts/webkitpy/layout_tests/port/websocket_server.py @@ -207,12 +207,13 @@ class PyWebSocket(http_server.Lighttpd): url = 'http' url = url + '://127.0.0.1:%d/' % self._port if not url_is_alive(url): - fp = codecs.open(output_log, "utf-8") - try: + if self._process.returncode == None: + # FIXME: We should use a non-static Executive for easier + # testing. + Executive().kill_process(self._process.pid) + with codecs.open(output_log, "r", "utf-8") as fp: for line in fp: _log.error(line) - finally: - fp.close() raise PyWebSocketNotStarted( 'Failed to start %s server on port %s.' % (self._server_name, self._port)) diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py index 3642286..e1fa673 100644 --- a/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py +++ b/WebKitTools/Scripts/webkitpy/thirdparty/__init__.py @@ -71,7 +71,8 @@ installer.install(url="http://pypi.python.org/packages/source/m/mechanize/mechan url_subpath="mechanize") installer.install(url="http://pypi.python.org/packages/source/p/pep8/pep8-0.5.0.tar.gz#md5=512a818af9979290cd619cce8e9c2e2b", url_subpath="pep8-0.5.0/pep8.py") - +installer.install(url="http://www.adambarth.com/webkit/eliza", + target_name="eliza.py") rietveld_dir = os.path.join(autoinstalled_dir, "rietveld") installer = AutoInstaller(target_dir=rietveld_dir) @@ -84,9 +85,9 @@ installer.install(url="http://webkit-rietveld.googlecode.com/svn/trunk/static/up # organization purposes. irc_dir = os.path.join(autoinstalled_dir, "irc") installer = AutoInstaller(target_dir=irc_dir) -installer.install(url="http://iweb.dl.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", +installer.install(url="http://hivelocity.dl.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", url_subpath="irclib.py") -installer.install(url="http://iweb.dl.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", +installer.install(url="http://hivelocity.dl.sourceforge.net/project/python-irclib/python-irclib/0.4.8/python-irclib-0.4.8.zip", url_subpath="ircbot.py") pywebsocket_dir = os.path.join(autoinstalled_dir, "pywebsocket") diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.mechanize.url b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.mechanize.url deleted file mode 100644 index 4186aee..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.mechanize.url +++ /dev/null @@ -1 +0,0 @@ -http://pypi.python.org/packages/source/m/mechanize/mechanize-0.1.11.zip \ No newline at end of file diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.pep8.py.url b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.pep8.py.url deleted file mode 100644 index 0fb1ef6..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/.pep8.py.url +++ /dev/null @@ -1 +0,0 @@ -http://pypi.python.org/packages/source/p/pep8/pep8-0.5.0.tar.gz#md5=512a818af9979290cd619cce8e9c2e2b \ No newline at end of file diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/README b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/README deleted file mode 100644 index 1d68cf3..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/README +++ /dev/null @@ -1,2 +0,0 @@ -This directory is auto-generated by WebKit and is safe to delete. -It contains needed third-party Python packages automatically downloaded from the web. \ No newline at end of file diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/__init__.py b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/__init__.py deleted file mode 100644 index c1e4c6d..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file is required for Python to search this directory for modules. diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/.ClientForm.py.url b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/.ClientForm.py.url deleted file mode 100644 index c723abf..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/.ClientForm.py.url +++ /dev/null @@ -1 +0,0 @@ -http://pypi.python.org/packages/source/C/ClientForm/ClientForm-0.2.10.zip \ No newline at end of file diff --git a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/ClientForm.py b/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/ClientForm.py deleted file mode 100644 index a622de7..0000000 --- a/WebKitTools/Scripts/webkitpy/thirdparty/autoinstalled/clientform/ClientForm.py +++ /dev/null @@ -1,3401 +0,0 @@ -"""HTML form handling for web clients. - -ClientForm is a Python module for handling HTML forms on the client -side, useful for parsing HTML forms, filling them in and returning the -completed forms to the server. It has developed from a port of Gisle -Aas' Perl module HTML::Form, from the libwww-perl library, but the -interface is not the same. - -The most useful docstring is the one for HTMLForm. - -RFC 1866: HTML 2.0 -RFC 1867: Form-based File Upload in HTML -RFC 2388: Returning Values from Forms: multipart/form-data -HTML 3.2 Specification, W3C Recommendation 14 January 1997 (for ISINDEX) -HTML 4.01 Specification, W3C Recommendation 24 December 1999 - - -Copyright 2002-2007 John J. Lee -Copyright 2005 Gary Poster -Copyright 2005 Zope Corporation -Copyright 1998-2000 Gisle Aas. - -This code is free software; you can redistribute it and/or modify it -under the terms of the BSD or ZPL 2.1 licenses (see the file -COPYING.txt included with the distribution). - -""" - -# XXX -# Remove parser testing hack -# safeUrl()-ize action -# Switch to unicode throughout (would be 0.3.x) -# See Wichert Akkerman's 2004-01-22 message to c.l.py. -# Add charset parameter to Content-type headers? How to find value?? -# Add some more functional tests -# Especially single and multiple file upload on the internet. -# Does file upload work when name is missing? Sourceforge tracker form -# doesn't like it. Check standards, and test with Apache. Test -# binary upload with Apache. -# mailto submission & enctype text/plain -# I'm not going to fix this unless somebody tells me what real servers -# that want this encoding actually expect: If enctype is -# application/x-www-form-urlencoded and there's a FILE control present. -# Strictly, it should be 'name=data' (see HTML 4.01 spec., section -# 17.13.2), but I send "name=" ATM. What about multiple file upload?? - -# Would be nice, but I'm not going to do it myself: -# ------------------------------------------------- -# Maybe a 0.4.x? -# Replace by_label etc. with moniker / selector concept. Allows, eg., -# a choice between selection by value / id / label / element -# contents. Or choice between matching labels exactly or by -# substring. Etc. -# Remove deprecated methods. -# ...what else? -# Work on DOMForm. -# XForms? Don't know if there's a need here. - -__all__ = ['AmbiguityError', 'CheckboxControl', 'Control', - 'ControlNotFoundError', 'FileControl', 'FormParser', 'HTMLForm', - 'HiddenControl', 'IgnoreControl', 'ImageControl', 'IsindexControl', - 'Item', 'ItemCountError', 'ItemNotFoundError', 'Label', - 'ListControl', 'LocateError', 'Missing', 'ParseError', 'ParseFile', - 'ParseFileEx', 'ParseResponse', 'ParseResponseEx','PasswordControl', - 'RadioControl', 'ScalarControl', 'SelectControl', - 'SubmitButtonControl', 'SubmitControl', 'TextControl', - 'TextareaControl', 'XHTMLCompatibleFormParser'] - -try: True -except NameError: - True = 1 - False = 0 - -try: bool -except NameError: - def bool(expr): - if expr: return True - else: return False - -try: - import logging - import inspect -except ImportError: - def debug(msg, *args, **kwds): - pass -else: - _logger = logging.getLogger("ClientForm") - OPTIMIZATION_HACK = True - - def debug(msg, *args, **kwds): - if OPTIMIZATION_HACK: - return - - caller_name = inspect.stack()[1][3] - extended_msg = '%%s %s' % msg - extended_args = (caller_name,)+args - debug = _logger.debug(extended_msg, *extended_args, **kwds) - - def _show_debug_messages(): - global OPTIMIZATION_HACK - OPTIMIZATION_HACK = False - _logger.setLevel(logging.DEBUG) - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(logging.DEBUG) - _logger.addHandler(handler) - -import sys, urllib, urllib2, types, mimetools, copy, urlparse, \ - htmlentitydefs, re, random -from cStringIO import StringIO - -import sgmllib -# monkeypatch to fix http://www.python.org/sf/803422 :-( -sgmllib.charref = re.compile("&#(x?[0-9a-fA-F]+)[^0-9a-fA-F]") - -# HTMLParser.HTMLParser is recent, so live without it if it's not available -# (also, sgmllib.SGMLParser is much more tolerant of bad HTML) -try: - import HTMLParser -except ImportError: - HAVE_MODULE_HTMLPARSER = False -else: - HAVE_MODULE_HTMLPARSER = True - -try: - import warnings -except ImportError: - def deprecation(message, stack_offset=0): - pass -else: - def deprecation(message, stack_offset=0): - warnings.warn(message, DeprecationWarning, stacklevel=3+stack_offset) - -VERSION = "0.2.10" - -CHUNK = 1024 # size of chunks fed to parser, in bytes - -DEFAULT_ENCODING = "latin-1" - -class Missing: pass - -_compress_re = re.compile(r"\s+") -def compress_text(text): return _compress_re.sub(" ", text.strip()) - -def normalize_line_endings(text): - return re.sub(r"(?:(? - w = MimeWriter(f) - ...call w.addheader(key, value) 0 or more times... - - followed by either: - - f = w.startbody(content_type) - ...call f.write(data) for body data... - - or: - - w.startmultipartbody(subtype) - for each part: - subwriter = w.nextpart() - ...use the subwriter's methods to create the subpart... - w.lastpart() - - The subwriter is another MimeWriter instance, and should be - treated in the same way as the toplevel MimeWriter. This way, - writing recursive body parts is easy. - - Warning: don't forget to call lastpart()! - - XXX There should be more state so calls made in the wrong order - are detected. - - Some special cases: - - - startbody() just returns the file passed to the constructor; - but don't use this knowledge, as it may be changed. - - - startmultipartbody() actually returns a file as well; - this can be used to write the initial 'if you can read this your - mailer is not MIME-aware' message. - - - If you call flushheaders(), the headers accumulated so far are - written out (and forgotten); this is useful if you don't need a - body part at all, e.g. for a subpart of type message/rfc822 - that's (mis)used to store some header-like information. - - - Passing a keyword argument 'prefix=' to addheader(), - start*body() affects where the header is inserted; 0 means - append at the end, 1 means insert at the start; default is - append for addheader(), but insert for start*body(), which use - it to determine where the Content-type header goes. - - """ - - def __init__(self, fp, http_hdrs=None): - self._http_hdrs = http_hdrs - self._fp = fp - self._headers = [] - self._boundary = [] - self._first_part = True - - def addheader(self, key, value, prefix=0, - add_to_http_hdrs=0): - """ - prefix is ignored if add_to_http_hdrs is true. - """ - lines = value.split("\r\n") - while lines and not lines[-1]: del lines[-1] - while lines and not lines[0]: del lines[0] - if add_to_http_hdrs: - value = "".join(lines) - # 2.2 urllib2 doesn't normalize header case - self._http_hdrs.append((key.capitalize(), value)) - else: - for i in range(1, len(lines)): - lines[i] = " " + lines[i].strip() - value = "\r\n".join(lines) + "\r\n" - line = key.title() + ": " + value - if prefix: - self._headers.insert(0, line) - else: - self._headers.append(line) - - def flushheaders(self): - self._fp.writelines(self._headers) - self._headers = [] - - def startbody(self, ctype=None, plist=[], prefix=1, - add_to_http_hdrs=0, content_type=1): - """ - prefix is ignored if add_to_http_hdrs is true. - """ - if content_type and ctype: - for name, value in plist: - ctype = ctype + ';\r\n %s=%s' % (name, value) - self.addheader("Content-Type", ctype, prefix=prefix, - add_to_http_hdrs=add_to_http_hdrs) - self.flushheaders() - if not add_to_http_hdrs: self._fp.write("\r\n") - self._first_part = True - return self._fp - - def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1, - add_to_http_hdrs=0, content_type=1): - boundary = boundary or choose_boundary() - self._boundary.append(boundary) - return self.startbody("multipart/" + subtype, - [("boundary", boundary)] + plist, - prefix=prefix, - add_to_http_hdrs=add_to_http_hdrs, - content_type=content_type) - - def nextpart(self): - boundary = self._boundary[-1] - if self._first_part: - self._first_part = False - else: - self._fp.write("\r\n") - self._fp.write("--" + boundary + "\r\n") - return self.__class__(self._fp) - - def lastpart(self): - if self._first_part: - self.nextpart() - boundary = self._boundary.pop() - self._fp.write("\r\n--" + boundary + "--\r\n") - - -class LocateError(ValueError): pass -class AmbiguityError(LocateError): pass -class ControlNotFoundError(LocateError): pass -class ItemNotFoundError(LocateError): pass - -class ItemCountError(ValueError): pass - -# for backwards compatibility, ParseError derives from exceptions that were -# raised by versions of ClientForm <= 0.2.5 -if HAVE_MODULE_HTMLPARSER: - SGMLLIB_PARSEERROR = sgmllib.SGMLParseError - class ParseError(sgmllib.SGMLParseError, - HTMLParser.HTMLParseError, - ): - pass -else: - if hasattr(sgmllib, "SGMLParseError"): - SGMLLIB_PARSEERROR = sgmllib.SGMLParseError - class ParseError(sgmllib.SGMLParseError): - pass - else: - SGMLLIB_PARSEERROR = RuntimeError - class ParseError(RuntimeError): - pass - - -class _AbstractFormParser: - """forms attribute contains HTMLForm instances on completion.""" - # thanks to Moshe Zadka for an example of sgmllib/htmllib usage - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - if entitydefs is None: - entitydefs = get_entitydefs() - self._entitydefs = entitydefs - self._encoding = encoding - - self.base = None - self.forms = [] - self.labels = [] - self._current_label = None - self._current_form = None - self._select = None - self._optgroup = None - self._option = None - self._textarea = None - - # forms[0] will contain all controls that are outside of any form - # self._global_form is an alias for self.forms[0] - self._global_form = None - self.start_form([]) - self.end_form() - self._current_form = self._global_form = self.forms[0] - - def do_base(self, attrs): - debug("%s", attrs) - for key, value in attrs: - if key == "href": - self.base = self.unescape_attr_if_required(value) - - def end_body(self): - debug("") - if self._current_label is not None: - self.end_label() - if self._current_form is not self._global_form: - self.end_form() - - def start_form(self, attrs): - debug("%s", attrs) - if self._current_form is not self._global_form: - raise ParseError("nested FORMs") - name = None - action = None - enctype = "application/x-www-form-urlencoded" - method = "GET" - d = {} - for key, value in attrs: - if key == "name": - name = self.unescape_attr_if_required(value) - elif key == "action": - action = self.unescape_attr_if_required(value) - elif key == "method": - method = self.unescape_attr_if_required(value.upper()) - elif key == "enctype": - enctype = self.unescape_attr_if_required(value.lower()) - d[key] = self.unescape_attr_if_required(value) - controls = [] - self._current_form = (name, action, method, enctype), d, controls - - def end_form(self): - debug("") - if self._current_label is not None: - self.end_label() - if self._current_form is self._global_form: - raise ParseError("end of FORM before start") - self.forms.append(self._current_form) - self._current_form = self._global_form - - def start_select(self, attrs): - debug("%s", attrs) - if self._select is not None: - raise ParseError("nested SELECTs") - if self._textarea is not None: - raise ParseError("SELECT inside TEXTAREA") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._select = d - self._add_label(d) - - self._append_select_control({"__select": d}) - - def end_select(self): - debug("") - if self._select is None: - raise ParseError("end of SELECT before start") - - if self._option is not None: - self._end_option() - - self._select = None - - def start_optgroup(self, attrs): - debug("%s", attrs) - if self._select is None: - raise ParseError("OPTGROUP outside of SELECT") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._optgroup = d - - def end_optgroup(self): - debug("") - if self._optgroup is None: - raise ParseError("end of OPTGROUP before start") - self._optgroup = None - - def _start_option(self, attrs): - debug("%s", attrs) - if self._select is None: - raise ParseError("OPTION outside of SELECT") - if self._option is not None: - self._end_option() - - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - - self._option = {} - self._option.update(d) - if (self._optgroup and self._optgroup.has_key("disabled") and - not self._option.has_key("disabled")): - self._option["disabled"] = None - - def _end_option(self): - debug("") - if self._option is None: - raise ParseError("end of OPTION before start") - - contents = self._option.get("contents", "").strip() - self._option["contents"] = contents - if not self._option.has_key("value"): - self._option["value"] = contents - if not self._option.has_key("label"): - self._option["label"] = contents - # stuff dict of SELECT HTML attrs into a special private key - # (gets deleted again later) - self._option["__select"] = self._select - self._append_select_control(self._option) - self._option = None - - def _append_select_control(self, attrs): - debug("%s", attrs) - controls = self._current_form[2] - name = self._select.get("name") - controls.append(("select", name, attrs)) - - def start_textarea(self, attrs): - debug("%s", attrs) - if self._textarea is not None: - raise ParseError("nested TEXTAREAs") - if self._select is not None: - raise ParseError("TEXTAREA inside SELECT") - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - self._add_label(d) - - self._textarea = d - - def end_textarea(self): - debug("") - if self._textarea is None: - raise ParseError("end of TEXTAREA before start") - controls = self._current_form[2] - name = self._textarea.get("name") - controls.append(("textarea", name, self._textarea)) - self._textarea = None - - def start_label(self, attrs): - debug("%s", attrs) - if self._current_label: - self.end_label() - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - taken = bool(d.get("for")) # empty id is invalid - d["__text"] = "" - d["__taken"] = taken - if taken: - self.labels.append(d) - self._current_label = d - - def end_label(self): - debug("") - label = self._current_label - if label is None: - # something is ugly in the HTML, but we're ignoring it - return - self._current_label = None - # if it is staying around, it is True in all cases - del label["__taken"] - - def _add_label(self, d): - #debug("%s", d) - if self._current_label is not None: - if not self._current_label["__taken"]: - self._current_label["__taken"] = True - d["__label"] = self._current_label - - def handle_data(self, data): - debug("%s", data) - - if self._option is not None: - # self._option is a dictionary of the OPTION element's HTML - # attributes, but it has two special keys, one of which is the - # special "contents" key contains text between OPTION tags (the - # other is the "__select" key: see the end_option method) - map = self._option - key = "contents" - elif self._textarea is not None: - map = self._textarea - key = "value" - data = normalize_line_endings(data) - # not if within option or textarea - elif self._current_label is not None: - map = self._current_label - key = "__text" - else: - return - - if data and not map.has_key(key): - # according to - # http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1 line break - # immediately after start tags or immediately before end tags must - # be ignored, but real browsers only ignore a line break after a - # start tag, so we'll do that. - if data[0:2] == "\r\n": - data = data[2:] - elif data[0:1] in ["\n", "\r"]: - data = data[1:] - map[key] = data - else: - map[key] = map[key] + data - - def do_button(self, attrs): - debug("%s", attrs) - d = {} - d["type"] = "submit" # default - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - type = d["type"] - name = d.get("name") - # we don't want to lose information, so use a type string that - # doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON} - # e.g. type for BUTTON/RESET is "resetbutton" - # (type for INPUT/RESET is "reset") - type = type+"button" - self._add_label(d) - controls.append((type, name, d)) - - def do_input(self, attrs): - debug("%s", attrs) - d = {} - d["type"] = "text" # default - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - type = d["type"] - name = d.get("name") - self._add_label(d) - controls.append((type, name, d)) - - def do_isindex(self, attrs): - debug("%s", attrs) - d = {} - for key, val in attrs: - d[key] = self.unescape_attr_if_required(val) - controls = self._current_form[2] - - self._add_label(d) - # isindex doesn't have type or name HTML attributes - controls.append(("isindex", None, d)) - - def handle_entityref(self, name): - #debug("%s", name) - self.handle_data(unescape( - '&%s;' % name, self._entitydefs, self._encoding)) - - def handle_charref(self, name): - #debug("%s", name) - self.handle_data(unescape_charref(name, self._encoding)) - - def unescape_attr(self, name): - #debug("%s", name) - return unescape(name, self._entitydefs, self._encoding) - - def unescape_attrs(self, attrs): - #debug("%s", attrs) - escaped_attrs = {} - for key, val in attrs.items(): - try: - val.items - except AttributeError: - escaped_attrs[key] = self.unescape_attr(val) - else: - # e.g. "__select" -- yuck! - escaped_attrs[key] = self.unescape_attrs(val) - return escaped_attrs - - def unknown_entityref(self, ref): self.handle_data("&%s;" % ref) - def unknown_charref(self, ref): self.handle_data("&#%s;" % ref) - - -if not HAVE_MODULE_HTMLPARSER: - class XHTMLCompatibleFormParser: - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - raise ValueError("HTMLParser could not be imported") -else: - class XHTMLCompatibleFormParser(_AbstractFormParser, HTMLParser.HTMLParser): - """Good for XHTML, bad for tolerance of incorrect HTML.""" - # thanks to Michael Howitz for this! - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - HTMLParser.HTMLParser.__init__(self) - _AbstractFormParser.__init__(self, entitydefs, encoding) - - def feed(self, data): - try: - HTMLParser.HTMLParser.feed(self, data) - except HTMLParser.HTMLParseError, exc: - raise ParseError(exc) - - def start_option(self, attrs): - _AbstractFormParser._start_option(self, attrs) - - def end_option(self): - _AbstractFormParser._end_option(self) - - def handle_starttag(self, tag, attrs): - try: - method = getattr(self, "start_" + tag) - except AttributeError: - try: - method = getattr(self, "do_" + tag) - except AttributeError: - pass # unknown tag - else: - method(attrs) - else: - method(attrs) - - def handle_endtag(self, tag): - try: - method = getattr(self, "end_" + tag) - except AttributeError: - pass # unknown tag - else: - method() - - def unescape(self, name): - # Use the entitydefs passed into constructor, not - # HTMLParser.HTMLParser's entitydefs. - return self.unescape_attr(name) - - def unescape_attr_if_required(self, name): - return name # HTMLParser.HTMLParser already did it - def unescape_attrs_if_required(self, attrs): - return attrs # ditto - - def close(self): - HTMLParser.HTMLParser.close(self) - self.end_body() - - -class _AbstractSgmllibParser(_AbstractFormParser): - - def do_option(self, attrs): - _AbstractFormParser._start_option(self, attrs) - - if sys.version_info[:2] >= (2,5): - # we override this attr to decode hex charrefs - entity_or_charref = re.compile( - '&(?:([a-zA-Z][-.a-zA-Z0-9]*)|#(x?[0-9a-fA-F]+))(;?)') - def convert_entityref(self, name): - return unescape("&%s;" % name, self._entitydefs, self._encoding) - def convert_charref(self, name): - return unescape_charref("%s" % name, self._encoding) - def unescape_attr_if_required(self, name): - return name # sgmllib already did it - def unescape_attrs_if_required(self, attrs): - return attrs # ditto - else: - def unescape_attr_if_required(self, name): - return self.unescape_attr(name) - def unescape_attrs_if_required(self, attrs): - return self.unescape_attrs(attrs) - - -class FormParser(_AbstractSgmllibParser, sgmllib.SGMLParser): - """Good for tolerance of incorrect HTML, bad for XHTML.""" - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - sgmllib.SGMLParser.__init__(self) - _AbstractFormParser.__init__(self, entitydefs, encoding) - - def feed(self, data): - try: - sgmllib.SGMLParser.feed(self, data) - except SGMLLIB_PARSEERROR, exc: - raise ParseError(exc) - - def close(self): - sgmllib.SGMLParser.close(self) - self.end_body() - - -# sigh, must support mechanize by allowing dynamic creation of classes based on -# its bundled copy of BeautifulSoup (which was necessary because of dependency -# problems) - -def _create_bs_classes(bs, - icbinbs, - ): - class _AbstractBSFormParser(_AbstractSgmllibParser): - bs_base_class = None - def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING): - _AbstractFormParser.__init__(self, entitydefs, encoding) - self.bs_base_class.__init__(self) - def handle_data(self, data): - _AbstractFormParser.handle_data(self, data) - self.bs_base_class.handle_data(self, data) - def feed(self, data): - try: - self.bs_base_class.feed(self, data) - except SGMLLIB_PARSEERROR, exc: - raise ParseError(exc) - def close(self): - self.bs_base_class.close(self) - self.end_body() - - class RobustFormParser(_AbstractBSFormParser, bs): - """Tries to be highly tolerant of incorrect HTML.""" - pass - RobustFormParser.bs_base_class = bs - class NestingRobustFormParser(_AbstractBSFormParser, icbinbs): - """Tries to be highly tolerant of incorrect HTML. - - Different from RobustFormParser in that it more often guesses nesting - above missing end tags (see BeautifulSoup docs). - - """ - pass - NestingRobustFormParser.bs_base_class = icbinbs - - return RobustFormParser, NestingRobustFormParser - -try: - if sys.version_info[:2] < (2, 2): - raise ImportError # BeautifulSoup uses generators - import BeautifulSoup -except ImportError: - pass -else: - RobustFormParser, NestingRobustFormParser = _create_bs_classes( - BeautifulSoup.BeautifulSoup, BeautifulSoup.ICantBelieveItsBeautifulSoup - ) - __all__ += ['RobustFormParser', 'NestingRobustFormParser'] - - -#FormParser = XHTMLCompatibleFormParser # testing hack -#FormParser = RobustFormParser # testing hack - - -def ParseResponseEx(response, - select_default=False, - form_parser_class=FormParser, - request_class=urllib2.Request, - entitydefs=None, - encoding=DEFAULT_ENCODING, - - # private - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - """Identical to ParseResponse, except that: - - 1. The returned list contains an extra item. The first form in the list - contains all controls not contained in any FORM element. - - 2. The arguments ignore_errors and backwards_compat have been removed. - - 3. Backwards-compatibility mode (backwards_compat=True) is not available. - """ - return _ParseFileEx(response, response.geturl(), - select_default, - False, - form_parser_class, - request_class, - entitydefs, - False, - encoding, - _urljoin=_urljoin, - _urlparse=_urlparse, - _urlunparse=_urlunparse, - ) - -def ParseFileEx(file, base_uri, - select_default=False, - form_parser_class=FormParser, - request_class=urllib2.Request, - entitydefs=None, - encoding=DEFAULT_ENCODING, - - # private - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - """Identical to ParseFile, except that: - - 1. The returned list contains an extra item. The first form in the list - contains all controls not contained in any FORM element. - - 2. The arguments ignore_errors and backwards_compat have been removed. - - 3. Backwards-compatibility mode (backwards_compat=True) is not available. - """ - return _ParseFileEx(file, base_uri, - select_default, - False, - form_parser_class, - request_class, - entitydefs, - False, - encoding, - _urljoin=_urljoin, - _urlparse=_urlparse, - _urlunparse=_urlunparse, - ) - -def ParseResponse(response, *args, **kwds): - """Parse HTTP response and return a list of HTMLForm instances. - - The return value of urllib2.urlopen can be conveniently passed to this - function as the response parameter. - - ClientForm.ParseError is raised on parse errors. - - response: file-like object (supporting read() method) with a method - geturl(), returning the URI of the HTTP response - select_default: for multiple-selection SELECT controls and RADIO controls, - pick the first item as the default if none are selected in the HTML - form_parser_class: class to instantiate and use to pass - request_class: class to return from .click() method (default is - urllib2.Request) - entitydefs: mapping like {"&": "&", ...} containing HTML entity - definitions (a sensible default is used) - encoding: character encoding used for encoding numeric character references - when matching link text. ClientForm does not attempt to find the encoding - in a META HTTP-EQUIV attribute in the document itself (mechanize, for - example, does do that and will pass the correct value to ClientForm using - this parameter). - - backwards_compat: boolean that determines whether the returned HTMLForm - objects are backwards-compatible with old code. If backwards_compat is - true: - - - ClientForm 0.1 code will continue to work as before. - - - Label searches that do not specify a nr (number or count) will always - get the first match, even if other controls match. If - backwards_compat is False, label searches that have ambiguous results - will raise an AmbiguityError. - - - Item label matching is done by strict string comparison rather than - substring matching. - - - De-selecting individual list items is allowed even if the Item is - disabled. - - The backwards_compat argument will be deprecated in a future release. - - Pass a true value for select_default if you want the behaviour specified by - RFC 1866 (the HTML 2.0 standard), which is to select the first item in a - RADIO or multiple-selection SELECT control if none were selected in the - HTML. Most browsers (including Microsoft Internet Explorer (IE) and - Netscape Navigator) instead leave all items unselected in these cases. The - W3C HTML 4.0 standard leaves this behaviour undefined in the case of - multiple-selection SELECT controls, but insists that at least one RADIO - button should be checked at all times, in contradiction to browser - behaviour. - - There is a choice of parsers. ClientForm.XHTMLCompatibleFormParser (uses - HTMLParser.HTMLParser) works best for XHTML, ClientForm.FormParser (uses - sgmllib.SGMLParser) (the default) works better for ordinary grubby HTML. - Note that HTMLParser is only available in Python 2.2 and later. You can - pass your own class in here as a hack to work around bad HTML, but at your - own risk: there is no well-defined interface. - - """ - return _ParseFileEx(response, response.geturl(), *args, **kwds)[1:] - -def ParseFile(file, base_uri, *args, **kwds): - """Parse HTML and return a list of HTMLForm instances. - - ClientForm.ParseError is raised on parse errors. - - file: file-like object (supporting read() method) containing HTML with zero - or more forms to be parsed - base_uri: the URI of the document (note that the base URI used to submit - the form will be that given in the BASE element if present, not that of - the document) - - For the other arguments and further details, see ParseResponse.__doc__. - - """ - return _ParseFileEx(file, base_uri, *args, **kwds)[1:] - -def _ParseFileEx(file, base_uri, - select_default=False, - ignore_errors=False, - form_parser_class=FormParser, - request_class=urllib2.Request, - entitydefs=None, - backwards_compat=True, - encoding=DEFAULT_ENCODING, - _urljoin=urlparse.urljoin, - _urlparse=urlparse.urlparse, - _urlunparse=urlparse.urlunparse, - ): - if backwards_compat: - deprecation("operating in backwards-compatibility mode", 1) - fp = form_parser_class(entitydefs, encoding) - while 1: - data = file.read(CHUNK) - try: - fp.feed(data) - except ParseError, e: - e.base_uri = base_uri - raise - if len(data) != CHUNK: break - fp.close() - if fp.base is not None: - # HTML BASE element takes precedence over document URI - base_uri = fp.base - labels = [] # Label(label) for label in fp.labels] - id_to_labels = {} - for l in fp.labels: - label = Label(l) - labels.append(label) - for_id = l["for"] - coll = id_to_labels.get(for_id) - if coll is None: - id_to_labels[for_id] = [label] - else: - coll.append(label) - forms = [] - for (name, action, method, enctype), attrs, controls in fp.forms: - if action is None: - action = base_uri - else: - action = _urljoin(base_uri, action) - # would be nice to make HTMLForm class (form builder) pluggable - form = HTMLForm( - action, method, enctype, name, attrs, request_class, - forms, labels, id_to_labels, backwards_compat) - form._urlparse = _urlparse - form._urlunparse = _urlunparse - for ii in range(len(controls)): - type, name, attrs = controls[ii] - # index=ii*10 allows ImageControl to return multiple ordered pairs - form.new_control( - type, name, attrs, select_default=select_default, index=ii*10) - forms.append(form) - for form in forms: - form.fixup() - return forms - - -class Label: - def __init__(self, attrs): - self.id = attrs.get("for") - self._text = attrs.get("__text").strip() - self._ctext = compress_text(self._text) - self.attrs = attrs - self._backwards_compat = False # maintained by HTMLForm - - def __getattr__(self, name): - if name == "text": - if self._backwards_compat: - return self._text - else: - return self._ctext - return getattr(Label, name) - - def __setattr__(self, name, value): - if name == "text": - # don't see any need for this, so make it read-only - raise AttributeError("text attribute is read-only") - self.__dict__[name] = value - - def __str__(self): - return "" % (self.id, self.text) - - -def _get_label(attrs): - text = attrs.get("__label") - if text is not None: - return Label(text) - else: - return None - -class Control: - """An HTML form control. - - An HTMLForm contains a sequence of Controls. The Controls in an HTMLForm - are accessed using the HTMLForm.find_control method or the - HTMLForm.controls attribute. - - Control instances are usually constructed using the ParseFile / - ParseResponse functions. If you use those functions, you can ignore the - rest of this paragraph. A Control is only properly initialised after the - fixup method has been called. In fact, this is only strictly necessary for - ListControl instances. This is necessary because ListControls are built up - from ListControls each containing only a single item, and their initial - value(s) can only be known after the sequence is complete. - - The types and values that are acceptable for assignment to the value - attribute are defined by subclasses. - - If the disabled attribute is true, this represents the state typically - represented by browsers by 'greying out' a control. If the disabled - attribute is true, the Control will raise AttributeError if an attempt is - made to change its value. In addition, the control will not be considered - 'successful' as defined by the W3C HTML 4 standard -- ie. it will - contribute no data to the return value of the HTMLForm.click* methods. To - enable a control, set the disabled attribute to a false value. - - If the readonly attribute is true, the Control will raise AttributeError if - an attempt is made to change its value. To make a control writable, set - the readonly attribute to a false value. - - All controls have the disabled and readonly attributes, not only those that - may have the HTML attributes of the same names. - - On assignment to the value attribute, the following exceptions are raised: - TypeError, AttributeError (if the value attribute should not be assigned - to, because the control is disabled, for example) and ValueError. - - If the name or value attributes are None, or the value is an empty list, or - if the control is disabled, the control is not successful. - - Public attributes: - - type: string describing type of control (see the keys of the - HTMLForm.type2class dictionary for the allowable values) (readonly) - name: name of control (readonly) - value: current value of control (subclasses may allow a single value, a - sequence of values, or either) - disabled: disabled state - readonly: readonly state - id: value of id HTML attribute - - """ - def __init__(self, type, name, attrs, index=None): - """ - type: string describing type of control (see the keys of the - HTMLForm.type2class dictionary for the allowable values) - name: control name - attrs: HTML attributes of control's HTML element - - """ - raise NotImplementedError() - - def add_to_form(self, form): - self._form = form - form.controls.append(self) - - def fixup(self): - pass - - def is_of_kind(self, kind): - raise NotImplementedError() - - def clear(self): - raise NotImplementedError() - - def __getattr__(self, name): raise NotImplementedError() - def __setattr__(self, name, value): raise NotImplementedError() - - def pairs(self): - """Return list of (key, value) pairs suitable for passing to urlencode. - """ - return [(k, v) for (i, k, v) in self._totally_ordered_pairs()] - - def _totally_ordered_pairs(self): - """Return list of (key, value, index) tuples. - - Like pairs, but allows preserving correct ordering even where several - controls are involved. - - """ - raise NotImplementedError() - - def _write_mime_data(self, mw, name, value): - """Write data for a subitem of this control to a MimeWriter.""" - # called by HTMLForm - mw2 = mw.nextpart() - mw2.addheader("Content-Disposition", - 'form-data; name="%s"' % name, 1) - f = mw2.startbody(prefix=0) - f.write(value) - - def __str__(self): - raise NotImplementedError() - - def get_labels(self): - """Return all labels (Label instances) for this control. - - If the control was surrounded by a