diff --git a/.gitattributes b/.gitattributes index 4a6b0863975271d1562444de9e11d468f0a0f4bf..5e4155c8e01f93c2c93118c3d9255fcae06b7c7d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4921,6 +4921,7 @@ scripts/md5base64.pl -text scripts/opac2.sql -text scripts/opac3.el -text scripts/org-link-minor-mode.el -text +scripts/phpunit.el -text scripts/split_unimarc.php -text scripts/svn-export.sh -text scripts/test_performance_recherche.php -text diff --git a/scripts/phpunit.el b/scripts/phpunit.el new file mode 100644 index 0000000000000000000000000000000000000000..4d62c7fe0457e385c883f5b4f70baf6337d16fb7 --- /dev/null +++ b/scripts/phpunit.el @@ -0,0 +1,377 @@ +;;; phpunit.el --- Helper function for unittest with Phpunit + +;; Copyright (C) 2009 Free Software Foundation, Inc. +;; +;; Author: Ye Wenbin <wenbinye@gmail.com> +;; Maintainer: Ye Wenbin <wenbinye@gmail.com> +;; Created: 21 Dec 2009 +;; Version: 0.01 +;; Keywords: languages, tools + +;; This program 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 2, or (at your option) +;; any later version. +;; +;; This program 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 this program; if not, write to the Free Software +;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Commentary: +;; Features privodes: +;; 1. automatic generate test +;; 2. run test for select function + +;; Put this file into your load-path and the following into your ~/.emacs: +;; (require 'phpunit) +;; (add-hook 'php-mode-hook (lambda () (phpunit-mode 1))) + +;;; Code: + +(eval-when-compile + (require 'cl)) + +(defvar phpunit-class-regexp + "^\\s-*\\(?:abstract\\s-+\\)?class\\s-+\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*" + "Regexp that match class declaration") + +(defvar phpunit-function-regexp + "^\\s-*\\(\\(?:\\(?:abstract\\|final\\|private\\|protected\\|public\\|static\\)\\s-+\\)*\\)function\\s-+\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*(" + "Regexp that match function declaration") + +(defvar phpunit-project-config + '( + (get-test-class-function . phpunit-get-test-class-suffix) + (get-source-class-function . phpunit-get-source-class-suffix) + (get-source-file-function . phpunit-get-source-file-annotated) + (get-test-file-function . phpunit-get-test-file-simple) + (get-file-type-function . phpunit-get-file-type-simple) + (source-directory . "lib") + (test-directory . "tests") + (source-file-extension . ".php") + (test-file-extension . ".php") + ) + "Project setting for misc operations. + + `get-test-class-function': get test class name from source class name + `get-source-class-function': get source class name from test class name + `get-source-file-function': get source file from source class name + `get-test-file-function': get test file from test class name + `get-file-type-function': detect whether current file is in source file or test file + `source-directory': source file directory, default is 'lib' + `test-directory': test file directory, default is 'tests' + `source-file-extension': file extension for source file + `test-file-extension': file extension for test file +") + +(defvar phpunit-create-test-function 'phpunit-create-test-simple + "Function to create test class code") + +(defvar phpunit-config-file ".phpunit-config" + "Project configuration file name") + +(defvar phpunit-template-file "phpunit.tpl" + "Template file name for test file") + +(defvar phpunit-class-cache-file ".phpunit-classes" + "Cache file name for lookup class-file") + +(defun phpunit-class-ap () + "Get current class name" + (save-excursion + (if (re-search-backward phpunit-class-regexp nil t) + (match-string-no-properties 1)))) + +(defun phpunit-function-ap () + "Get current function name" + (save-excursion + (goto-char (line-end-position)) + (if (re-search-backward phpunit-function-regexp nil t) + (cons (match-string-no-properties 1) (match-string-no-properties 2))))) + +(defun phpunit-file-class () + "Get class name for current file" + (or (phpunit-class-ap) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward phpunit-class-regexp nil t) + (match-string-no-properties 1))))) + +(defun phpunit-file-functions () + "Get all functions for current file" + (let (functions) + (save-excursion + (goto-char (point-min)) + (while (and (not (eobp)) + (re-search-forward phpunit-function-regexp nil t)) + (push (cons (match-string-no-properties 1) (match-string-no-properties 2)) + functions))) + functions)) + +(defun phpunit-project-config (entry) + (if (assoc entry phpunit-project-config) + (cdr (assoc entry phpunit-project-config)) + (cdr (assoc entry (default-value 'phpunit-project-config))))) + +(defun phpunit-get-test-class-suffix (class) + "Add suffix 'Test' to get test class name" + (concat class "Test")) + +(defun phpunit-get-source-class-suffix (test-class) + "Remove suffix 'Test' to get source class name" + (replace-regexp-in-string "Test$" "" test-class)) + +(defun phpunit-get-test-class (class) + "Get test class name for `class'" + (funcall (phpunit-project-config 'get-test-class-function) class)) + +(defun phpunit-get-source-class (test-class) + "Get source class name for `test-class'" + (funcall (phpunit-project-config 'get-source-class-function) test-class)) + +(defun phpunit-find-top-directory (file &optional dir) + "Find `file' in all parent directories of `dir'(include `dir')" + (or dir (setq dir (expand-file-name default-directory))) + (let ((thefile (expand-file-name file dir))) + (if (file-exists-p thefile) + thefile + (setq pdir (directory-file-name (file-name-directory dir))) + (if (string= pdir dir) + nil + (phpunit-find-top-directory file pdir))))) + +;; (defun phpunit-find-file-cached (class) +;; "Find class name from cached file" +;; (let ((file (phpunit-find-top-directory phpunit-class-cache-file))) +;; (unless file +;; (let ((dir (read-directory-name "Project root directory: "))) +;; (phpunit-build-cache-file dir))))) + +(defun phpunit-get-source-file (class) + "Get source file for `class'" + (funcall (phpunit-project-config 'get-source-file-function) class)) + +(defun phpunit-get-test-file (test-class) + "Get test file for `test-class'" + (funcall (phpunit-project-config 'get-test-file-function) test-class)) + +(defun phpunit-get-test-file-simple (test-class) + "Get file in `test-directory'. +The file name equals to test class name + `test-file-extension'." + (let ((dir (phpunit-find-top-directory (phpunit-project-config 'test-directory)))) + (if dir + (concat (file-name-as-directory dir) test-class + (phpunit-project-config 'test-file-extension)) + (message "Can't not find test directory '%s'" (phpunit-project-config 'test-directory))))) + +(defun phpunit-get-source-file-annotated (class) + "Find source file from annotation in current buffer when edit test file" + (save-excursion + (goto-char (point-min)) + (if (re-search-forward "@source\\s-+\\([--:\\\\$+<>@-Z_a-z~*?\x100-\xffff]+\\)" nil t) + (match-string 1)))) + +(defun phpunit-get-test-file-pear (test-class) + "Find test file using pear class name rule" + (let ((dir (phpunit-find-top-directory (phpunit-project-config 'test-directory)))) + (if dir + (concat (file-name-as-directory dir) + (replace-regexp-in-string "_" "/" test-class) + (phpunit-project-config 'test-file-extension)) + (message "Can't not find test directory '%s'" (phpunit-project-config 'test-directory))))) + +(defun phpunit-get-source-file-pear (class) + "Find source file using pear class name rule" + (let ((dir (phpunit-find-top-directory (phpunit-project-config 'source-directory)))) + (if dir + (concat (file-name-as-directory dir) + (replace-regexp-in-string "_" "/" class) + (phpunit-project-config 'source-file-extension)) + (message "Can't not find test directory '%s'" (phpunit-project-config 'source-directory))))) + +(defun phpunit-get-file-type () + "Detect file whether is test class or source class" + (funcall (phpunit-project-config 'get-file-type-function))) + +(defun phpunit-get-file-type-simple () + "Detect php file type by check whether current class name end with 'Test'" + (if (string-match "Test$" (phpunit-file-class)) + 'test + 'source)) + +(defun phpunit-load-config (&optional reload) + "Load phpunit config" + (interactive "P") + (when (or (not (local-variable-p 'phpunit-project-config)) reload) + (make-local-variable 'phpunit-project-config) + (let ((file (phpunit-find-top-directory phpunit-config-file))) + (when file + (load file t))))) + +(defun phpunit-create-test-template (test-class test-file source-class source-file) + "create test code using `template-simple'" + (let ((file (locate-file phpunit-template-file template-directory-list))) + (if file + (template-simple-expand-template file) + (message "Template file '%s' is not exists" phpunit-template-file)))) + +(defun phpunit-create-test-simple (test-class test-file source-class source-file) + "Create test code" + (insert (format "<?php +/** + * TestCase for %s + * @source %s + */ +require_once('PHPUnit/Framework.php'); +require_once('%s'); +class %s extends PHPUnit_Framework_TestCase +{ + protected function setUp() + { + } +} +" + source-class source-file source-file test-class))) + +(defun phpunit-create-test-1 (test-class test-file source-class source-file &optional other-window) + (funcall (if other-window 'find-file-other-window 'find-file) test-file) + (funcall phpunit-create-test-function test-class test-file source-class source-file) + (save-buffer)) + +(defun phpunit-get-source-function (test-function) + "Get function name from `test-function'" + (let ((name (replace-regexp-in-string "^test" "" test-function))) + (if (> (length name) 0) + ;; downcase first letter + (concat (downcase (substring name 0 1)) (substring name 1)) + name))) + +(defun phpunit-get-test-function (function) + "Get function name from `test-function'" + (concat "test" (upcase-initials function))) + +(defun phpunit-function-position (function) + "Get point for `function'" + (save-excursion + (goto-char (point-min)) + (if (re-search-forward (concat "^\\s-*\\(\\(?:\\(?:abstract\\|final\\|private\\|protected\\|public\\|static\\)\\s-+\\)*\\)function\\s-+" + (regexp-quote function) + "\\s-*(") nil t) + (+ (line-end-position) 1)))) + +(defun phpunit-goto-function (function) + "Go to point for `function'" + (let ((pos (phpunit-function-position function))) + (if pos (goto-char pos)))) + +(defun phpunit-switch (&optional other-window) + "Switch between test file and source file" + (interactive "P") + (phpunit-load-config) + (let ((function (phpunit-function-ap)) + (class (phpunit-file-class)) + (find-file-function (if other-window 'find-file-other-window 'find-file)) + file) + (if class + (if (eq (phpunit-get-file-type) 'test) + (progn + (setq file (phpunit-get-source-file (phpunit-get-source-class class))) + (if (and file (file-exists-p file)) + (progn + (funcall find-file-function file) + (and function + (phpunit-goto-function (phpunit-get-source-function (cdr function))))) + (message "Can't locate source file for class '%s'" class))) + (let ((test-class (phpunit-get-test-class class))) + (setq file (phpunit-get-test-file test-class)) + (if (and file (file-exists-p file)) + (progn + (funcall find-file-function file) + (and function + (phpunit-goto-function (phpunit-get-test-function (cdr function))))) + (when (y-or-n-p (format "Test class '%s' not exists, create file %s" test-class file)) + (when (not (file-exists-p (file-name-directory file))) + (make-directory (file-name-directory file) t)) + (phpunit-create-test-1 test-class file + class (file-relative-name buffer-file-name (file-name-directory file)) + other-window))))) + (message "No class found in current buffer")))) + +(defun phpunit-create-test (&optional all) + "Create test method for current class. +With prefix argument, create all test function in current class" + (interactive "P") + (if (eq (phpunit-get-file-type) 'source) + (let ((class (phpunit-file-class)) + (source-file buffer-file-name) + functions class-end pos) + (dolist (function + (if all + (phpunit-file-functions) + (list (phpunit-function-ap)))) + (when (and (consp function) + (not (string-match "protected\\|private" (car function)))) + (push (phpunit-get-test-function (cdr function)) functions))) + (phpunit-switch) + (if functions + (save-excursion + (goto-char (point-min)) + (when (not (re-search-forward phpunit-class-regexp nil t)) + (save-excursion + (funcall phpunit-create-test-function + (phpunit-get-test-class class) buffer-file-name + class source-file)) + (re-search-forward phpunit-class-regexp nil t)) + (when (re-search-forward "{" nil t) + (backward-char 1) + (setq class-end (copy-marker (scan-sexps (point) 1))) + (dolist (function functions) + (unless (phpunit-function-position function) + (goto-char class-end) + (backward-char 1) + (delete-region (progn (skip-chars-backward " \n\t") (point)) (1- class-end)) + (insert (format "\n\n function %s()\n {\n \n }\n" function)) + (setq pos (- (point) 7)))))) + (message "No function to create test")) + (if pos (goto-char pos))) + (message "Not in source file"))) + +(defun phpunit-run-test (&optional all) + "Run test for current function." + (interactive "P") + (let ((class (phpunit-file-class)) + (function (phpunit-function-ap)) + (file-type (phpunit-get-file-type)) + test-class test-file test-function compile-command) + (if class + (progn + (if (eq file-type 'source) + (setq test-file (phpunit-get-test-file (phpunit-get-test-class class)) + test-class (phpunit-get-test-class class)) + (setq test-file buffer-file-name + test-class class)) + (if all + (setq compile-command (format "phpunit %s \"%s\"" class test-file)) + (setq test-function (if (eq file-type 'source) (phpunit-get-test-function (cdr function)) (cdr function))) + (setq compile-command (format "phpunit --filter /::%s$/ %s \"%s\"" test-function class test-file))) + (call-interactively 'compile)) + (message "No test found")))) + +(define-minor-mode phpunit-mode + "Toggle phpunit mode." + :lighter " Phpunit" + :keymap + '(("\C-c\C-tb" . phpunit-switch) + ("\C-c\C-tc" . phpunit-create-test) + ("\C-c\C-tr" . phpunit-run-test) + ("\C-c\C-t\C-b" . phpunit-switch) + ("\C-c\C-t\C-c" . phpunit-create-test) + ("\C-c\C-t\C-r" . phpunit-run-test))) + +(provide 'phpunit) +;;; phpunit.el ends here