mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
b141da4694
This commit introduces the `mach rage` command. It opens a web browser and loads a new tab with a short Google Form. Google Forms appears to not have optional login where they capture login info if available. So, we attempt to prepopulate the contact info from version control or from the current login name. This field is directly above the submit button, so it should be obvious enough that people can clear it if they want to leave an anonymous response. The alternative is we require a login to the Mozilla Google Apps account. And that feels wrong. r+ on this bug also constitutes r+ on the Google Form content. MozReview-Commit-ID: HVAVVVva29T --HG-- extra : rebase_source : d00aa424ffd42d8cd5d2d8a85aef8b3c24b59520
365 lines
14 KiB
Python
365 lines
14 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
import sys
|
|
import os
|
|
import stat
|
|
import platform
|
|
import errno
|
|
import subprocess
|
|
|
|
from mach.decorators import (
|
|
CommandArgument,
|
|
CommandProvider,
|
|
Command,
|
|
)
|
|
|
|
from mozbuild.base import MachCommandBase, MozbuildObject
|
|
|
|
|
|
@CommandProvider
|
|
class SearchProvider(object):
|
|
@Command('dxr', category='misc',
|
|
description='Search for something in DXR.')
|
|
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
|
|
def dxr(self, term):
|
|
import webbrowser
|
|
term = ' '.join(term)
|
|
uri = 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
|
|
webbrowser.open_new_tab(uri)
|
|
|
|
@Command('mdn', category='misc',
|
|
description='Search for something on MDN.')
|
|
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
|
|
def mdn(self, term):
|
|
import webbrowser
|
|
term = ' '.join(term)
|
|
uri = 'https://developer.mozilla.org/search?q=%s' % term
|
|
webbrowser.open_new_tab(uri)
|
|
|
|
@Command('google', category='misc',
|
|
description='Search for something on Google.')
|
|
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
|
|
def google(self, term):
|
|
import webbrowser
|
|
term = ' '.join(term)
|
|
uri = 'https://www.google.com/search?q=%s' % term
|
|
webbrowser.open_new_tab(uri)
|
|
|
|
@Command('search', category='misc',
|
|
description='Search for something on the Internets. '
|
|
'This will open 3 new browser tabs and search for the term on Google, '
|
|
'MDN, and DXR.')
|
|
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
|
|
def search(self, term):
|
|
self.google(term)
|
|
self.mdn(term)
|
|
self.dxr(term)
|
|
|
|
|
|
@CommandProvider
|
|
class UUIDProvider(object):
|
|
@Command('uuid', category='misc',
|
|
description='Generate a uuid.')
|
|
@CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
|
|
help='Output format for the generated uuid.')
|
|
def uuid(self, format=None):
|
|
import uuid
|
|
u = uuid.uuid4()
|
|
if format in [None, 'idl']:
|
|
print(u)
|
|
if format is None:
|
|
print('')
|
|
if format in [None, 'cpp', 'c++']:
|
|
u = u.hex
|
|
print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
|
|
pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
|
|
print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
|
|
|
|
|
|
@CommandProvider
|
|
class RageProvider(MachCommandBase):
|
|
@Command('rage', category='misc',
|
|
description='Express your frustration')
|
|
def rage(self):
|
|
"""Have a bad experience developing Firefox? Run this command to
|
|
express your frustration.
|
|
|
|
This command will open your default configured web browser to a short
|
|
form where you can submit feedback. Just close the tab when done.
|
|
"""
|
|
import getpass
|
|
import urllib
|
|
import webbrowser
|
|
|
|
# Try to resolve the current user.
|
|
user = None
|
|
with open(os.devnull, 'wb') as null:
|
|
if os.path.exists(os.path.join(self.topsrcdir, '.hg')):
|
|
try:
|
|
user = subprocess.check_output(['hg', 'config',
|
|
'ui.username'],
|
|
cwd=self.topsrcdir,
|
|
stderr=null)
|
|
|
|
i = user.find('<')
|
|
if i >= 0:
|
|
user = user[i + 1:-2]
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
elif os.path.exists(os.path.join(self.topsrcdir, '.git')):
|
|
try:
|
|
user = subprocess.check_output(['git', 'config', '--get',
|
|
'user.email'],
|
|
cwd=self.topsrcdir,
|
|
stderr=null)
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
|
|
if not user:
|
|
try:
|
|
user = getpass.getuser()
|
|
except Exception:
|
|
pass
|
|
|
|
url = 'https://docs.google.com/a/mozilla.com/forms/d/e/1FAIpQLSeDVC3IXJu5d33Hp_ZTCOw06xEUiYH1pBjAqJ1g_y63sO2vvA/viewform'
|
|
if user:
|
|
url += '?entry.1281044204=%s' % urllib.quote(user)
|
|
|
|
print('Please leave your feedback in the opened web form')
|
|
webbrowser.open_new_tab(url)
|
|
|
|
|
|
@CommandProvider
|
|
class PastebinProvider(object):
|
|
@Command('pastebin', category='misc',
|
|
description='Command line interface to pastebin.mozilla.org.')
|
|
@CommandArgument('--language', default=None,
|
|
help='Language to use for syntax highlighting')
|
|
@CommandArgument('--poster', default='',
|
|
help='Specify your name for use with pastebin.mozilla.org')
|
|
@CommandArgument('--duration', default='day',
|
|
choices=['d', 'day', 'm', 'month', 'f', 'forever'],
|
|
help='Keep for specified duration (default: %(default)s)')
|
|
@CommandArgument('file', nargs='?', default=None,
|
|
help='Specify the file to upload to pastebin.mozilla.org')
|
|
|
|
def pastebin(self, language, poster, duration, file):
|
|
import urllib
|
|
import urllib2
|
|
|
|
URL = 'https://pastebin.mozilla.org/'
|
|
|
|
FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'},
|
|
{'value': 'bash', 'name': 'Bash', 'extension': 'sh'},
|
|
{'value': 'c', 'name': 'C', 'extension': 'c'},
|
|
{'value': 'cpp', 'name': 'C++', 'extension': 'cpp'},
|
|
{'value': 'html4strict', 'name': 'HTML', 'extension': 'html'},
|
|
{'value': 'javascript', 'name': 'Javascript', 'extension': 'js'},
|
|
{'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'},
|
|
{'value': 'lua', 'name': 'Lua', 'extension': 'lua'},
|
|
{'value': 'perl', 'name': 'Perl', 'extension': 'pl'},
|
|
{'value': 'php', 'name': 'PHP', 'extension': 'php'},
|
|
{'value': 'python', 'name': 'Python', 'extension': 'py'},
|
|
{'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'},
|
|
{'value': 'css', 'name': 'CSS', 'extension': 'css'},
|
|
{'value': 'diff', 'name': 'Diff', 'extension': 'diff'},
|
|
{'value': 'ini', 'name': 'INI file', 'extension': 'ini'},
|
|
{'value': 'java', 'name': 'Java', 'extension': 'java'},
|
|
{'value': 'xml', 'name': 'XML', 'extension': 'xml'},
|
|
{'value': 'xml', 'name': 'XML', 'extension': 'xul'}]
|
|
|
|
lang = ''
|
|
|
|
if file:
|
|
try:
|
|
with open(file, 'r') as f:
|
|
content = f.read()
|
|
# TODO: Use mime-types instead of extensions; suprocess('file <f_name>')
|
|
# Guess File-type based on file extension
|
|
extension = file.split('.')[-1]
|
|
for l in FILE_TYPES:
|
|
if extension == l['extension']:
|
|
print('Identified file as %s' % l['name'])
|
|
lang = l['value']
|
|
except IOError:
|
|
print('ERROR. No such file')
|
|
return 1
|
|
else:
|
|
content = sys.stdin.read()
|
|
duration = duration[0]
|
|
|
|
if language:
|
|
lang = language
|
|
|
|
|
|
params = [
|
|
('parent_pid', ''),
|
|
('format', lang),
|
|
('code2', content),
|
|
('poster', poster),
|
|
('expiry', duration),
|
|
('paste', 'Send')]
|
|
|
|
data = urllib.urlencode(params)
|
|
print('Uploading ...')
|
|
try:
|
|
req = urllib2.Request(URL, data)
|
|
response = urllib2.urlopen(req)
|
|
http_response_code = response.getcode()
|
|
if http_response_code == 200:
|
|
print(response.geturl())
|
|
else:
|
|
print('Could not upload the file, '
|
|
'HTTP Response Code %s' %(http_response_code))
|
|
except urllib2.URLError:
|
|
print('ERROR. Could not connect to pastebin.mozilla.org.')
|
|
return 1
|
|
return 0
|
|
|
|
|
|
@CommandProvider
|
|
class FormatProvider(MachCommandBase):
|
|
@Command('clang-format', category='misc',
|
|
description='Run clang-format on current changes')
|
|
@CommandArgument('--show', '-s', action = 'store_true',
|
|
help = 'Show diff output on instead of applying changes')
|
|
def clang_format(self, show=False):
|
|
import urllib2
|
|
|
|
plat = platform.system()
|
|
fmt = plat.lower() + "/clang-format-3.5"
|
|
fmt_diff = "clang-format-diff-3.5"
|
|
|
|
# We are currently using a modified version of clang-format hosted on people.mozilla.org.
|
|
# This is a temporary work around until we upstream the necessary changes and we can use
|
|
# a system version of clang-format. See bug 961541.
|
|
if plat == "Windows":
|
|
fmt += ".exe"
|
|
else:
|
|
arch = os.uname()[4]
|
|
if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64':
|
|
print("Unsupported platform " + plat + "/" + arch +
|
|
". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
|
|
return 1
|
|
|
|
os.chdir(self.topsrcdir)
|
|
self.prompt = True
|
|
|
|
try:
|
|
if not self.locate_or_fetch(fmt):
|
|
return 1
|
|
clang_format_diff = self.locate_or_fetch(fmt_diff)
|
|
if not clang_format_diff:
|
|
return 1
|
|
|
|
except urllib2.HTTPError as e:
|
|
print("HTTP error {0}: {1}".format(e.code, e.reason))
|
|
return 1
|
|
|
|
from subprocess import Popen, PIPE
|
|
|
|
if os.path.exists(".hg"):
|
|
diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
|
|
"--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h",
|
|
"--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
|
|
else:
|
|
git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE)
|
|
try:
|
|
diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
|
|
"--exclude-from-file=.clang-format-ignore"],
|
|
stdin=git_process.stdout, stdout=PIPE)
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
print("Can't find filterdiff. Please install patchutils.")
|
|
else:
|
|
print("OSError {0}: {1}".format(e.code, e.reason))
|
|
return 1
|
|
|
|
|
|
args = [sys.executable, clang_format_diff, "-p1"]
|
|
if not show:
|
|
args.append("-i")
|
|
cf_process = Popen(args, stdin=diff_process.stdout)
|
|
return cf_process.communicate()[0]
|
|
|
|
def locate_or_fetch(self, root):
|
|
target = os.path.join(self._mach_context.state_dir, os.path.basename(root))
|
|
if not os.path.exists(target):
|
|
site = "https://people.mozilla.org/~ajones/clang-format/"
|
|
if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y':
|
|
print("Download aborted.")
|
|
return 1
|
|
self.prompt = False
|
|
|
|
u = site + root
|
|
print("Downloading {0} to {1}".format(u, target))
|
|
data = urllib2.urlopen(url=u).read()
|
|
temp = target + ".tmp"
|
|
with open(temp, "wb") as fh:
|
|
fh.write(data)
|
|
fh.close()
|
|
os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
os.rename(temp, target)
|
|
return target
|
|
|
|
def mozregression_import():
|
|
# Lazy loading of mozregression.
|
|
# Note that only the mach_interface module should be used from this file.
|
|
try:
|
|
import mozregression.mach_interface
|
|
except ImportError:
|
|
return None
|
|
return mozregression.mach_interface
|
|
|
|
|
|
def mozregression_create_parser():
|
|
# Create the mozregression command line parser.
|
|
# if mozregression is not installed, or not up to date, it will
|
|
# first be installed.
|
|
cmd = MozbuildObject.from_environment()
|
|
cmd._activate_virtualenv()
|
|
mozregression = mozregression_import()
|
|
if not mozregression:
|
|
# mozregression is not here at all, install it
|
|
cmd.virtualenv_manager.install_pip_package('mozregression')
|
|
print("mozregression was installed. please re-run your"
|
|
" command. If you keep getting this message please "
|
|
" manually run: 'pip install -U mozregression'.")
|
|
else:
|
|
# check if there is a new release available
|
|
release = mozregression.new_release_on_pypi()
|
|
if release:
|
|
print(release)
|
|
# there is one, so install it. Note that install_pip_package
|
|
# does not work here, so just run pip directly.
|
|
cmd.virtualenv_manager._run_pip([
|
|
'install',
|
|
'mozregression==%s' % release
|
|
])
|
|
print("mozregression was updated to version %s. please"
|
|
" re-run your command." % release)
|
|
else:
|
|
# mozregression is up to date, return the parser.
|
|
return mozregression.parser()
|
|
# exit if we updated or installed mozregression because
|
|
# we may have already imported mozregression and running it
|
|
# as this may cause issues.
|
|
sys.exit(0)
|
|
|
|
|
|
@CommandProvider
|
|
class MozregressionCommand(MachCommandBase):
|
|
@Command('mozregression',
|
|
category='misc',
|
|
description=("Regression range finder for nightly"
|
|
" and inbound builds."),
|
|
parser=mozregression_create_parser)
|
|
def run(self, **options):
|
|
self._activate_virtualenv()
|
|
mozregression = mozregression_import()
|
|
mozregression.run(options)
|