1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__
import absolute_import
, print_function
, unicode_literals
9 from mach
.decorators
import (
16 from mozbuild
.base
import MachCommandBase
, MozbuildObject
20 class BustedProvider(object):
21 @Command('busted', category
='misc',
22 description
='Query known bugs in our tooling, and file new ones.')
23 def busted_default(self
):
25 payload
= {'include_fields': 'id,summary,last_change_time',
28 response
= requests
.get('https://bugzilla.mozilla.org/rest/bug', payload
)
29 response
.raise_for_status()
30 json_response
= response
.json()
31 if 'bugs' in json_response
and len(json_response
['bugs']) > 0:
32 # Display most recently modifed bugs first.
33 bugs
= sorted(json_response
['bugs'], key
=lambda item
: item
['last_change_time'],
36 print("Bug %s - %s" % (bug
['id'], bug
['summary']))
38 print("No known tooling issues found.")
42 description
='File a bug for busted tooling.')
43 def busted_file(self
):
45 uri
= ('https://bugzilla.mozilla.org/enter_bug.cgi?'
46 'product=Firefox%20Build%20System&component=General&blocked=1543241')
47 webbrowser
.open_new_tab(uri
)
51 class SearchProvider(object):
52 @Command('searchfox', category
='misc',
53 description
='Search for something in Searchfox.')
54 @CommandArgument('term', nargs
='+', help='Term(s) to search for.')
55 def searchfox(self
, term
):
58 uri
= 'https://searchfox.org/mozilla-central/search?q=%s' % term
59 webbrowser
.open_new_tab(uri
)
60 @Command('dxr', category
='misc',
61 description
='Search for something in DXR.')
62 @CommandArgument('term', nargs
='+', help='Term(s) to search for.')
66 uri
= 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
67 webbrowser
.open_new_tab(uri
)
69 @Command('mdn', category
='misc',
70 description
='Search for something on MDN.')
71 @CommandArgument('term', nargs
='+', help='Term(s) to search for.')
75 uri
= 'https://developer.mozilla.org/search?q=%s' % term
76 webbrowser
.open_new_tab(uri
)
78 @Command('google', category
='misc',
79 description
='Search for something on Google.')
80 @CommandArgument('term', nargs
='+', help='Term(s) to search for.')
81 def google(self
, term
):
84 uri
= 'https://www.google.com/search?q=%s' % term
85 webbrowser
.open_new_tab(uri
)
87 @Command('search', category
='misc',
88 description
='Search for something on the Internets. '
89 'This will open 4 new browser tabs and search for the term on Google, '
90 'MDN, DXR, and Searchfox.')
91 @CommandArgument('term', nargs
='+', help='Term(s) to search for.')
92 def search(self
, term
):
100 class UUIDProvider(object):
101 @Command('uuid', category
='misc',
102 description
='Generate a uuid.')
103 @CommandArgument('--format', '-f', choices
=['idl', 'cpp', 'c++'],
104 help='Output format for the generated uuid.')
105 def uuid(self
, format
=None):
108 if format
in [None, 'idl']:
112 if format
in [None, 'cpp', 'c++']:
114 print('{ 0x%s, 0x%s, 0x%s, \\' % (u
[0:8], u
[8:12], u
[12:16]))
115 pairs
= tuple(map(lambda n
: u
[n
:n
+2], range(16, 32, 2)))
116 print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs
)
119 MACH_PASTEBIN_DURATIONS
= {
120 'onetime': 'onetime',
127 EXTENSION_TO_HIGHLIGHTER
= {
129 'Dockerfile': 'docker',
131 'applescript': 'applescript',
132 'arduino': 'arduino',
136 'clojure': 'clojure',
138 'coffee': 'coffee-script',
139 'console': 'console',
154 'handlebars': 'handlebars',
155 'haskell': 'haskell',
159 'ipy': 'ipythonconsole',
160 'ipynb': 'ipythonconsole',
169 'lisp': 'common-lisp',
170 'lsp': 'common-lisp',
182 'postgresql': 'postgresql',
194 'typoscript': 'typoscript',
203 def guess_highlighter_from_path(path
):
204 '''Return a known highlighter from a given path
206 Attempt to select a highlighter by checking the file extension in the mapping
207 of extensions to highlighter. If that fails, attempt to pass the basename of
208 the file. Return `_code` as the default highlighter if that fails.
212 _name
, ext
= os
.path
.splitext(path
)
214 if ext
.startswith('.'):
217 if ext
in EXTENSION_TO_HIGHLIGHTER
:
218 return EXTENSION_TO_HIGHLIGHTER
[ext
]
220 basename
= os
.path
.basename(path
)
222 return EXTENSION_TO_HIGHLIGHTER
.get(basename
, '_code')
225 PASTEMO_MAX_CONTENT_LENGTH
= 250 * 1024 * 1024
227 PASTEMO_URL
= 'https://paste.mozilla.org/api/'
229 MACH_PASTEBIN_DESCRIPTION
= '''
230 Command line interface to paste.mozilla.org.
232 Takes either a filename whose content should be pasted, or reads
233 content from standard input. If a highlighter is specified it will
234 be used, otherwise the file name will be used to determine an
235 appropriate highlighter.
240 class PastebinProvider(object):
241 @Command('pastebin', category
='misc',
242 description
=MACH_PASTEBIN_DESCRIPTION
)
243 @CommandArgument('--list-highlighters', action
='store_true',
244 help='List known highlighters and exit')
245 @CommandArgument('--highlighter', default
=None,
246 help='Syntax highlighting to use for paste')
247 @CommandArgument('--expires', default
='week',
248 choices
=sorted(MACH_PASTEBIN_DURATIONS
.keys()),
249 help='Expire paste after given time duration (default: %(default)s)')
250 @CommandArgument('--verbose', action
='store_true',
251 help='Print extra info such as selected syntax highlighter')
252 @CommandArgument('path', nargs
='?', default
=None,
253 help='Path to file for upload to paste.mozilla.org')
254 def pastebin(self
, list_highlighters
, highlighter
, expires
, verbose
, path
):
257 def verbose_print(*args
, **kwargs
):
258 '''Print a string if `--verbose` flag is set'''
260 print(*args
, **kwargs
)
262 # Show known highlighters and exit.
263 if list_highlighters
:
264 lexers
= set(EXTENSION_TO_HIGHLIGHTER
.values())
265 print('Available lexers:\n'
266 ' - %s' % '\n - '.join(sorted(lexers
)))
269 # Get a correct expiry value.
271 verbose_print('Setting expiry from %s' % expires
)
272 expires
= MACH_PASTEBIN_DURATIONS
[expires
]
273 verbose_print('Using %s as expiry' % expires
)
275 print('%s is not a valid duration.\n'
276 '(hint: try one of %s)' %
277 (expires
, ', '.join(MACH_PASTEBIN_DURATIONS
.keys())))
285 # Get content to be pasted.
287 verbose_print('Reading content from %s' % path
)
289 with
open(path
, 'r') as f
:
292 print('ERROR. No such file %s' % path
)
295 lexer
= guess_highlighter_from_path(path
)
297 data
['lexer'] = lexer
299 verbose_print('Reading content from stdin')
300 content
= sys
.stdin
.read()
302 # Assert the length of content to be posted does not exceed the maximum.
303 content_length
= len(content
)
304 verbose_print('Checking size of content is okay (%d)' % content_length
)
305 if content_length
> PASTEMO_MAX_CONTENT_LENGTH
:
306 print('Paste content is too large (%d, maximum %d)' %
307 (content_length
, PASTEMO_MAX_CONTENT_LENGTH
))
310 data
['content'] = content
312 # Highlight as specified language, overwriting value set from filename.
314 verbose_print('Setting %s as highlighter' % highlighter
)
315 data
['lexer'] = highlighter
318 verbose_print('Sending request to %s' % PASTEMO_URL
)
319 resp
= requests
.post(PASTEMO_URL
, data
=data
)
321 # Error code should always be 400.
322 # Response content will include a helpful error message,
323 # so print it here (for example, if an invalid highlighter is
324 # provided, it will return a list of valid highlighters).
325 if resp
.status_code
>= 400:
326 print('Error code %d: %s' % (resp
.status_code
, resp
.content
))
329 verbose_print('Pasted successfully')
331 response_json
= resp
.json()
333 verbose_print('Paste highlighted as %s' % response_json
['lexer'])
334 print(response_json
['url'])
337 except Exception as e
:
338 print('ERROR. Paste failed.')
343 def mozregression_import():
344 # Lazy loading of mozregression.
345 # Note that only the mach_interface module should be used from this file.
347 import mozregression
.mach_interface
350 return mozregression
.mach_interface
353 def mozregression_create_parser():
354 # Create the mozregression command line parser.
355 # if mozregression is not installed, or not up to date, it will
356 # first be installed.
357 cmd
= MozbuildObject
.from_environment()
358 cmd
._activate
_virtualenv
()
359 mozregression
= mozregression_import()
360 if not mozregression
:
361 # mozregression is not here at all, install it
362 cmd
.virtualenv_manager
.install_pip_package('mozregression')
363 print("mozregression was installed. please re-run your"
364 " command. If you keep getting this message please "
365 " manually run: 'pip install -U mozregression'.")
367 # check if there is a new release available
368 release
= mozregression
.new_release_on_pypi()
371 # there is one, so install it. Note that install_pip_package
372 # does not work here, so just run pip directly.
373 cmd
.virtualenv_manager
._run
_pip
([
375 'mozregression==%s' % release
377 print("mozregression was updated to version %s. please"
378 " re-run your command." % release
)
380 # mozregression is up to date, return the parser.
381 return mozregression
.parser()
382 # exit if we updated or installed mozregression because
383 # we may have already imported mozregression and running it
384 # as this may cause issues.
389 class MozregressionCommand(MachCommandBase
):
390 @Command('mozregression',
392 description
=("Regression range finder for nightly"
393 " and inbound builds."),
394 parser
=mozregression_create_parser
)
395 def run(self
, **options
):
396 self
._activate
_virtualenv
()
397 mozregression
= mozregression_import()
398 mozregression
.run(options
)