| ; To use this, | 
 | ; 1) Add to init.el: | 
 | ;    (setq-default chrome-root "/path/to/chrome/src/") | 
 | ;    (add-to-list 'load-path (concat chrome-root "tools/emacs")) | 
 | ;    (require 'trybot) | 
 | ; 2) Run on trybot output: | 
 | ;    M-x trybot | 
 | ; | 
 | ; To hack on this, | 
 | ; M-x eval-buffer | 
 | ; M-x trybot-test-win  or  M-x trybot-test-mac | 
 |  | 
 | (defvar chrome-root nil | 
 |   "Path to the src/ directory of your Chrome checkout.") | 
 |  | 
 | (defun get-chrome-root () | 
 |   (or chrome-root default-directory)) | 
 |  | 
 | ; Hunt down from the top, case correcting each path component as needed. | 
 | ; Currently does not keep a cache.  Returns nil if no matching file can be | 
 | ; figured out. | 
 | (defun case-corrected-filename (filename) | 
 |   (save-match-data | 
 |     (let ((path-components (split-string filename "/")) | 
 |           (corrected-path (file-name-as-directory (get-chrome-root)))) | 
 |       (mapc | 
 |        (function | 
 |         (lambda (elt) | 
 |           (if corrected-path | 
 |               (let ((next-component | 
 |                      (car (member-ignore-case | 
 |                            elt (directory-files corrected-path))))) | 
 |                 (setq corrected-path | 
 |                       (and next-component | 
 |                            (file-name-as-directory | 
 |                             (concat corrected-path next-component)))))))) | 
 |        path-components) | 
 |       (if corrected-path | 
 |           (file-relative-name (directory-file-name corrected-path) | 
 |                               (get-chrome-root)) | 
 |         nil)))) | 
 |  | 
 | (defun trybot-fixup-win () | 
 |   "Fix up Windows-specific output." | 
 |  | 
 |   ; Fix Windows paths ("d:\...\src\"). | 
 |   (save-excursion | 
 |     ; This regexp is subtle and rather hard to read. :~( | 
 |     ; Use regexp-builder when making changes to it. | 
 |     (while (re-search-forward | 
 |             (concat | 
 |              ; First part: path leader, either of the form | 
 |              ;   e:\...src\  or  ..\ | 
 |              "\\(^.:\\\\.*\\\\src\\\\\\|\\.\\.\\\\\\)" | 
 |              ; Second part: path, followed by error message marker. | 
 |              "\\(.*?\\)[(:]") nil t) | 
 |       (replace-match "" nil t nil 1) | 
 |       ; Line now looks like: | 
 |       ;  foo\bar\baz.cc error message here | 
 |       ; We want to fixup backslashes in path into forward slashes, | 
 |       ; without modifying the error message - by matching up to the | 
 |       ; first colon above (which will be just beyond the end of the | 
 |       ; filename) we can use the end of the match as a limit. | 
 |       (subst-char-in-region (point) (match-end 0) ?\\ ?/) | 
 |       ; See if we can correct the file name casing. | 
 |       (let ((filename (buffer-substring (match-beginning 2) (match-end 2)))) | 
 |         (if (and (not (file-exists-p filename)) | 
 |                  (setq filename (case-corrected-filename filename))) | 
 |             (replace-match filename t t nil 2)))))) | 
 |  | 
 | (defun trybot-fixup-maclin () | 
 |   "Fix up Mac/Linux output." | 
 |   (save-excursion | 
 |     (while (re-search-forward "^/b/build/[^ ]*/src/" nil t) | 
 |       (replace-match "")))) | 
 |  | 
 | (defun trybot-fixup (type-hint) | 
 |   "Parse and fixup the contents of the current buffer as trybot output." | 
 |  | 
 |   ; XXX is there something I should so so this stuff doesn't end up on the | 
 |   ; undo stack? | 
 |  | 
 |   ;; Fixup paths. | 
 |   (cd (get-chrome-root)) | 
 |  | 
 |   (goto-char (point-min)) | 
 |  | 
 |   ;; Fix up path references. | 
 |   (cond ((eq type-hint 'win) (trybot-fixup-win)) | 
 |         ((eq type-hint 'mac) (trybot-fixup-maclin)) | 
 |         ((eq type-hint 'linux) (trybot-fixup-maclin)) | 
 |         (t (trybot-fixup-win) (trybot-fixup-maclin))) | 
 |  | 
 |   (compilation-mode)) | 
 |  | 
 | (defun trybot-get-new-buffer () | 
 |   "Get a new clean buffer for trybot output." | 
 |   ; Use trybot-buffer-name if available; otherwise, "*trybot*". | 
 |   (let ((buffer-name (if (boundp 'trybot-buffer-name) | 
 |                          trybot-buffer-name | 
 |                        "*trybot*"))) | 
 |     (let ((old (get-buffer buffer-name))) | 
 |       (when old (kill-buffer old))) | 
 |     (get-buffer-create buffer-name))) | 
 |  | 
 | (defun trybot-fetch (type-hint url) | 
 |   "Fetch a URL and postprocess it as trybot output." | 
 |  | 
 |   (let ((on-fetch-completion | 
 |          (lambda (process state) | 
 |            (switch-to-buffer (process-buffer process)) | 
 |            (when (equal state "finished\n") | 
 |              (trybot-fixup (process-get process 'type-hint))))) | 
 |         (command (concat "curl -s " (shell-quote-argument url) | 
 |                          ; Pipe it through the output shortener. | 
 |                          (cond | 
 |                           ((eq type-hint 'win) | 
 |                            (concat " | " (get-chrome-root) | 
 |                                    "build/sanitize-win-build-log.sh")) | 
 |                           ((eq type-hint 'mac) | 
 |                            (concat " | " (get-chrome-root) | 
 |                                    "build/sanitize-mac-build-log.sh")))))) | 
 |  | 
 |     ; Start up the subprocess. | 
 |     (let* ((coding-system-for-read 'utf-8-dos) | 
 |            (buffer (trybot-get-new-buffer)) | 
 |            (process (start-process-shell-command "curl" buffer command))) | 
 |       ; Attach the type hint to the process so we can get it back when | 
 |       ; the process completes. | 
 |       (process-put process 'type-hint type-hint) | 
 |       (set-process-query-on-exit-flag process nil) | 
 |       (set-process-sentinel process on-fetch-completion)))) | 
 |  | 
 | (defun trybot-test (type-hint filename) | 
 |   "Load the given test data filename and do the trybot parse on it." | 
 |  | 
 |   (let ((trybot-buffer-name "*trybot-test*") | 
 |         (url (concat "file://" (get-chrome-root) "tools/emacs/" filename))) | 
 |     (trybot-fetch type-hint url))) | 
 |  | 
 | (defun trybot-test-win () | 
 |   "Load the Windows test data and do the trybot parse on it." | 
 |   (interactive) | 
 |   (trybot-test 'win "trybot-windows.txt")) | 
 | (defun trybot-test-mac () | 
 |   "Load the Mac test data and do the trybot parse on it." | 
 |   (interactive) | 
 |   (trybot-test 'mac "trybot-mac.txt")) | 
 | (defun trybot-test-linux () | 
 |   "Load the Linux test data and do the trybot parse on it." | 
 |   (interactive) | 
 |   (trybot-test 'linux "trybot-linux.txt")) | 
 |  | 
 | (defun trybot (url) | 
 |   "Fetch a trybot URL and fix up the output into a compilation-mode buffer." | 
 |   (interactive "sURL to trybot stdout (leave empty to use clipboard): ") | 
 |  | 
 |   ;; Yank URL from clipboard if necessary. | 
 |   (when (= (length url) 0) | 
 |     (with-temp-buffer | 
 |       (clipboard-yank) | 
 |       (setq url (buffer-string)))) | 
 |  | 
 |   ;; Append /text to the URL to get plain text output in the common | 
 |   ;; case of getting a URL to the HTML build log. | 
 |   (when (equal "stdio" (car (last (split-string url "/")))) | 
 |     (setq url (concat url "/text"))) | 
 |  | 
 |   (let ((type-hint (cond ((string-match "/[Ww]in" url) 'win) | 
 |                          ((string-match "/mac/" url) 'mac) | 
 |                          ; Match /linux, /linux_view, etc. | 
 |                          ((string-match "/linux" url) 'linux) | 
 |                          (t 'unknown)))) | 
 |     (trybot-fetch type-hint url))) | 
 |  | 
 | (provide 'trybot) |