Ignas Mikalajūnas avatar Ignas Mikalajūnas committed 9c4b9b0

Added emacs integration for the complexity calculation tool.

As a side effect now you should be able to use complexity.py as a
command line tool. If you pass a python source file to it you will
get:

line_start line_end complexity_score block_type

for every block in the code. So it should make integration with other
editors/tools easier. You can also pass python code to STDIN.

Comments (0)

Files changed (3)

+#!/usr/bin/env python
+from tempfile import mkstemp
+import sys
+import os
 import compiler#{{{
 from compiler.visitor import ASTVisitor
 
         vim.command(':sign place %i line=%i name=%s file=%s' %
                     (line, line, complexity, filename))#}}}
 
+
+def main():
+    if sys.stdin.isatty() and len(sys.argv) < 2:
+        print "Missing filename"
+        return
+
+    temporary_file = None
+    if len(sys.argv) > 1:
+        file_name = sys.argv[1]
+    else:
+        content = sys.stdin.read()
+        temporary_fd, file_name = mkstemp()
+        temporary_file = os.fdopen(temporary_fd, "w")
+        temporary_file.write(content)
+        temporary_file.close()
+
+    try:
+        for score in compute_scores_for(file_name):
+            print score.start_line, score.end_line, score.score, score.type_
+    except:
+        pass
+    finally:
+        if temporary_file is not None:
+            os.unlink(file_name)
+
+
+if __name__ == '__main__':
+    main()
+;;; linum.el --- Display line numbers to the left of buffers
+
+;; Copyright (C) 2007, 2008  Markus Triska
+
+;; Author: Markus Triska <markus.triska@gmx.at>
+;; Keywords: convenience
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file GPL.txt .  If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; Display line numbers for the current buffer. Copy linum.el to your
+;; load-path and add to your .emacs:
+
+;;    (require 'linum)
+
+;; Then toggle display of line numbers with M-x linum-mode. To enable
+;; line numbering in all buffers, use M-x global-linum-mode.
+
+;;; Code:
+
+(defconst linum-version "0.9wza")
+
+(defvar linum-overlays nil "Overlays used in this buffer.")
+(defvar linum-available nil "Overlays available for reuse.")
+(defvar linum-before-numbering-hook nil
+  "Functions run in each buffer before line numbering starts.")
+
+(mapc #'make-variable-buffer-local '(linum-overlays linum-available))
+
+(defgroup linum nil
+  "Show line numbers to the left of buffers"
+  :group 'convenience)
+
+;;;###autoload
+(defcustom linum-format 'dynamic
+  "Format used to display line numbers. Either a format string
+like \"%7d\", 'dynamic to adapt the width as needed, or a
+function that is called with a line number as its argument and
+should evaluate to a string to be shown on that line. See also
+`linum-before-numbering-hook'."
+  :group 'linum
+  :type 'sexp)
+
+(defface linum
+  '((t :inherit (shadow default)))
+  "Face for displaying line numbers in the display margin."
+  :group 'linum)
+
+(defcustom linum-eager t
+  "Whether line numbers should be updated after each command.
+The conservative setting `nil' might miss some buffer changes,
+and you have to scroll or press C-l to update the numbers."
+  :group 'linum
+  :type 'boolean)
+
+(defcustom linum-delay nil
+  "Delay updates to give Emacs a chance for other changes."
+  :group 'linum
+  :type 'boolean)
+
+;;;###autoload
+(define-minor-mode linum-mode
+  "Toggle display of line numbers in the left marginal area."
+  :lighter ""                           ; for desktop.el
+  (if linum-mode
+      (progn
+        (if linum-eager
+            (add-hook 'post-command-hook (if linum-delay
+                                             'linum-schedule
+                                           'linum-update-current) nil t)
+          (add-hook 'after-change-functions 'linum-after-change nil t))
+        (add-hook 'window-scroll-functions 'linum-after-scroll nil t)
+        ;; mistake in Emacs: window-size-change-functions cannot be local
+        (add-hook 'window-size-change-functions 'linum-after-size)
+        (add-hook 'change-major-mode-hook 'linum-delete-overlays nil t)
+        (add-hook 'window-configuration-change-hook
+                  'linum-after-config nil t)
+        (linum-update-current))
+    (remove-hook 'post-command-hook 'linum-update-current t)
+    (remove-hook 'post-command-hook 'linum-schedule t)
+    (remove-hook 'window-size-change-functions 'linum-after-size)
+    (remove-hook 'window-scroll-functions 'linum-after-scroll t)
+    (remove-hook 'after-change-functions 'linum-after-change t)
+    (remove-hook 'window-configuration-change-hook 'linum-after-config t)
+    (remove-hook 'change-major-mode-hook 'linum-delete-overlays t)
+    (linum-delete-overlays)))
+
+;;;###autoload
+(define-globalized-minor-mode global-linum-mode linum-mode linum-on)
+
+(defun linum-on ()
+  (unless (minibufferp)
+    (linum-mode 1)))
+
+(defun linum-delete-overlays ()
+  "Delete all overlays displaying line numbers for this buffer."
+  (mapc #'delete-overlay linum-overlays)
+  (setq linum-overlays nil)
+  (dolist (w (get-buffer-window-list (current-buffer) nil t))
+    (set-window-margins w 0)))
+
+(defun linum-update-current ()
+  "Update line numbers for the current buffer."
+  (linum-update (current-buffer)))
+
+(defun linum-update (buffer)
+  "Update line numbers for all windows displaying BUFFER."
+  (with-current-buffer buffer
+    (when linum-mode
+      (setq linum-available linum-overlays)
+      (setq linum-overlays nil)
+      (save-excursion
+        (mapc #'linum-update-window
+              (get-buffer-window-list buffer nil 'visible)))
+      (mapc #'delete-overlay linum-available)
+      (setq linum-available nil))))
+
+(defun linum-update-window (win)
+  "Update line numbers for the portion visible in window WIN."
+  (goto-char (window-start win))
+  (let ((line (line-number-at-pos))
+        (limit (window-end win t))
+        (fmt (cond ((stringp linum-format) linum-format)
+                   ((eq linum-format 'dynamic)
+                    (let ((w (length (number-to-string
+                                      (count-lines (point-min) (point-max))))))
+                      (concat "%" (number-to-string w) "d")))))
+        (width 0))
+    (run-hooks 'linum-before-numbering-hook)
+    ;; Create an overlay (or reuse an existing one) for each
+    ;; line visible in this window, if necessary.
+    (while (and (not (eobp)) (<= (point) limit))
+      (let* ((str (if fmt
+                      (propertize (format fmt line) 'face 'linum)
+                    (funcall linum-format line)))
+             (visited (catch 'visited
+                        (dolist (o (overlays-in (point) (point)))
+                          (when (string= (overlay-get o 'linum-str) str)
+                            (unless (memq o linum-overlays)
+                              (push o linum-overlays))
+                            (setq linum-available (delete o linum-available))
+                            (throw 'visited t))))))
+        (setq width (max width (length str)))
+        (unless visited
+          (let ((ov (if (null linum-available)
+                        (make-overlay (point) (point))
+                      (move-overlay (pop linum-available) (point) (point)))))
+            (push ov linum-overlays)
+            (overlay-put ov 'before-string
+                         (propertize " " 'display `((margin left-margin) ,str)))
+            (overlay-put ov 'linum-str str))))
+      (forward-line)
+      (setq line (1+ line)))
+    (set-window-margins win width)))
+
+(defun linum-after-change (beg end len)
+  ;; update overlays on deletions, and after newlines are inserted
+  (when (or (= beg end)
+            (= end (point-max))
+            ;; TODO: use string-match-p with CVS or new release
+            (string-match "\n" (buffer-substring-no-properties beg end)))
+    (linum-update-current)))
+
+(defun linum-after-scroll (win start)
+  (linum-update (window-buffer win)))
+
+(defun linum-after-size (frame)
+  (linum-after-config))
+
+(defun linum-schedule ()
+  ;; schedule an update; the delay gives Emacs a chance for display changes
+  (run-with-idle-timer 0 nil #'linum-update-current))
+
+(defun linum-after-config ()
+  (walk-windows (lambda (w) (linum-update (window-buffer w))) nil 'visible))
+
+(provide 'linum)
+;;; linum.el ends here
+;;; pycomplexity.el --- Display python code complexity to the left of buffers
+
+;; Copyright (C) 2009 Ignas Mikalajunas
+
+;; Author: Ignas Mikalajunas
+;; Keywords: convenience
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file GPL.txt .  If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; Display complexity information for the current buffer.
+
+;; Add to your .emacs:
+
+;;    (add-to-list 'load-path "~/.site-lisp/vim-complexity/")
+
+;;    (require 'linum)
+;;    (require 'pycomplexity)
+;;    (add-hook 'python-mode-hook
+;;        (function (lambda ()
+;;          (pycomplexity-mode)
+;;          (linum-mode))))
+
+;;; Code:
+
+(defconst pycomplexity-version "0.1")
+
+
+(defvar complexity-last-change 0 "Time last change to some python buffer happened.")
+(defvar complexity-data nil "Calcuated code complexity information for this buffer.")
+(make-variable-buffer-local 'complexity-data)
+
+(defgroup pycomplexity nil
+  "Show complexity information to the left of buffers"
+  :group 'convenience)
+
+(defface pycomplexity-complexity-low
+    '((t (:background "green"
+          :foreground "green")))
+  "Face that marks simple code "
+  :group 'pycomplexity)
+
+(defface pycomplexity-complexity-normal
+    '((t (:background "yellow"
+          :foreground "yellow")))
+  "Face that marks normal code "
+  :group 'pycomplexity)
+
+(defface pycomplexity-complexity-high
+    '((t (:background "red"
+          :foreground "red")))
+  "Face that marks complex code "
+  :group 'pycomplexity)
+
+(defcustom pycomplexity-delay 5
+  "Update coverage information once in this many seconds."
+  :group 'pycomplexity
+  :type 'int)
+
+(defcustom pycomplexity-python "python"
+  "Python interpreter used to run the complexity calculation script."
+  :group 'pycomplexity
+  :type 'string)
+
+(defcustom pycomplexity-script
+  (expand-file-name "complexity.py"
+                    (file-name-directory (or load-file-name buffer-file-name)))
+  "Pycomplexity python script."
+  :group 'pycomplexity
+  :type 'string)
+
+;;;###autoload
+(define-minor-mode pycomplexity-mode
+  "Toggle display complexity of the python code you are editing."
+  :lighter ""                           ; for desktop.el
+  (if pycomplexity-mode
+      (progn
+        (add-hook 'after-change-functions 'pycomplexity-on-change nil t)
+        (add-hook 'after-save-hook 'pycomplexity-on-change-force nil t)
+        (setf linum-format 'pycomplexity-line-format)
+        (pycomplexity-on-change-force))
+      (setf linum-format 'dynamic)
+      (remove-hook 'after-change-functions 'pycomplexity-on-change t)))
+
+(defun pycomplexity-get-complexity (line data)
+  (multiple-value-bind (face str complexity)
+      (loop for info in data
+         for from = (first info)
+         for to = (second info)
+         for complexity = (third info)
+         when (and (>= line from)
+                   (<= line to))
+         return (cond ((> complexity 14) (values 'pycomplexity-complexity-high "h" complexity))
+                      ((> complexity 7) (values 'pycomplexity-complexity-normal "n" complexity))
+                      (t (values 'pycomplexity-complexity-low "l" complexity)))
+         when (< line from)
+         return (values 'default " " 0))
+    (if face (values face str complexity)
+        (values 'default " " 0))))
+
+(defun pycomplexity-line-format (line)
+  (multiple-value-bind (face str complexity)
+      (pycomplexity-get-complexity line complexity-data)
+    (propertize str 'face face
+                'help-echo (format "Complexity of this function is %d" complexity))))
+
+
+(defun pycomplexity-make-buffer-copy ()
+  (let*  ((source-file-name       buffer-file-name)
+          (file-name (flymake-create-temp-inplace source-file-name "complexity")))
+      (make-directory (file-name-directory file-name) 1)
+      (write-region nil nil file-name nil 566)
+      file-name))
+
+(defun pycomplexity-get-raw-complexity-data (file-name)
+  (shell-command-to-string (format "%s %s %s"
+                                   pycomplexity-python
+                                   pycomplexity-script
+                                   file-name)))
+
+(defun pycomplexity-on-change-force (&optional beg end len)
+  (pycomplexity-on-change beg end len t))
+
+(defun pycomplexity-on-change (&optional beg end len force)
+  (let ((since-last-change (- (float-time) complexity-last-change)))
+    (when (or (> since-last-change pycomplexity-delay) force)
+       (setf complexity-last-change (float-time))
+       (let* ((temp-source-file-name (pycomplexity-make-buffer-copy))
+              (result (pycomplexity-get-raw-complexity-data temp-source-file-name))
+              (data (loop for line in (split-string result "[\n\r]+")
+                 for parsed-line = (loop for item in (split-string line)
+                                              when item collect (read item))
+                 when (and parsed-line
+                           (equal (car (last parsed-line)) 'function))
+                 collect (subseq parsed-line 0 3))))
+          (when data (setf complexity-data data))
+          (delete-file temp-source-file-name)))))
+
+(provide 'pycomplexity)
+;;; pycomplexity.el ends here
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.